forked from admin/services_core
Compare commits
6 Commits
e813e56454
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 22e8e12676 | |||
| b52e67c428 | |||
| 3e4039df67 | |||
| 844b8a0ff0 | |||
| 2dc461ae89 | |||
| 2f805a233d |
@@ -0,0 +1,72 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=base64:nAnc26mWBQ848KLUo97mNg1/iXHA09c9oSrOLW9CWQ0=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
# APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
PHP_CLI_SERVER_WORKERS=4
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
# DB_CONNECTION=sqlite
|
||||||
|
# DB_HOST=127.0.0.1
|
||||||
|
# DB_PORT=3306
|
||||||
|
# DB_DATABASE=laravel
|
||||||
|
# DB_USERNAME=root
|
||||||
|
# DB_PASSWORD=
|
||||||
|
|
||||||
|
DB_CONNECTION=pgsql
|
||||||
|
DB_HOST=103.242.106.56
|
||||||
|
DB_PORT=35432
|
||||||
|
DB_DATABASE=service_core
|
||||||
|
DB_USERNAME=service_core
|
||||||
|
DB_PASSWORD=service_core
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=database
|
||||||
|
|
||||||
|
CACHE_STORE=database
|
||||||
|
# CACHE_PREFIX=
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=log
|
||||||
|
MAIL_SCHEME=null
|
||||||
|
MAIL_HOST=127.0.0.1
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_DEFAULT_REGION=us-east-1
|
||||||
|
AWS_BUCKET=
|
||||||
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
+1
-1
@@ -20,4 +20,4 @@ yarn-error.log
|
|||||||
/.idea
|
/.idea
|
||||||
/.nova
|
/.nova
|
||||||
/.vscode
|
/.vscode
|
||||||
/.zed
|
/.zed
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Docs;
|
|
||||||
|
|
||||||
use OpenApi\Attributes as OA;
|
|
||||||
|
|
||||||
#[OA\Info(
|
|
||||||
version: '1.0.0',
|
|
||||||
title: 'Services Core API',
|
|
||||||
description: 'API Backend Services Core'
|
|
||||||
)]
|
|
||||||
|
|
||||||
#[OA\Server(
|
|
||||||
url: 'http://127.0.0.1:8000',
|
|
||||||
description: 'Local Development Server'
|
|
||||||
)]
|
|
||||||
|
|
||||||
class OpenApiSpec
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum TicketStatus:string
|
||||||
|
{
|
||||||
|
case DRAFT = 'draft';
|
||||||
|
case OPEN = 'open';
|
||||||
|
case APPROVED = 'approved';
|
||||||
|
case ASSIGNED = 'assigned';
|
||||||
|
case PROGRESS = 'progress';
|
||||||
|
case PENDING = 'pending';
|
||||||
|
case RESOLVED = 'resolved';
|
||||||
|
case CLOSED = 'closed';
|
||||||
|
case CANCELLED = 'cancelled';
|
||||||
|
case REJECTED = 'rejected';
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Traits\ApiResponse;
|
||||||
|
|
||||||
|
class ApiController extends Controller
|
||||||
|
{
|
||||||
|
use ApiResponse;
|
||||||
|
}
|
||||||
@@ -5,38 +5,10 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use OpenApi\Attributes as OA;
|
|
||||||
|
|
||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
{
|
{
|
||||||
#[OA\Post(
|
|
||||||
path: "/api/login",
|
|
||||||
summary: "Login User",
|
|
||||||
tags: ["Authentication"]
|
|
||||||
)]
|
|
||||||
#[OA\RequestBody(
|
|
||||||
required: true,
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
required: ["email", "password"],
|
|
||||||
properties: [
|
|
||||||
new OA\Property(
|
|
||||||
property: "email",
|
|
||||||
type: "string",
|
|
||||||
example: "admin@test.com"
|
|
||||||
),
|
|
||||||
new OA\Property(
|
|
||||||
property: "password",
|
|
||||||
type: "string",
|
|
||||||
example: "123456"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: "Login Success"
|
|
||||||
)]
|
|
||||||
|
|
||||||
public function login(Request $request)
|
public function login(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Ticket;
|
||||||
|
|
||||||
|
use App\Http\Controllers\ApiController;
|
||||||
|
use App\Http\Requests\Ticket\StoreTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\UpdateTicketRequest;
|
||||||
|
use App\Http\Resources\Ticket\TicketResource;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Services\Ticket\TicketService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TicketController extends ApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected TicketService $service
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$tickets = Ticket::latest()->paginate(
|
||||||
|
$request->get('per_page', 10)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success($tickets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreTicketRequest $request)
|
||||||
|
{
|
||||||
|
$ticket = $this->service->create(
|
||||||
|
$request->validated()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->created(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil dibuat'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Ticket $ticket)
|
||||||
|
{
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(
|
||||||
|
UpdateTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->update(
|
||||||
|
$ticket,
|
||||||
|
$request->validated()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->updated(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil diperbarui'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Ticket $ticket)
|
||||||
|
{
|
||||||
|
$this->service->delete($ticket);
|
||||||
|
|
||||||
|
return $this->deleted(
|
||||||
|
'Ticket berhasil dibatalkan'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Ticket;
|
||||||
|
|
||||||
|
use App\Http\Controllers\ApiController;
|
||||||
|
use App\Http\Requests\TicketType\StoreTicketTypeRequest;
|
||||||
|
use App\Http\Requests\TicketType\UpdateTicketTypeRequest;
|
||||||
|
use App\Http\Resources\TicketType\TicketTypeResource;
|
||||||
|
use App\Models\TicketType;
|
||||||
|
use App\Services\Ticket\TicketTypeService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TicketTypeController extends ApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected TicketTypeService $service
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$data = TicketType::latest()->paginate(
|
||||||
|
$request->get('per_page', 10)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreTicketTypeRequest $request)
|
||||||
|
{
|
||||||
|
$type = $this->service->create(
|
||||||
|
$request->validated()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->created(
|
||||||
|
new TicketTypeResource($type),
|
||||||
|
'Ticket Type berhasil dibuat'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(TicketType $ticket_type)
|
||||||
|
{
|
||||||
|
return $this->success(
|
||||||
|
new TicketTypeResource($ticket_type)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(
|
||||||
|
UpdateTicketTypeRequest $request,
|
||||||
|
TicketType $ticket_type
|
||||||
|
) {
|
||||||
|
$type = $this->service->update(
|
||||||
|
$ticket_type,
|
||||||
|
$request->validated()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->updated(
|
||||||
|
new TicketTypeResource($type),
|
||||||
|
'Ticket Type berhasil diupdate'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(TicketType $ticket_type)
|
||||||
|
{
|
||||||
|
$this->service->delete($ticket_type);
|
||||||
|
|
||||||
|
return $this->deleted(
|
||||||
|
'Ticket Type berhasil dihapus'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Ticket;
|
||||||
|
|
||||||
|
use App\Http\Controllers\ApiController;
|
||||||
|
use App\Http\Resources\Ticket\TicketResource;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Services\Ticket\TicketWorkflowService;
|
||||||
|
|
||||||
|
use App\Http\Requests\Ticket\AssignTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\ReassignTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\ApproveTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\RejectTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\PendingTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\ResolveTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\CloseTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\CancelTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\EscalateTicketRequest;
|
||||||
|
use App\Http\Requests\Ticket\StartTicketRequest;
|
||||||
|
|
||||||
|
class TicketWorkflowController extends ApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected TicketWorkflowService $service
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* History Workflow
|
||||||
|
*/
|
||||||
|
public function history(Ticket $ticket)
|
||||||
|
{
|
||||||
|
$data = $this->service->history($ticket);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
$data,
|
||||||
|
'Workflow berhasil diambil'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign Teknisi
|
||||||
|
*/
|
||||||
|
public function assign(
|
||||||
|
AssignTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->assign(
|
||||||
|
$ticket,
|
||||||
|
$request->technicians,
|
||||||
|
$request->leader_id,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Teknisi berhasil ditugaskan'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reassign Teknisi
|
||||||
|
*/
|
||||||
|
public function reassign(
|
||||||
|
ReassignTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->reassign(
|
||||||
|
$ticket,
|
||||||
|
$request->technicians,
|
||||||
|
$request->leader_id,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Penugasan berhasil diperbarui'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Approve
|
||||||
|
*/
|
||||||
|
public function approve(
|
||||||
|
ApproveTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->approve(
|
||||||
|
$ticket,
|
||||||
|
$request->notes,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil disetujui'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject
|
||||||
|
*/
|
||||||
|
public function reject(
|
||||||
|
RejectTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->reject(
|
||||||
|
$ticket,
|
||||||
|
$request->reason,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil ditolak'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start Work
|
||||||
|
*/
|
||||||
|
public function start(
|
||||||
|
StartTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->start(
|
||||||
|
$ticket,
|
||||||
|
$request->validated(),
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Pekerjaan dimulai'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending
|
||||||
|
*/
|
||||||
|
public function pending(
|
||||||
|
PendingTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->pending(
|
||||||
|
$ticket,
|
||||||
|
$request->reason,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket pending'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume
|
||||||
|
*/
|
||||||
|
public function resume(Ticket $ticket)
|
||||||
|
{
|
||||||
|
$ticket = $this->service->resume(
|
||||||
|
$ticket,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket dilanjutkan'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve
|
||||||
|
*/
|
||||||
|
public function resolve(
|
||||||
|
ResolveTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->resolve(
|
||||||
|
$ticket,
|
||||||
|
$request->notes,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil diselesaikan'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close
|
||||||
|
*/
|
||||||
|
public function close(
|
||||||
|
CloseTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->close(
|
||||||
|
$ticket,
|
||||||
|
$request->notes,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil ditutup'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel
|
||||||
|
*/
|
||||||
|
public function cancel(
|
||||||
|
CancelTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->cancel(
|
||||||
|
$ticket,
|
||||||
|
$request->reason,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil dibatalkan'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escalate
|
||||||
|
*/
|
||||||
|
public function escalate(
|
||||||
|
EscalateTicketRequest $request,
|
||||||
|
Ticket $ticket
|
||||||
|
) {
|
||||||
|
$ticket = $this->service->escalate(
|
||||||
|
$ticket,
|
||||||
|
$request->reason,
|
||||||
|
auth()->id() ?? 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->success(
|
||||||
|
new TicketResource($ticket),
|
||||||
|
'Ticket berhasil dieskalasi'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ApproveTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'notes' => 'nullable|string|max:1000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class AssignTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'technicians' => 'required|array|min:1',
|
||||||
|
'leader_id' => 'required|integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CancelTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'reason' => 'required|string|max:2000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CloseTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'notes' => 'nullable|string|max:2000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class EscalateTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'reason' => 'required|string|max:2000',
|
||||||
|
'level' => 'nullable|in:L1,L2,L3,NOC'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'reason.required' => 'Alasan eskalasi wajib diisi'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class PendingTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'reason' => 'required|string|max:1000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ReassignTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'technicians' => 'required|array|min:1',
|
||||||
|
'technicians.*' => 'integer',
|
||||||
|
'leader_id' => 'required|integer'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'technicians.required' => 'Daftar teknisi wajib diisi',
|
||||||
|
'technicians.array' => 'Format teknisi harus array',
|
||||||
|
'leader_id.required' => 'Leader teknisi wajib dipilih'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class RejectTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'reason' => 'required|string|max:1000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ResolveTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'notes' => 'required|string|max:2000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StartTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'latitude' => 'nullable|numeric',
|
||||||
|
'longitude' => 'nullable|numeric',
|
||||||
|
'notes' => 'nullable|string|max:1000'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ticket_type_id' => 'required|integer',
|
||||||
|
'customer_id' => 'nullable|integer',
|
||||||
|
'title' => 'required|string|max:255',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'priority' => 'required|in:low,medium,high,critical'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ticket_type_id' => 'sometimes|integer',
|
||||||
|
'customer_id' => 'nullable|integer',
|
||||||
|
'title' => 'sometimes|string|max:255',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'priority' => 'sometimes|in:low,medium,high,critical',
|
||||||
|
'status' => 'sometimes|in:draft,open,approved,assigned,progress,pending,resolved,closed,cancelled,rejected'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\TicketType;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreTicketTypeRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => 'required|string|max:50|unique:ticket_types,code',
|
||||||
|
'name' => 'required|string|max:100',
|
||||||
|
'need_approval' => 'boolean',
|
||||||
|
'sla_minutes' => 'nullable|integer'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\TicketType;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateTicketTypeRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => 'sometimes|string|max:50|unique:ticket_types,code,' . $this->route('ticket_type'),
|
||||||
|
'name' => 'sometimes|string|max:100',
|
||||||
|
'need_approval' => 'sometimes|boolean',
|
||||||
|
'sla_minutes' => 'nullable|integer'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Ticket;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class TicketResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'ticket_no' => $this->ticket_no,
|
||||||
|
'ticket_type_id' => $this->ticket_type_id,
|
||||||
|
'customer_id' => $this->customer_id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'description' => $this->description,
|
||||||
|
'priority' => $this->priority,
|
||||||
|
'status' => $this->status,
|
||||||
|
'created_by' => $this->created_by,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
'updated_at' => $this->updated_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\TicketType;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class TicketTypeResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'code' => $this->code,
|
||||||
|
'name' => $this->name,
|
||||||
|
'need_approval' => $this->need_approval,
|
||||||
|
'sla_minutes' => $this->sla_minutes,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
'updated_at' => $this->updated_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Ticket extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'ticket_no',
|
||||||
|
'ticket_type_id',
|
||||||
|
'customer_id',
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'priority',
|
||||||
|
'status',
|
||||||
|
'created_by',
|
||||||
|
'assigned_at',
|
||||||
|
'approved_by',
|
||||||
|
'approved_at',
|
||||||
|
'rejected_by',
|
||||||
|
'rejected_at',
|
||||||
|
'rejection_reason',
|
||||||
|
'resolved_at',
|
||||||
|
'closed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Relationships
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function ticketType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(
|
||||||
|
TicketType::class,
|
||||||
|
'ticket_type_id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TicketApproval extends Model
|
||||||
|
{
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'ticket_id',
|
||||||
|
'approver_id',
|
||||||
|
'status',
|
||||||
|
'notes',
|
||||||
|
'approved_at',
|
||||||
|
'created_at'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TicketAssignment extends Model
|
||||||
|
{
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'ticket_id',
|
||||||
|
'user_id',
|
||||||
|
'is_leader',
|
||||||
|
'assigned_by',
|
||||||
|
'assigned_at',
|
||||||
|
'status'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TicketFile extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TicketLog extends Model
|
||||||
|
{
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'ticket_id',
|
||||||
|
'user_id',
|
||||||
|
'activity',
|
||||||
|
'notes',
|
||||||
|
'latitude',
|
||||||
|
'longitude',
|
||||||
|
'created_at'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TicketMaterial extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TicketType extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'ticket_types';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'code',
|
||||||
|
'name',
|
||||||
|
'need_approval',
|
||||||
|
'sla_minutes',
|
||||||
|
'require_photo',
|
||||||
|
'require_material',
|
||||||
|
'need_customer',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'need_approval' => 'boolean',
|
||||||
|
'require_photo' => 'boolean',
|
||||||
|
'require_material' => 'boolean',
|
||||||
|
'need_customer' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Relationships
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function tickets()
|
||||||
|
{
|
||||||
|
return $this->hasMany(
|
||||||
|
Ticket::class,
|
||||||
|
'ticket_type_id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Ticket;
|
||||||
|
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Models\TicketLog;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class TicketService
|
||||||
|
{
|
||||||
|
public function create(array $data): Ticket
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($data) {
|
||||||
|
|
||||||
|
$ticket = Ticket::create([
|
||||||
|
'ticket_no' => $this->generateTicketNo(),
|
||||||
|
'ticket_type_id' => $data['ticket_type_id'],
|
||||||
|
'customer_id' => $data['customer_id'] ?? null,
|
||||||
|
'title' => $data['title'],
|
||||||
|
'description' => $data['description'] ?? null,
|
||||||
|
'priority' => $data['priority'],
|
||||||
|
'status' => 'open',
|
||||||
|
'created_by' => 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
TicketLog::create([
|
||||||
|
'ticket_id' => $ticket->id,
|
||||||
|
'user_id' => 1,
|
||||||
|
'activity' => 'Ticket Created',
|
||||||
|
'notes' => 'Ticket dibuat oleh sistem',
|
||||||
|
'created_at'=> now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $ticket;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Ticket $ticket, array $data): Ticket
|
||||||
|
{
|
||||||
|
$ticket->update($data);
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Ticket $ticket): Ticket
|
||||||
|
{
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'cancelled'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateTicketNo(): string
|
||||||
|
{
|
||||||
|
$prefix = 'TCK-' . now()->format('Ym');
|
||||||
|
|
||||||
|
$lastTicket = Ticket::where(
|
||||||
|
'ticket_no',
|
||||||
|
'like',
|
||||||
|
$prefix . '%'
|
||||||
|
)
|
||||||
|
->latest('id')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$number = 1;
|
||||||
|
|
||||||
|
if ($lastTicket) {
|
||||||
|
$number = ((int) substr($lastTicket->ticket_no, -6)) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prefix . '-' . str_pad(
|
||||||
|
$number,
|
||||||
|
6,
|
||||||
|
'0',
|
||||||
|
STR_PAD_LEFT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Ticket;
|
||||||
|
|
||||||
|
use App\Models\TicketType;
|
||||||
|
|
||||||
|
class TicketTypeService
|
||||||
|
{
|
||||||
|
public function create(array $data): TicketType
|
||||||
|
{
|
||||||
|
return TicketType::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(TicketType $type, array $data): TicketType
|
||||||
|
{
|
||||||
|
$type->update($data);
|
||||||
|
return $type->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(TicketType $type): bool
|
||||||
|
{
|
||||||
|
return $type->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Ticket;
|
||||||
|
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Models\TicketLog;
|
||||||
|
use App\Models\TicketApproval;
|
||||||
|
use App\Models\TicketAssignment;
|
||||||
|
use App\Services\Ticket\TicketWorkflowValidator;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class TicketWorkflowService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Workflow History
|
||||||
|
*/
|
||||||
|
public function history(Ticket $ticket)
|
||||||
|
{
|
||||||
|
return TicketLog::where('ticket_id', $ticket->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =========================
|
||||||
|
* VALIDATION RULE ENGINE
|
||||||
|
* =========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
private function type(Ticket $ticket)
|
||||||
|
{
|
||||||
|
return $ticket->ticketType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureRequiredAssets(Ticket $ticket, array $payload = [])
|
||||||
|
{
|
||||||
|
$type = $this->type($ticket);
|
||||||
|
|
||||||
|
// PHOTO VALIDATION
|
||||||
|
if ($type?->require_photo) {
|
||||||
|
$photoCount = $payload['photo_count'] ?? 0;
|
||||||
|
|
||||||
|
if ($photoCount <= 0) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'photo' => 'Foto wajib diupload untuk ticket ini'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MATERIAL VALIDATION
|
||||||
|
if ($type?->require_material) {
|
||||||
|
$materialCount = $payload['material_count'] ?? 0;
|
||||||
|
|
||||||
|
if ($materialCount <= 0) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'material' => 'Material wajib diinput untuk ticket ini'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureCustomer(Ticket $ticket)
|
||||||
|
{
|
||||||
|
$type = $this->type($ticket);
|
||||||
|
|
||||||
|
if ($type?->need_customer && empty($ticket->customer_id)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'customer_id' => 'Customer wajib ada untuk ticket ini'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =========================
|
||||||
|
* ASSIGN / REASSIGN
|
||||||
|
* =========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function assign(
|
||||||
|
Ticket $ticket,
|
||||||
|
array $technicians,
|
||||||
|
int $leaderId,
|
||||||
|
int $userId
|
||||||
|
): Ticket {
|
||||||
|
|
||||||
|
TicketWorkflowValidator::allow($ticket, ['approved']);
|
||||||
|
|
||||||
|
DB::transaction(function () use (
|
||||||
|
$ticket,
|
||||||
|
$technicians,
|
||||||
|
$leaderId,
|
||||||
|
$userId
|
||||||
|
) {
|
||||||
|
|
||||||
|
foreach ($technicians as $technicianId) {
|
||||||
|
TicketAssignment::create([
|
||||||
|
'ticket_id' => $ticket->id,
|
||||||
|
'user_id' => $technicianId,
|
||||||
|
'is_leader' => ($technicianId == $leaderId),
|
||||||
|
'assigned_by' => $userId,
|
||||||
|
'assigned_at' => now(),
|
||||||
|
'status' => 'assigned'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'assigned',
|
||||||
|
'assigned_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Assign', 'Teknisi ditugaskan');
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reassign(
|
||||||
|
Ticket $ticket,
|
||||||
|
array $technicians,
|
||||||
|
int $leaderId,
|
||||||
|
int $userId
|
||||||
|
): Ticket {
|
||||||
|
|
||||||
|
DB::transaction(function () use (
|
||||||
|
$ticket,
|
||||||
|
$technicians,
|
||||||
|
$leaderId,
|
||||||
|
$userId
|
||||||
|
) {
|
||||||
|
|
||||||
|
TicketAssignment::where('ticket_id', $ticket->id)->delete();
|
||||||
|
|
||||||
|
foreach ($technicians as $technicianId) {
|
||||||
|
TicketAssignment::create([
|
||||||
|
'ticket_id' => $ticket->id,
|
||||||
|
'user_id' => $technicianId,
|
||||||
|
'is_leader' => ($technicianId == $leaderId),
|
||||||
|
'assigned_by' => $userId,
|
||||||
|
'assigned_at' => now(),
|
||||||
|
'status' => 'assigned'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Reassign', 'Penugasan diperbarui');
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =========================
|
||||||
|
* APPROVAL FLOW
|
||||||
|
* =========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function approve(
|
||||||
|
Ticket $ticket,
|
||||||
|
?string $notes,
|
||||||
|
int $userId
|
||||||
|
): Ticket {
|
||||||
|
|
||||||
|
TicketWorkflowValidator::allow($ticket, ['open']);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($ticket, $notes, $userId) {
|
||||||
|
|
||||||
|
TicketApproval::create([
|
||||||
|
'ticket_id' => $ticket->id,
|
||||||
|
'approver_id' => $userId,
|
||||||
|
'status' => 'approved',
|
||||||
|
'notes' => $notes,
|
||||||
|
'approved_at' => now(),
|
||||||
|
'created_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'approved',
|
||||||
|
'approved_by' => $userId,
|
||||||
|
'approved_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Approved', $notes);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject(Ticket $ticket, string $reason, int $userId): Ticket
|
||||||
|
{
|
||||||
|
DB::transaction(function () use ($ticket, $reason, $userId) {
|
||||||
|
|
||||||
|
TicketApproval::create([
|
||||||
|
'ticket_id' => $ticket->id,
|
||||||
|
'approver_id' => $userId,
|
||||||
|
'status' => 'rejected',
|
||||||
|
'notes' => $reason,
|
||||||
|
'created_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'rejected',
|
||||||
|
'rejected_by' => $userId,
|
||||||
|
'rejected_at' => now(),
|
||||||
|
'rejection_reason' => $reason
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Rejected', $reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =========================
|
||||||
|
* WORKFLOW START
|
||||||
|
* =========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function start(Ticket $ticket, array $location, int $userId): Ticket
|
||||||
|
{
|
||||||
|
TicketWorkflowValidator::allow($ticket, ['assigned']);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($ticket, $location, $userId) {
|
||||||
|
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'progress'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log(
|
||||||
|
$ticket->id,
|
||||||
|
$userId,
|
||||||
|
'Start Work',
|
||||||
|
'Pekerjaan dimulai'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pending(Ticket $ticket, string $reason, int $userId): Ticket
|
||||||
|
{
|
||||||
|
$ticket->update(['status' => 'pending']);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Pending', $reason);
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resume(Ticket $ticket, int $userId): Ticket
|
||||||
|
{
|
||||||
|
$ticket->update(['status' => 'progress']);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Resume', 'Dilanjutkan');
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =========================
|
||||||
|
* RESOLVE (VALIDATION POINT)
|
||||||
|
* =========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function resolve(
|
||||||
|
Ticket $ticket,
|
||||||
|
string $notes,
|
||||||
|
int $userId,
|
||||||
|
array $payload = []
|
||||||
|
): Ticket {
|
||||||
|
|
||||||
|
TicketWorkflowValidator::allow($ticket, ['progress']);
|
||||||
|
|
||||||
|
// 🔥 VALIDASI MATERIAL + PHOTO + CUSTOMER
|
||||||
|
$this->ensureRequiredAssets($ticket, $payload);
|
||||||
|
$this->ensureCustomer($ticket);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($ticket, $notes, $userId) {
|
||||||
|
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'resolved',
|
||||||
|
'resolved_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Resolved', $notes);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(Ticket $ticket, ?string $notes, int $userId): Ticket
|
||||||
|
{
|
||||||
|
TicketWorkflowValidator::allow($ticket, ['resolved']);
|
||||||
|
|
||||||
|
$ticket->update([
|
||||||
|
'status' => 'closed',
|
||||||
|
'closed_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Closed', $notes);
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(Ticket $ticket, string $reason, int $userId): Ticket
|
||||||
|
{
|
||||||
|
$ticket->update(['status' => 'cancelled']);
|
||||||
|
|
||||||
|
$this->log($ticket->id, $userId, 'Cancelled', $reason);
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function escalate(Ticket $ticket, string $reason, int $userId): Ticket
|
||||||
|
{
|
||||||
|
$this->log($ticket->id, $userId, 'Escalated', $reason);
|
||||||
|
|
||||||
|
return $ticket->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =========================
|
||||||
|
* LOG SYSTEM
|
||||||
|
* =========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
private function log(
|
||||||
|
int $ticketId,
|
||||||
|
int $userId,
|
||||||
|
string $activity,
|
||||||
|
?string $notes = null
|
||||||
|
): void {
|
||||||
|
|
||||||
|
TicketLog::create([
|
||||||
|
'ticket_id' => $ticketId,
|
||||||
|
'user_id' => $userId,
|
||||||
|
'activity' => $activity,
|
||||||
|
'notes' => $notes,
|
||||||
|
'created_at'=> now()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Ticket;
|
||||||
|
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class TicketWorkflowValidator
|
||||||
|
{
|
||||||
|
public static function allow(
|
||||||
|
Ticket $ticket,
|
||||||
|
array $allowedStatuses
|
||||||
|
): void {
|
||||||
|
|
||||||
|
if (! in_array($ticket->status, $allowedStatuses)) {
|
||||||
|
|
||||||
|
throw new Exception(
|
||||||
|
"Ticket status '{$ticket->status}' tidak valid untuk proses ini"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
trait ApiResponse
|
||||||
|
{
|
||||||
|
protected function success($data = null, string $message = 'Success', int $code = 200)
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data
|
||||||
|
], $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function created($data = null, string $message = 'Data berhasil dibuat')
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => 201,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updated($data = null, string $message = 'Data berhasil diperbarui')
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleted(string $message = 'Data berhasil dihapus')
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'message' => $message
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function error(string $message = 'Internal Server Error', int $code = 500)
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message
|
||||||
|
], $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validationError($errors)
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'code' => 422,
|
||||||
|
'message' => 'Validation Error',
|
||||||
|
'errors' => $errors
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function notFound(string $message = 'Data tidak ditemukan')
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'code' => 404,
|
||||||
|
'message' => $message
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
|
|
||||||
->withMiddleware(function (Middleware $middleware) {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
//
|
//
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
info:
|
||||||
|
name: Add Tickets
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: http://127.0.0.1:8000/api/tickets
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"user_id": 1,
|
||||||
|
"ticket_type_id": 1,
|
||||||
|
"customer_id": 133,
|
||||||
|
"title": "Lampu PON modem merah",
|
||||||
|
"description": "Pelanggan mengeluhkan internet lambat",
|
||||||
|
"priority": "high"
|
||||||
|
}
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: ll4X9vVjWRRDGbGQyrzcAhWVHmlEbpmQ7odklBCh7423957d
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
info:
|
||||||
|
name: Get List Tickets
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://127.0.0.1:8000/api/tickets
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: ll4X9vVjWRRDGbGQyrzcAhWVHmlEbpmQ7odklBCh7423957d
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: Ticketing
|
||||||
|
type: folder
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: Untitled
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: ""
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
+1
-1
@@ -7,12 +7,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"darkaonline/l5-swagger": "^11.1",
|
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/sanctum": "^4.3",
|
"laravel/sanctum": "^4.3",
|
||||||
"laravel/tinker": "^2.10.1"
|
"laravel/tinker": "^2.10.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"dedoc/scramble": "^0.13.28",
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
"laravel/pail": "^1.2.2",
|
"laravel/pail": "^1.2.2",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pint": "^1.13",
|
||||||
|
|||||||
Generated
+232
-467
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "13ca615bebb1db69527a7039d10b8684",
|
"content-hash": "dc5dc467fbab2c5682c8e676f4c2ffb4",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -135,86 +135,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-02-09T16:56:22+00:00"
|
"time": "2024-02-09T16:56:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "darkaonline/l5-swagger",
|
|
||||||
"version": "11.1.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/DarkaOnLine/L5-Swagger.git",
|
|
||||||
"reference": "110b59478c9417c13794cef62a82b019433d642a"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/110b59478c9417c13794cef62a82b019433d642a",
|
|
||||||
"reference": "110b59478c9417c13794cef62a82b019433d642a",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"ext-json": "*",
|
|
||||||
"laravel/framework": "^13.0 || ^12.1 || ^11.44",
|
|
||||||
"php": "^8.2",
|
|
||||||
"swagger-api/swagger-ui": ">=5.18.3",
|
|
||||||
"symfony/yaml": "^5.0 || ^6.0 || ^7.0 || ^8.0",
|
|
||||||
"zircote/swagger-php": "^6.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"mockery/mockery": "1.*",
|
|
||||||
"orchestra/testbench": "^11.0 || ^10.0 || ^9.0 || ^8.0 || 7.* || ^6.15 || 5.*",
|
|
||||||
"php-coveralls/php-coveralls": "^2.0",
|
|
||||||
"phpstan/phpstan": "^2.1",
|
|
||||||
"phpunit/phpunit": "^11.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"laravel": {
|
|
||||||
"aliases": {
|
|
||||||
"L5Swagger": "L5Swagger\\L5SwaggerFacade"
|
|
||||||
},
|
|
||||||
"providers": [
|
|
||||||
"L5Swagger\\L5SwaggerServiceProvider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"files": [
|
|
||||||
"src/helpers.php"
|
|
||||||
],
|
|
||||||
"psr-4": {
|
|
||||||
"L5Swagger\\": "src"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Darius Matulionis",
|
|
||||||
"email": "darius@matulionis.lt"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "OpenApi or Swagger integration to Laravel",
|
|
||||||
"keywords": [
|
|
||||||
"api",
|
|
||||||
"documentation",
|
|
||||||
"laravel",
|
|
||||||
"openapi",
|
|
||||||
"specification",
|
|
||||||
"swagger",
|
|
||||||
"ui"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/DarkaOnLine/L5-Swagger/issues",
|
|
||||||
"source": "https://github.com/DarkaOnLine/L5-Swagger/tree/11.1.0"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://github.com/DarkaOnLine",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2026-06-12T10:04:41+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "dflydev/dot-access-data",
|
"name": "dflydev/dot-access-data",
|
||||||
"version": "v3.0.3",
|
"version": "v3.0.3",
|
||||||
@@ -2754,53 +2674,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-12-27T19:41:33+00:00"
|
"time": "2025-12-27T19:41:33+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "phpstan/phpdoc-parser",
|
|
||||||
"version": "2.3.2",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
|
||||||
"reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
|
|
||||||
"reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^7.4 || ^8.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"doctrine/annotations": "^2.0",
|
|
||||||
"nikic/php-parser": "^5.3.0",
|
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
|
||||||
"phpstan/extension-installer": "^1.0",
|
|
||||||
"phpstan/phpstan": "^2.0",
|
|
||||||
"phpstan/phpstan-phpunit": "^2.0",
|
|
||||||
"phpstan/phpstan-strict-rules": "^2.0",
|
|
||||||
"phpunit/phpunit": "^9.6",
|
|
||||||
"symfony/process": "^5.2"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"PHPStan\\PhpDocParser\\": [
|
|
||||||
"src/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
|
||||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
|
|
||||||
},
|
|
||||||
"time": "2026-01-25T14:56:51+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "psr/clock",
|
"name": "psr/clock",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -3292,68 +3165,6 @@
|
|||||||
},
|
},
|
||||||
"time": "2026-05-23T13:41:31+00:00"
|
"time": "2026-05-23T13:41:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "radebatz/type-info-extras",
|
|
||||||
"version": "1.0.7",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/DerManoMann/type-info-extras.git",
|
|
||||||
"reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/95a524a74a61648b44e355cb33d38db4b17ef5ce",
|
|
||||||
"reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=8.2",
|
|
||||||
"phpstan/phpdoc-parser": "^2.0",
|
|
||||||
"symfony/type-info": "^7.3.8 || ^7.4.1 || ^8.0 || ^8.1-@dev"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"friendsofphp/php-cs-fixer": "^3.70",
|
|
||||||
"phpstan/phpstan": "^2.1",
|
|
||||||
"phpunit/phpunit": "^11.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Radebatz\\TypeInfoExtras\\": "src"
|
|
||||||
},
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Martin Rademacher",
|
|
||||||
"email": "mano@radebatz.org"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Extras for symfony/type-info",
|
|
||||||
"homepage": "http://radebatz.net/mano/",
|
|
||||||
"keywords": [
|
|
||||||
"component",
|
|
||||||
"symfony",
|
|
||||||
"type-info",
|
|
||||||
"types"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/DerManoMann/type-info-extras/issues",
|
|
||||||
"source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.7"
|
|
||||||
},
|
|
||||||
"time": "2026-03-06T22:40:29+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "ralouphie/getallheaders",
|
"name": "ralouphie/getallheaders",
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@@ -3552,67 +3363,6 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-12-14T04:43:48+00:00"
|
"time": "2025-12-14T04:43:48+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "swagger-api/swagger-ui",
|
|
||||||
"version": "v5.32.6",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/swagger-api/swagger-ui.git",
|
|
||||||
"reference": "dcdca62c8b64a0a54e4decd4e1a6c3c712fdcc60"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/dcdca62c8b64a0a54e4decd4e1a6c3c712fdcc60",
|
|
||||||
"reference": "dcdca62c8b64a0a54e4decd4e1a6c3c712fdcc60",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"Apache-2.0"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Anna Bodnia",
|
|
||||||
"email": "anna.bodnia@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Buu Nguyen",
|
|
||||||
"email": "buunguyen@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Josh Ponelat",
|
|
||||||
"email": "jponelat@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Kyle Shockey",
|
|
||||||
"email": "kyleshockey1@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Robert Barnwell",
|
|
||||||
"email": "robert@robertismy.name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sahar Jafari",
|
|
||||||
"email": "shr.jafari@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.",
|
|
||||||
"homepage": "http://swagger.io",
|
|
||||||
"keywords": [
|
|
||||||
"api",
|
|
||||||
"documentation",
|
|
||||||
"openapi",
|
|
||||||
"specification",
|
|
||||||
"swagger",
|
|
||||||
"ui"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/swagger-api/swagger-ui/issues",
|
|
||||||
"source": "https://github.com/swagger-api/swagger-ui/tree/v5.32.6"
|
|
||||||
},
|
|
||||||
"time": "2026-05-12T09:35:37+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "symfony/clock",
|
"name": "symfony/clock",
|
||||||
"version": "v7.4.8",
|
"version": "v7.4.8",
|
||||||
@@ -5957,89 +5707,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-01-05T13:30:16+00:00"
|
"time": "2026-01-05T13:30:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "symfony/type-info",
|
|
||||||
"version": "v7.4.9",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/type-info.git",
|
|
||||||
"reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/type-info/zipball/cafeedbf157b890e94ac5b83eaed85595106d5d6",
|
|
||||||
"reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=8.2",
|
|
||||||
"psr/container": "^1.1|^2.0",
|
|
||||||
"symfony/deprecation-contracts": "^2.5|^3"
|
|
||||||
},
|
|
||||||
"conflict": {
|
|
||||||
"phpstan/phpdoc-parser": "<1.30"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpstan/phpdoc-parser": "^1.30|^2.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Component\\TypeInfo\\": ""
|
|
||||||
},
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/Tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Mathias Arlaud",
|
|
||||||
"email": "mathias.arlaud@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Baptiste LEDUC",
|
|
||||||
"email": "baptiste.leduc@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Extracts PHP types information.",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"PHPStan",
|
|
||||||
"phpdoc",
|
|
||||||
"symfony",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"source": "https://github.com/symfony/type-info/tree/v7.4.9"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/nicolas-grekas",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2026-04-22T15:21:55+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "symfony/uid",
|
"name": "symfony/uid",
|
||||||
"version": "v7.4.9",
|
"version": "v7.4.9",
|
||||||
@@ -6205,82 +5872,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-03-30T13:44:50+00:00"
|
"time": "2026-03-30T13:44:50+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "symfony/yaml",
|
|
||||||
"version": "v7.4.13",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/yaml.git",
|
|
||||||
"reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/a7ec3b1156faf8815db7683ec7c1e7338e6f977c",
|
|
||||||
"reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=8.2",
|
|
||||||
"symfony/deprecation-contracts": "^2.5|^3",
|
|
||||||
"symfony/polyfill-ctype": "^1.8"
|
|
||||||
},
|
|
||||||
"conflict": {
|
|
||||||
"symfony/console": "<6.4"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"symfony/console": "^6.4|^7.0|^8.0"
|
|
||||||
},
|
|
||||||
"bin": [
|
|
||||||
"Resources/bin/yaml-lint"
|
|
||||||
],
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Component\\Yaml\\": ""
|
|
||||||
},
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/Tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Fabien Potencier",
|
|
||||||
"email": "fabien@symfony.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Loads and dumps YAML files",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"support": {
|
|
||||||
"source": "https://github.com/symfony/yaml/tree/v7.4.13"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/nicolas-grekas",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2026-05-25T06:06:12+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "tijsverkoyen/css-to-inline-styles",
|
"name": "tijsverkoyen/css-to-inline-styles",
|
||||||
"version": "v2.4.0",
|
"version": "v2.4.0",
|
||||||
@@ -6493,99 +6084,89 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-04-26T05:33:54+00:00"
|
"time": "2026-04-26T05:33:54+00:00"
|
||||||
},
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "zircote/swagger-php",
|
"name": "dedoc/scramble",
|
||||||
"version": "6.2.0",
|
"version": "v0.13.28",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zircote/swagger-php.git",
|
"url": "https://github.com/dedoc/scramble.git",
|
||||||
"reference": "060af3bb9c4cba6a5859aba2c51cd1c129479410"
|
"reference": "e50927c732a341bb743671066892eec6bd2e958b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/060af3bb9c4cba6a5859aba2c51cd1c129479410",
|
"url": "https://api.github.com/repos/dedoc/scramble/zipball/e50927c732a341bb743671066892eec6bd2e958b",
|
||||||
"reference": "060af3bb9c4cba6a5859aba2c51cd1c129479410",
|
"reference": "e50927c732a341bb743671066892eec6bd2e958b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"nikic/php-parser": "^4.19 || ^5.0",
|
"illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
|
||||||
"php": ">=8.2",
|
"myclabs/deep-copy": "^1.12",
|
||||||
"phpstan/phpdoc-parser": "^2.0",
|
"nikic/php-parser": "^5.0",
|
||||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
"php": "^8.1",
|
||||||
"radebatz/type-info-extras": "^1.0.2",
|
"phpstan/phpdoc-parser": "^1.0|^2.0",
|
||||||
"symfony/console": "^7.4 || ^8.0",
|
"spatie/laravel-package-tools": "^1.9.2"
|
||||||
"symfony/deprecation-contracts": "^2 || ^3",
|
|
||||||
"symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0",
|
|
||||||
"symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0"
|
|
||||||
},
|
|
||||||
"conflict": {
|
|
||||||
"symfony/process": ">=6, <6.4.14"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"composer/package-versions-deprecated": "^1.11",
|
"larastan/larastan": "^3.3",
|
||||||
"doctrine/annotations": "^2.0",
|
"laravel/pint": "^v1.1.0",
|
||||||
"friendsofphp/php-cs-fixer": "^3.62.0",
|
"nunomaduro/collision": "^7.0|^8.0",
|
||||||
"phpstan/phpstan": "^2.0",
|
"orchestra/testbench": "^8.0|^9.0|^10.0|^11.0",
|
||||||
"phpunit/phpunit": "^11.5 || >=12.5.22",
|
"pestphp/pest": "^2.34|^3.7|^4.4",
|
||||||
"rector/rector": "^2.3.1"
|
"pestphp/pest-plugin-laravel": "^2.3|^3.1|^4.1",
|
||||||
|
"phpstan/extension-installer": "^1.4",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.5|^11.5.3|^12.5.12",
|
||||||
|
"spatie/laravel-permission": "^6.10|^7.2",
|
||||||
|
"spatie/pest-plugin-snapshots": "^2.1"
|
||||||
},
|
},
|
||||||
"bin": [
|
|
||||||
"bin/openapi"
|
|
||||||
],
|
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"laravel": {
|
||||||
"dev-master": "6.x-dev"
|
"providers": [
|
||||||
|
"Dedoc\\Scramble\\ScrambleServiceProvider"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"OpenApi\\": "src"
|
"Dedoc\\Scramble\\": "src",
|
||||||
|
"Dedoc\\Scramble\\Database\\Factories\\": "database/factories"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
"license": [
|
"license": [
|
||||||
"Apache-2.0"
|
"MIT"
|
||||||
],
|
],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Robert Allen",
|
"name": "Roman Lytvynenko",
|
||||||
"email": "zircote@gmail.com"
|
"email": "litvinenko95@gmail.com",
|
||||||
},
|
"role": "Developer"
|
||||||
{
|
|
||||||
"name": "Bob Fanger",
|
|
||||||
"email": "bfanger@gmail.com",
|
|
||||||
"homepage": "https://bfanger.nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Martin Rademacher",
|
|
||||||
"email": "mano@radebatz.net",
|
|
||||||
"homepage": "https://radebatz.net"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations",
|
"description": "Automatic generation of API documentation for Laravel applications.",
|
||||||
"homepage": "https://github.com/zircote/swagger-php",
|
"homepage": "https://github.com/dedoc/scramble",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"api",
|
"documentation",
|
||||||
"json",
|
"laravel",
|
||||||
"rest",
|
"openapi"
|
||||||
"service discovery"
|
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/zircote/swagger-php/issues",
|
"issues": "https://github.com/dedoc/scramble/issues",
|
||||||
"source": "https://github.com/zircote/swagger-php/tree/6.2.0"
|
"source": "https://github.com/dedoc/scramble/tree/v0.13.28"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"url": "https://github.com/zircote",
|
"url": "https://github.com/romalytvynenko",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-06-14T06:51:56+00:00"
|
"time": "2026-06-14T18:21:12+00:00"
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"packages-dev": [
|
|
||||||
{
|
{
|
||||||
"name": "fakerphp/faker",
|
"name": "fakerphp/faker",
|
||||||
"version": "v1.24.1",
|
"version": "v1.24.1",
|
||||||
@@ -7339,6 +6920,53 @@
|
|||||||
},
|
},
|
||||||
"time": "2022-02-21T01:04:05+00:00"
|
"time": "2022-02-21T01:04:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpdoc-parser",
|
||||||
|
"version": "2.3.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||||
|
"reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
|
||||||
|
"reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/annotations": "^2.0",
|
||||||
|
"nikic/php-parser": "^5.3.0",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
|
"phpstan/extension-installer": "^1.0",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9.6",
|
||||||
|
"symfony/process": "^5.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPStan\\PhpDocParser\\": [
|
||||||
|
"src/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||||
|
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
|
||||||
|
},
|
||||||
|
"time": "2026-01-25T14:56:51+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "11.0.12",
|
"version": "11.0.12",
|
||||||
@@ -8782,6 +8410,67 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-10-09T05:16:32+00:00"
|
"time": "2024-10-09T05:16:32+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/laravel-package-tools",
|
||||||
|
"version": "1.93.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/laravel-package-tools.git",
|
||||||
|
"reference": "d5552849801f2642aea710557463234b59ef65eb"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d5552849801f2642aea710557463234b59ef65eb",
|
||||||
|
"reference": "d5552849801f2642aea710557463234b59ef65eb",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.5",
|
||||||
|
"orchestra/testbench": "^8.0|^9.2|^10.0|^11.0",
|
||||||
|
"pestphp/pest": "^2.1|^3.1|^4.0",
|
||||||
|
"phpunit/php-code-coverage": "^10.0|^11.0|^12.0",
|
||||||
|
"phpunit/phpunit": "^10.5|^11.5|^12.5",
|
||||||
|
"spatie/pest-plugin-test-time": "^2.2|^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\LaravelPackageTools\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Tools for creating Laravel packages",
|
||||||
|
"homepage": "https://github.com/spatie/laravel-package-tools",
|
||||||
|
"keywords": [
|
||||||
|
"laravel-package-tools",
|
||||||
|
"spatie"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/laravel-package-tools/issues",
|
||||||
|
"source": "https://github.com/spatie/laravel-package-tools/tree/1.93.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-19T14:06:37+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "staabm/side-effects-detector",
|
"name": "staabm/side-effects-detector",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@@ -8834,6 +8523,82 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-10-20T05:08:20+00:00"
|
"time": "2024-10-20T05:08:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/yaml",
|
||||||
|
"version": "v7.4.13",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/yaml.git",
|
||||||
|
"reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/yaml/zipball/a7ec3b1156faf8815db7683ec7c1e7338e6f977c",
|
||||||
|
"reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/polyfill-ctype": "^1.8"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/console": "<6.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/console": "^6.4|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"Resources/bin/yaml-lint"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Yaml\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Loads and dumps YAML files",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/yaml/tree/v7.4.13"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-25T06:06:12+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "theseer/tokenizer",
|
"name": "theseer/tokenizer",
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
'default' => 'default',
|
|
||||||
'documentations' => [
|
|
||||||
'default' => [
|
|
||||||
'api' => [
|
|
||||||
'title' => 'L5 Swagger UI',
|
|
||||||
],
|
|
||||||
|
|
||||||
'routes' => [
|
|
||||||
/*
|
|
||||||
* Route for accessing api documentation interface
|
|
||||||
*/
|
|
||||||
'api' => 'api/documentation',
|
|
||||||
],
|
|
||||||
'paths' => [
|
|
||||||
/*
|
|
||||||
* Edit to include full URL in ui for assets
|
|
||||||
*/
|
|
||||||
'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Edit to set path where swagger ui assets should be stored
|
|
||||||
*/
|
|
||||||
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File name of the generated json documentation file
|
|
||||||
*/
|
|
||||||
'docs_json' => 'api-docs.json',
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File name of the generated YAML documentation file
|
|
||||||
*/
|
|
||||||
'docs_yaml' => 'api-docs.yaml',
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set this to `json` or `yaml` to determine which documentation file to use in UI
|
|
||||||
*/
|
|
||||||
'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Absolute paths to directory containing the swagger annotations are stored.
|
|
||||||
*/
|
|
||||||
'annotations' => [
|
|
||||||
base_path('app'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'defaults' => [
|
|
||||||
'routes' => [
|
|
||||||
/*
|
|
||||||
* Route for accessing parsed swagger annotations.
|
|
||||||
*/
|
|
||||||
'docs' => 'docs',
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Route for Oauth2 authentication callback.
|
|
||||||
*/
|
|
||||||
'oauth2_callback' => 'api/oauth2-callback',
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Middleware allows to prevent unexpected access to API documentation
|
|
||||||
*/
|
|
||||||
'middleware' => [
|
|
||||||
'api' => [],
|
|
||||||
'asset' => [],
|
|
||||||
'docs' => [],
|
|
||||||
'oauth2_callback' => [],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Route Group options
|
|
||||||
*/
|
|
||||||
'group_options' => [],
|
|
||||||
],
|
|
||||||
|
|
||||||
'paths' => [
|
|
||||||
/*
|
|
||||||
* Absolute path to location where parsed annotations will be stored
|
|
||||||
*/
|
|
||||||
'docs' => storage_path('api-docs'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Absolute path to directory where to export views
|
|
||||||
*/
|
|
||||||
'views' => base_path('resources/views/vendor/l5-swagger'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Edit to set the api's base path
|
|
||||||
*/
|
|
||||||
'base' => env('L5_SWAGGER_BASE_PATH', null),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Absolute path to directories that should be excluded from scanning
|
|
||||||
* @deprecated Please use `scanOptions.exclude`
|
|
||||||
* `scanOptions.exclude` overwrites this
|
|
||||||
*/
|
|
||||||
'excludes' => [],
|
|
||||||
],
|
|
||||||
|
|
||||||
'scanOptions' => [
|
|
||||||
/**
|
|
||||||
* Optional CustomGeneratorInterface implementation that creates an OpenApi\Generator instance.
|
|
||||||
* Use this to provide a custom pre-configured generator.
|
|
||||||
* Accepts an instance or a class name (FQCN) implementing the interface.
|
|
||||||
*
|
|
||||||
* @see \L5Swagger\CustomGeneratorInterface
|
|
||||||
*/
|
|
||||||
'generator_factory' => null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for default processors. Allows to pass processors configuration to swagger-php.
|
|
||||||
*
|
|
||||||
* @link https://zircote.github.io/swagger-php/reference/processors.html
|
|
||||||
*/
|
|
||||||
'default_processors_configuration' => [
|
|
||||||
/** Example */
|
|
||||||
/**
|
|
||||||
* 'operationId.hash' => true,
|
|
||||||
* 'pathFilter' => [
|
|
||||||
* 'tags' => [
|
|
||||||
* '/pets/',
|
|
||||||
* '/store/',
|
|
||||||
* ],
|
|
||||||
* ],.
|
|
||||||
*/
|
|
||||||
],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* analyser: defaults to \OpenApi\StaticAnalyser .
|
|
||||||
*
|
|
||||||
* @see \OpenApi\scan
|
|
||||||
*/
|
|
||||||
'analyser' => null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* analysis: defaults to a new \OpenApi\Analysis .
|
|
||||||
*
|
|
||||||
* @see \OpenApi\scan
|
|
||||||
*/
|
|
||||||
'analysis' => null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom processors.
|
|
||||||
*
|
|
||||||
* Each entry can be:
|
|
||||||
* - A class name or instance (inserted after BuildPaths by default)
|
|
||||||
* - An array with 'class' and 'after' keys for precise positioning:
|
|
||||||
* ['class' => MyProcessor::class, 'after' => SomeProcessor::class]
|
|
||||||
*
|
|
||||||
* @link https://github.com/zircote/swagger-php/tree/master/Examples/processors/schema-query-parameter
|
|
||||||
* @see \OpenApi\scan
|
|
||||||
*/
|
|
||||||
'processors' => [
|
|
||||||
// \App\SwaggerProcessors\SchemaQueryParameter::class,
|
|
||||||
// ['class' => \App\SwaggerProcessors\Custom::class, 'after' => \OpenApi\Processors\AugmentSchemas::class],
|
|
||||||
],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pattern: string $pattern File pattern(s) to scan (default: *.php) .
|
|
||||||
*
|
|
||||||
* @see \OpenApi\scan
|
|
||||||
*/
|
|
||||||
'pattern' => null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Absolute path to directories that should be excluded from scanning
|
|
||||||
* @note This option overwrites `paths.excludes`
|
|
||||||
* @see \OpenApi\scan
|
|
||||||
*/
|
|
||||||
'exclude' => [],
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allows to generate specs either for OpenAPI 3.0.0 or OpenAPI 3.1.0.
|
|
||||||
* By default the spec will be in version 3.0.0
|
|
||||||
*/
|
|
||||||
'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', \L5Swagger\Generator::OPEN_API_DEFAULT_SPEC_VERSION),
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
* API security definitions. Will be generated into documentation file.
|
|
||||||
*/
|
|
||||||
'securityDefinitions' => [
|
|
||||||
'securitySchemes' => [
|
|
||||||
/*
|
|
||||||
* Examples of Security schemes
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
'api_key_security_example' => [ // Unique name of security
|
|
||||||
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
|
||||||
'description' => 'A short description for security scheme',
|
|
||||||
'name' => 'api_key', // The name of the header or query parameter to be used.
|
|
||||||
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
|
|
||||||
],
|
|
||||||
'oauth2_security_example' => [ // Unique name of security
|
|
||||||
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
|
||||||
'description' => 'A short description for oauth2 security scheme.',
|
|
||||||
'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode".
|
|
||||||
'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode)
|
|
||||||
//'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode)
|
|
||||||
'scopes' => [
|
|
||||||
'read:projects' => 'read your projects',
|
|
||||||
'write:projects' => 'modify projects in your account',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Open API 3.0 support
|
|
||||||
'passport' => [ // Unique name of security
|
|
||||||
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
|
||||||
'description' => 'Laravel passport oauth2 security.',
|
|
||||||
'in' => 'header',
|
|
||||||
'scheme' => 'https',
|
|
||||||
'flows' => [
|
|
||||||
"password" => [
|
|
||||||
"authorizationUrl" => config('app.url') . '/oauth/authorize',
|
|
||||||
"tokenUrl" => config('app.url') . '/oauth/token',
|
|
||||||
"refreshUrl" => config('app.url') . '/token/refresh',
|
|
||||||
"scopes" => []
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'sanctum' => [ // Unique name of security
|
|
||||||
'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2".
|
|
||||||
'description' => 'Enter token in format (Bearer <token>)',
|
|
||||||
'name' => 'Authorization', // The name of the header or query parameter to be used.
|
|
||||||
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
|
|
||||||
],
|
|
||||||
*/
|
|
||||||
],
|
|
||||||
'security' => [
|
|
||||||
/*
|
|
||||||
* Examples of Securities
|
|
||||||
*/
|
|
||||||
[
|
|
||||||
/*
|
|
||||||
'oauth2_security_example' => [
|
|
||||||
'read',
|
|
||||||
'write'
|
|
||||||
],
|
|
||||||
|
|
||||||
'passport' => []
|
|
||||||
*/
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set this to `true` in development mode so that docs would be regenerated on each request
|
|
||||||
* Set this to `false` to disable swagger generation on production
|
|
||||||
*/
|
|
||||||
'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set this to `true` to generate a copy of documentation in yaml format
|
|
||||||
*/
|
|
||||||
'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
|
|
||||||
* string[]
|
|
||||||
*/
|
|
||||||
'proxy' => false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
|
|
||||||
* See more at: https://github.com/swagger-api/swagger-ui#configs-plugin
|
|
||||||
*/
|
|
||||||
'additional_config_url' => null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically),
|
|
||||||
* 'method' (sort by HTTP method).
|
|
||||||
* Default is the order returned by the server unchanged.
|
|
||||||
*/
|
|
||||||
'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Pass the validatorUrl parameter to SwaggerUi init on the JS side.
|
|
||||||
* A null value here disables validation.
|
|
||||||
*/
|
|
||||||
'validator_url' => null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Swagger UI configuration parameters
|
|
||||||
*/
|
|
||||||
'ui' => [
|
|
||||||
'display' => [
|
|
||||||
'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false),
|
|
||||||
/*
|
|
||||||
* Controls the default expansion setting for the operations and tags. It can be :
|
|
||||||
* 'list' (expands only the tags),
|
|
||||||
* 'full' (expands the tags and operations),
|
|
||||||
* 'none' (expands nothing).
|
|
||||||
*/
|
|
||||||
'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set, enables filtering. The top bar will show an edit box that
|
|
||||||
* you can use to filter the tagged operations that are shown. Can be
|
|
||||||
* Boolean to enable or disable, or a string, in which case filtering
|
|
||||||
* will be enabled using that string as the filter expression. Filtering
|
|
||||||
* is case-sensitive matching the filter expression anywhere inside
|
|
||||||
* the tag.
|
|
||||||
*/
|
|
||||||
'filter' => env('L5_SWAGGER_UI_FILTERS', true), // true | false
|
|
||||||
],
|
|
||||||
|
|
||||||
'authorization' => [
|
|
||||||
/*
|
|
||||||
* If set to true, it persists authorization data, and it would not be lost on browser close/refresh
|
|
||||||
*/
|
|
||||||
'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false),
|
|
||||||
|
|
||||||
'oauth2' => [
|
|
||||||
/*
|
|
||||||
* If set to true, adds PKCE to AuthorizationCodeGrant flow
|
|
||||||
*/
|
|
||||||
'use_pkce_with_authorization_code_grant' => false,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
/*
|
|
||||||
* Constants which can be used in annotations
|
|
||||||
*/
|
|
||||||
'constants' => [
|
|
||||||
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Dedoc\Scramble\Http\Middleware\RestrictedDocsAccess;
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
* Which routes to document. String or array form; use Scramble::routes() for custom selection.
|
||||||
|
*
|
||||||
|
* 'api_path' => [
|
||||||
|
* 'include' => 'api',
|
||||||
|
* 'exclude' => ['api/internal'],
|
||||||
|
* ],
|
||||||
|
*
|
||||||
|
* Without *, patterns match path segments (api matches api and api/users, not apiary).
|
||||||
|
* With *, Str::is is used (e.g. api/v*).
|
||||||
|
*
|
||||||
|
* One static include → default server is /{include} and paths are stripped (/users).
|
||||||
|
* Multiple includes or wildcards → server defaults to / and paths stay full (/api/users).
|
||||||
|
* Override with `servers`, or use Scramble::registerApi() for separate bases.
|
||||||
|
*/
|
||||||
|
'api_path' => 'api',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Your API domain. By default, app domain is used. This is also a part of the default API routes
|
||||||
|
* matcher, so when implementing your own, make sure you use this config if needed.
|
||||||
|
*/
|
||||||
|
'api_domain' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The path where your OpenAPI specification will be exported.
|
||||||
|
*/
|
||||||
|
'export_path' => 'api.json',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cache configuration for the generated OpenAPI document.
|
||||||
|
*
|
||||||
|
* Use `scramble:cache` to warm the cache and `scramble:clear` to invalidate it.
|
||||||
|
*/
|
||||||
|
'cache' => [
|
||||||
|
'key' => 'scramble.openapi',
|
||||||
|
'store' => 'file',
|
||||||
|
],
|
||||||
|
|
||||||
|
'info' => [
|
||||||
|
/*
|
||||||
|
* API version.
|
||||||
|
*/
|
||||||
|
'version' => env('API_VERSION', '0.0.1'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Description rendered on the home page of the API documentation (`/docs/api`).
|
||||||
|
*/
|
||||||
|
'description' => '',
|
||||||
|
],
|
||||||
|
|
||||||
|
'ui' => [
|
||||||
|
'title' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'renderer' => 'elements',
|
||||||
|
|
||||||
|
'renderers' => [
|
||||||
|
/*
|
||||||
|
* Stoplight Elements config options: https://docs.stoplight.io/docs/elements/b074dc47b2826-elements-configuration-options
|
||||||
|
*/
|
||||||
|
'elements' => [
|
||||||
|
'view' => 'scramble::docs',
|
||||||
|
'theme' => 'light',
|
||||||
|
'hideTryIt' => false,
|
||||||
|
'hideSchemas' => false,
|
||||||
|
'logo' => '',
|
||||||
|
'tryItCredentialsPolicy' => 'include',
|
||||||
|
'layout' => 'responsive',
|
||||||
|
'router' => 'hash',
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* Scalar API reference config options: https://scalar.com/products/api-references/configuration
|
||||||
|
*/
|
||||||
|
'scalar' => [
|
||||||
|
'view' => 'scramble::scalar',
|
||||||
|
'cdn' => 'https://cdn.jsdelivr.net/npm/@scalar/api-reference',
|
||||||
|
'theme' => 'laravel',
|
||||||
|
'proxyUrl' => 'https://proxy.scalar.com',
|
||||||
|
'darkMode' => false,
|
||||||
|
'showDeveloperTools' => 'never',
|
||||||
|
'agent' => ['disabled' => true],
|
||||||
|
'credentials' => 'include',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The list of servers of the API. By default, when `null`, server URL will be created from
|
||||||
|
* `scramble.api_path` and `scramble.api_domain` config variables. When providing an array, you
|
||||||
|
* will need to specify the local server URL manually (if needed).
|
||||||
|
*
|
||||||
|
* Example of non-default config (final URLs are generated using Laravel `url` helper):
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
* 'servers' => [
|
||||||
|
* 'Live' => 'api',
|
||||||
|
* 'Prod' => 'https://scramble.dedoc.co/api',
|
||||||
|
* ],
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
'servers' => null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines how Scramble stores the descriptions of enum cases.
|
||||||
|
* Available options:
|
||||||
|
* - 'description' – Case descriptions are stored as the enum schema's description using table formatting.
|
||||||
|
* - 'extension' – Case descriptions are stored in the `x-enumDescriptions` enum schema extension.
|
||||||
|
*
|
||||||
|
* @see https://redocly.com/docs-legacy/api-reference-docs/specification-extensions/x-enum-descriptions
|
||||||
|
* - false - Case descriptions are ignored.
|
||||||
|
*/
|
||||||
|
'enum_cases_description_strategy' => 'description',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines how Scramble stores the names of enum cases.
|
||||||
|
* Available options:
|
||||||
|
* - 'names' – Case names are stored in the `x-enumNames` enum schema extension.
|
||||||
|
* - 'varnames' - Case names are stored in the `x-enum-varnames` enum schema extension.
|
||||||
|
* - false - Case names are not stored.
|
||||||
|
*/
|
||||||
|
'enum_cases_names_strategy' => false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When Scramble encounters deep objects in query parameters, it flattens the parameters so the generated
|
||||||
|
* OpenAPI document correctly describes the API. Flattening deep query parameters is relevant until
|
||||||
|
* OpenAPI 3.2 is released and query string structure can be described properly.
|
||||||
|
*
|
||||||
|
* For example, this nested validation rule describes the object with `bar` property:
|
||||||
|
* `['foo.bar' => ['required', 'int']]`.
|
||||||
|
*
|
||||||
|
* When `flatten_deep_query_parameters` is `true`, Scramble will document the parameter like so:
|
||||||
|
* `{"name":"foo[bar]", "schema":{"type":"int"}, "required":true}`.
|
||||||
|
*
|
||||||
|
* When `flatten_deep_query_parameters` is `false`, Scramble will document the parameter like so:
|
||||||
|
* `{"name":"foo", "schema": {"type":"object", "properties":{"bar":{"type": "int"}}, "required": ["bar"]}, "required":true}`.
|
||||||
|
*/
|
||||||
|
'flatten_deep_query_parameters' => true,
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'web',
|
||||||
|
RestrictedDocsAccess::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'extensions' => [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Automatically document API security (OpenAPI `security` / `securitySchemes`) based on route
|
||||||
|
* middleware.
|
||||||
|
*
|
||||||
|
* Disabled by default. Uncomment the line below to enable `MiddlewareAuthSecurityStrategy`.
|
||||||
|
* When at least one documented route uses middleware matching the configured patterns (by default
|
||||||
|
* `auth` and `auth:*`), bearer auth is applied globally. Routes without matching middleware are
|
||||||
|
* marked as public (`security: []`).
|
||||||
|
*
|
||||||
|
* Set to `null` explicitly to disable. If you already configure security manually via
|
||||||
|
* `afterOpenApiGenerated` / `extendOpenApi`, keep this disabled to avoid duplicate schemes.
|
||||||
|
*
|
||||||
|
* Customize with a class-string or [class, options]:
|
||||||
|
*
|
||||||
|
* 'security_strategy' => [
|
||||||
|
* \Dedoc\Scramble\SecurityDocumentation\MiddlewareAuthSecurityStrategy::class,
|
||||||
|
* [
|
||||||
|
* 'middleware' => ['auth', 'auth:*'],
|
||||||
|
* 'scheme' => \Dedoc\Scramble\Support\Generator\SecurityScheme::http('bearer'),
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
*/
|
||||||
|
// 'security_strategy' => \Dedoc\Scramble\SecurityDocumentation\MiddlewareAuthSecurityStrategy::class,
|
||||||
|
'security_strategy' => [
|
||||||
|
\Dedoc\Scramble\SecurityDocumentation\MiddlewareAuthSecurityStrategy::class,
|
||||||
|
[
|
||||||
|
'middleware' => ['auth', 'auth:*'],
|
||||||
|
'scheme' => \Dedoc\Scramble\Support\Generator\SecurityScheme::http('bearer'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ticket_types', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('code', 50)->unique();
|
||||||
|
$table->string('name', 100);
|
||||||
|
$table->boolean('need_approval')->default(false);
|
||||||
|
$table->integer('sla_minutes')->nullable();
|
||||||
|
|
||||||
|
// Requirement Ticket
|
||||||
|
$table->boolean('require_photo')->default(false);
|
||||||
|
$table->boolean('require_material')->default(false);
|
||||||
|
$table->boolean('need_customer')->default(false);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ticket_types');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ticket_logs', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_id');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
|
||||||
|
$table->string('activity');
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
|
||||||
|
$table->decimal('latitude', 10, 8)->nullable();
|
||||||
|
$table->decimal('longitude', 11, 8)->nullable();
|
||||||
|
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
|
||||||
|
$table->index('ticket_id');
|
||||||
|
$table->index('user_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ticket_logs');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tickets', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
$table->string('ticket_no', 50)->unique();
|
||||||
|
$table->unsignedBigInteger('ticket_type_id');
|
||||||
|
$table->unsignedBigInteger('tenant_id')->default(1);
|
||||||
|
$table->unsignedBigInteger('customer_id')->nullable();
|
||||||
|
$table->string('title');
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->enum('priority', [
|
||||||
|
'low',
|
||||||
|
'medium',
|
||||||
|
'high',
|
||||||
|
'critical'
|
||||||
|
])->default('medium');
|
||||||
|
|
||||||
|
$table->enum('status', [
|
||||||
|
'draft',
|
||||||
|
'open',
|
||||||
|
'approved',
|
||||||
|
'assigned',
|
||||||
|
'progress',
|
||||||
|
'pending',
|
||||||
|
'resolved',
|
||||||
|
'closed',
|
||||||
|
'cancelled',
|
||||||
|
'rejected'
|
||||||
|
])->default('draft');
|
||||||
|
|
||||||
|
$table->integer('sla_minutes')->nullable();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('created_by');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('approved_by')->nullable();
|
||||||
|
$table->timestamp('approved_at')->nullable();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('rejected_by')->nullable();
|
||||||
|
$table->timestamp('rejected_at')->nullable();
|
||||||
|
$table->text('rejection_reason')->nullable();
|
||||||
|
|
||||||
|
$table->timestamp('assigned_at')->nullable();
|
||||||
|
$table->timestamp('resolved_at')->nullable();
|
||||||
|
$table->timestamp('closed_at')->nullable();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index('ticket_no');
|
||||||
|
$table->index('ticket_type_id');
|
||||||
|
$table->index('tenant_id');
|
||||||
|
$table->index('customer_id');
|
||||||
|
$table->index('status');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tickets');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ticket_approvals', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_id');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('approver_id');
|
||||||
|
|
||||||
|
$table->enum('status', [
|
||||||
|
'pending',
|
||||||
|
'approved',
|
||||||
|
'rejected'
|
||||||
|
])->default('pending');
|
||||||
|
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
|
||||||
|
$table->timestamp('approved_at')->nullable();
|
||||||
|
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
|
||||||
|
$table->index('ticket_id');
|
||||||
|
$table->index('approver_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ticket_approvals');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ticket_assignments', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_id');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
|
||||||
|
$table->boolean('is_leader')->default(false);
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('assigned_by');
|
||||||
|
|
||||||
|
$table->timestamp('assigned_at')->nullable();
|
||||||
|
|
||||||
|
$table->enum('status', [
|
||||||
|
'assigned',
|
||||||
|
'accepted',
|
||||||
|
'rejected',
|
||||||
|
'completed'
|
||||||
|
])->default('assigned');
|
||||||
|
|
||||||
|
$table->index('ticket_id');
|
||||||
|
$table->index('user_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ticket_assignments');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ticket_files', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_id');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_log_id')->nullable();
|
||||||
|
|
||||||
|
$table->string('file_name');
|
||||||
|
$table->string('file_path', 500);
|
||||||
|
|
||||||
|
$table->bigInteger('file_size')->nullable();
|
||||||
|
|
||||||
|
$table->string('file_type', 100)->nullable();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('uploaded_by');
|
||||||
|
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
|
||||||
|
$table->index('ticket_id');
|
||||||
|
$table->index('ticket_log_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ticket_files');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ticket_materials', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_id');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_log_id')->nullable();
|
||||||
|
|
||||||
|
$table->string('material_name');
|
||||||
|
|
||||||
|
$table->decimal('qty', 12, 2);
|
||||||
|
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('used_by');
|
||||||
|
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
|
||||||
|
$table->index('ticket_id');
|
||||||
|
$table->index('ticket_log_id');
|
||||||
|
$table->index('used_by');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ticket_materials');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -14,6 +14,9 @@ class DatabaseSeeder extends Seeder
|
|||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
// User::factory(10)->create();
|
||||||
|
$this->call([
|
||||||
|
TicketTypeSeeder::class,
|
||||||
|
]);
|
||||||
|
|
||||||
User::factory()->create([
|
User::factory()->create([
|
||||||
'name' => 'Test User',
|
'name' => 'Test User',
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use App\Models\TicketType;
|
||||||
|
|
||||||
|
class TicketTypeSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$types = [
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'GGN',
|
||||||
|
'name' => 'Gangguan Internet',
|
||||||
|
'need_approval' => false,
|
||||||
|
'sla_minutes' => 240,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => false,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'INS',
|
||||||
|
'name' => 'Instalasi Baru',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 1440,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => true,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'ONU',
|
||||||
|
'name' => 'Ganti ONU',
|
||||||
|
'need_approval' => false,
|
||||||
|
'sla_minutes' => 240,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => true,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'CBL',
|
||||||
|
'name' => 'Tarik Kabel',
|
||||||
|
'need_approval' => false,
|
||||||
|
'sla_minutes' => 480,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => true,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'SVY',
|
||||||
|
'name' => 'Survey Lokasi',
|
||||||
|
'need_approval' => false,
|
||||||
|
'sla_minutes' => 480,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => false,
|
||||||
|
'need_customer' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'MNT',
|
||||||
|
'name' => 'Maintenance',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 1440,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => true,
|
||||||
|
'need_customer' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'ADM',
|
||||||
|
'name' => 'Administrasi',
|
||||||
|
'need_approval' => false,
|
||||||
|
'sla_minutes' => null,
|
||||||
|
'require_photo' => false,
|
||||||
|
'require_material' => false,
|
||||||
|
'need_customer' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'REL',
|
||||||
|
'name' => 'Relokasi Pelanggan',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 1440,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => true,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'UPG',
|
||||||
|
'name' => 'Upgrade Paket',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 240,
|
||||||
|
'require_photo' => false,
|
||||||
|
'require_material' => false,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'DWG',
|
||||||
|
'name' => 'Downgrade Paket',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 240,
|
||||||
|
'require_photo' => false,
|
||||||
|
'require_material' => false,
|
||||||
|
'need_customer' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'BTS',
|
||||||
|
'name' => 'Pekerjaan BTS/POP',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 2880,
|
||||||
|
'require_photo' => true,
|
||||||
|
'require_material' => true,
|
||||||
|
'need_customer' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'code' => 'NOC',
|
||||||
|
'name' => 'Pekerjaan NOC',
|
||||||
|
'need_approval' => true,
|
||||||
|
'sla_minutes' => 1440,
|
||||||
|
'require_photo' => false,
|
||||||
|
'require_material' => false,
|
||||||
|
'need_customer' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($types as $type) {
|
||||||
|
|
||||||
|
TicketType::updateOrCreate(
|
||||||
|
['code' => $type['code']],
|
||||||
|
$type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-theme="{{ $config->renderer()->get('theme', 'light') }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="color-scheme" content="{{ $config->renderer()->get('theme', 'light') }}">
|
||||||
|
<title>{{ $config->get('ui.title') ?? config('app.name') . ' - API Docs' }}</title>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/@stoplight/elements@8.4.2/web-components.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements@8.4.2/styles.min.css">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const originalFetch = window.fetch;
|
||||||
|
|
||||||
|
// intercept TryIt requests and add the XSRF-TOKEN header,
|
||||||
|
// which is necessary for Sanctum cookie-based authentication to work correctly
|
||||||
|
window.fetch = (url, options) => {
|
||||||
|
const CSRF_TOKEN_COOKIE_KEY = "XSRF-TOKEN";
|
||||||
|
const CSRF_TOKEN_HEADER_KEY = "X-XSRF-TOKEN";
|
||||||
|
const getCookieValue = (key) => {
|
||||||
|
const cookie = document.cookie.split(';').find((cookie) => cookie.trim().startsWith(key));
|
||||||
|
return cookie?.split("=")[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFetchHeaders = (
|
||||||
|
headers,
|
||||||
|
headerKey,
|
||||||
|
headerValue,
|
||||||
|
) => {
|
||||||
|
if (headers instanceof Headers) {
|
||||||
|
headers.set(headerKey, headerValue);
|
||||||
|
} else if (Array.isArray(headers)) {
|
||||||
|
headers.push([headerKey, headerValue]);
|
||||||
|
} else if (headers) {
|
||||||
|
headers[headerKey] = headerValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const csrfToken = getCookieValue(CSRF_TOKEN_COOKIE_KEY);
|
||||||
|
if (csrfToken) {
|
||||||
|
const { headers = new Headers() } = options || {};
|
||||||
|
updateFetchHeaders(headers, CSRF_TOKEN_HEADER_KEY, decodeURIComponent(csrfToken));
|
||||||
|
return originalFetch(url, {
|
||||||
|
...options,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalFetch(url, options);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html, body { margin:0; height:100%; }
|
||||||
|
body { background-color: var(--color-canvas); }
|
||||||
|
/* issues about the dark theme of stoplight/mosaic-code-viewer using web component:
|
||||||
|
* https://github.com/stoplightio/elements/issues/2188#issuecomment-1485461965
|
||||||
|
*/
|
||||||
|
[data-theme="dark"] .token.property {
|
||||||
|
color: rgb(128, 203, 196) !important;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .token.operator {
|
||||||
|
color: rgb(255, 123, 114) !important;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .token.number {
|
||||||
|
color: rgb(247, 140, 108) !important;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .token.string {
|
||||||
|
color: rgb(165, 214, 255) !important;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .token.boolean {
|
||||||
|
color: rgb(121, 192, 255) !important;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .token.punctuation {
|
||||||
|
color: #dbdbdb !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="height: 100vh; overflow-y: hidden">
|
||||||
|
<elements-api
|
||||||
|
id="docs"
|
||||||
|
@foreach($config->renderer()->all(except: ['theme']) as $key => $value)
|
||||||
|
@continue(! $value)
|
||||||
|
{{ $key }}="{{ $value === true ? 'true' : ($value === false ? 'false' : $value) }}"
|
||||||
|
@endforeach
|
||||||
|
/>
|
||||||
|
<script>
|
||||||
|
(async () => {
|
||||||
|
const docs = document.getElementById('docs');
|
||||||
|
docs.apiDescriptionDocument = @json($spec);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@if($config->renderer()->get('theme', 'light') === 'system')
|
||||||
|
<script>
|
||||||
|
var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
|
||||||
|
function updateTheme(e) {
|
||||||
|
if (e.matches) {
|
||||||
|
window.document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
window.document.getElementsByName('color-scheme')[0].setAttribute('content', 'dark');
|
||||||
|
} else {
|
||||||
|
window.document.documentElement.setAttribute('data-theme', 'light');
|
||||||
|
window.document.getElementsByName('color-scheme')[0].setAttribute('content', 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaQuery.addEventListener('change', updateTheme);
|
||||||
|
updateTheme(mediaQuery);
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>{{ $config->get('ui.title') ?? config('app.name') . ' - API Docs' }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="{{ $config->renderer()->get('cdn', 'https://cdn.jsdelivr.net/npm/@scalar/api-reference') }}"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const CSRF_TOKEN_COOKIE_KEY = "XSRF-TOKEN";
|
||||||
|
const CSRF_TOKEN_HEADER_KEY = "X-XSRF-TOKEN";
|
||||||
|
const getCookieValue = (key) => {
|
||||||
|
const cookie = document.cookie.split(';').find((cookie) => cookie.trim().startsWith(key));
|
||||||
|
return cookie?.split("=")[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
Scalar.createApiReference('#app', {
|
||||||
|
content: @json($spec),
|
||||||
|
...@json($config->renderer()->all(except: ['cdn', 'credentials'])),
|
||||||
|
onBeforeRequest: ({ requestBuilder }) => {
|
||||||
|
requestBuilder.headers.set(CSRF_TOKEN_HEADER_KEY, decodeURIComponent(getCookieValue(CSRF_TOKEN_COOKIE_KEY)))
|
||||||
|
},
|
||||||
|
customFetch: (input, init) => {
|
||||||
|
return window.fetch(input, { ...init, credentials: @json($config->renderer()->get('credentials', 'include')) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+48
-4
@@ -3,13 +3,57 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
|
use App\Http\Controllers\Ticket\TicketController;
|
||||||
|
use App\Http\Controllers\Ticket\TicketTypeController;
|
||||||
|
use App\Http\Controllers\Ticket\TicketWorkflowController;
|
||||||
|
|
||||||
Route::post('/login', [AuthController::class, 'login']);
|
Route::post('/login', [AuthController::class, 'login']);
|
||||||
|
|
||||||
|
|
||||||
|
// Route::prefix('tickets')->group(function () {
|
||||||
|
// Route::get('/', [TicketController::class, 'index']);
|
||||||
|
// Route::post('/', [TicketController::class, 'store']);
|
||||||
|
// });
|
||||||
|
Route::get('/generate-token', function () {
|
||||||
|
|
||||||
|
$user = \App\Models\User::find(1);
|
||||||
|
|
||||||
|
return $user->createToken(
|
||||||
|
'master-token'
|
||||||
|
)->plainTextToken;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
Route::middleware('auth:sanctum')->group(function () {
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
|
||||||
Route::get('/profile', function (Request $request) {
|
Route::apiResource('tickets', TicketController::class);
|
||||||
return response()->json($request->user());
|
|
||||||
});
|
Route::apiResource('ticket-types', TicketTypeController::class);
|
||||||
|
|
||||||
|
Route::prefix('tickets/{ticket}')
|
||||||
|
->controller(TicketWorkflowController::class)
|
||||||
|
->group(function () {
|
||||||
|
|
||||||
|
Route::get('/workflow', 'history');
|
||||||
|
|
||||||
|
Route::post('/assign', 'assign');
|
||||||
|
Route::post('/reassign', 'reassign');
|
||||||
|
|
||||||
|
Route::post('/approve', 'approve');
|
||||||
|
Route::post('/reject', 'reject');
|
||||||
|
|
||||||
|
Route::post('/start', 'start');
|
||||||
|
|
||||||
|
Route::post('/pending', 'pending');
|
||||||
|
Route::post('/resume', 'resume');
|
||||||
|
|
||||||
|
Route::post('/resolve', 'resolve');
|
||||||
|
|
||||||
|
Route::post('/close', 'close');
|
||||||
|
|
||||||
|
Route::post('/cancel', 'cancel');
|
||||||
|
|
||||||
|
Route::post('/escalate', 'escalate');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "admin@test.com"
|
"example": "admin@test.co.id"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 50600) {
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('HTTP/1.1 500 Internal Server Error');
|
||||||
|
}
|
||||||
|
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||||
|
if (!ini_get('display_errors')) {
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
fwrite(STDERR, $err);
|
||||||
|
} elseif (!headers_sent()) {
|
||||||
|
echo $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException($err);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|
||||||
|
return ComposerAutoloaderInitd8f67b824a1bf31c8999ead558d9b485::getLoader();
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../nesbot/carbon/bin/carbon)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/carbon
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../symfony/error-handler/Resources/bin/patch-type-declarations)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations';
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/patch-type-declarations
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/php-parse
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = 'phpvfscomposer://'.$this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
|
||||||
|
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/phpunit
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../laravel/pint/builds/pint)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/laravel/pint/builds/pint');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/laravel/pint/builds/pint';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/pint
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../psy/psysh/bin/psysh)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/psy/psysh/bin/psysh';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/psysh
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Support bash to support `source` with fallback on $0 if this does not run with bash
|
||||||
|
# https://stackoverflow.com/a/35006505/6512
|
||||||
|
selfArg="$BASH_SOURCE"
|
||||||
|
if [ -z "$selfArg" ]; then
|
||||||
|
selfArg="$0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
self=$(realpath "$selfArg" 2> /dev/null)
|
||||||
|
if [ -z "$self" ]; then
|
||||||
|
self="$selfArg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dir=$(cd "${self%[/\\]*}" > /dev/null; cd ../laravel/sail/bin && pwd)
|
||||||
|
|
||||||
|
if [ -d /proc/cygdrive ]; then
|
||||||
|
case $(which php) in
|
||||||
|
$(readlink -n /proc/cygdrive)/*)
|
||||||
|
# We are in Cygwin using Windows php, so the path must be translated
|
||||||
|
dir=$(cygpath -m "$dir");
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
|
||||||
|
|
||||||
|
# If bash is sourcing this file, we have to source the target as well
|
||||||
|
bashSource="$BASH_SOURCE"
|
||||||
|
if [ -n "$bashSource" ]; then
|
||||||
|
if [ "$bashSource" != "$0" ]; then
|
||||||
|
source "${dir}/sail" "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "${dir}/sail" "$@"
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/../laravel/sail/bin/sail
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
bash "%BIN_TARGET%" %*
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/var-dump-server
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy PHP file generated by Composer
|
||||||
|
*
|
||||||
|
* This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint)
|
||||||
|
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||||
|
*
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||||
|
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BinProxyWrapper
|
||||||
|
{
|
||||||
|
private $handle;
|
||||||
|
private $position;
|
||||||
|
private $realpath;
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path)
|
||||||
|
{
|
||||||
|
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||||
|
$opened_path = substr($path, 17);
|
||||||
|
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||||
|
$opened_path = $this->realpath;
|
||||||
|
$this->handle = fopen($this->realpath, $mode);
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
return (bool) $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read($count)
|
||||||
|
{
|
||||||
|
$data = fread($this->handle, $count);
|
||||||
|
|
||||||
|
if ($this->position === 0) {
|
||||||
|
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position += strlen($data);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_cast($castAs)
|
||||||
|
{
|
||||||
|
return $this->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close()
|
||||||
|
{
|
||||||
|
fclose($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_lock($operation)
|
||||||
|
{
|
||||||
|
return $operation ? flock($this->handle, $operation) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek($offset, $whence)
|
||||||
|
{
|
||||||
|
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||||
|
$this->position = ftell($this->handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof()
|
||||||
|
{
|
||||||
|
return feof($this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option($option, $arg1, $arg2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags)
|
||||||
|
{
|
||||||
|
$path = substr($path, 17);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return stat($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
|
) {
|
||||||
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
setlocal DISABLEDELAYEDEXPANSION
|
||||||
|
SET BIN_TARGET=%~dp0/yaml-lint
|
||||||
|
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||||
|
php "%BIN_TARGET%" %*
|
||||||
Vendored
+634
@@ -0,0 +1,634 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.14.8](https://github.com/brick/math/releases/tag/0.14.8) - 2026-02-10
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Method `BigInteger::testBit()` is deprecated, use `isBitSet()` instead
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigInteger::isBitSet()` (replaces `testBit()`)
|
||||||
|
- New method: `BigNumber::toString()` (alias of magic method `__toString()`)
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Performance optimization of `BigRational` comparison methods
|
||||||
|
- More exceptions have been documented with `@throws` annotations
|
||||||
|
|
||||||
|
## [0.14.7](https://github.com/brick/math/releases/tag/0.14.7) - 2026-02-07
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `clamp()` is now available on the base `BigNumber` class
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Improved `@throws` exception documentation
|
||||||
|
|
||||||
|
## [0.14.6](https://github.com/brick/math/releases/tag/0.14.6) - 2026-02-05
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Not passing a `$scale` to `BigDecimal::dividedBy()` is deprecated; **`$scale` will be required in 0.15**
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `BigRational::toFloat()` never returns `NAN` anymore
|
||||||
|
|
||||||
|
## [0.14.5](https://github.com/brick/math/releases/tag/0.14.5) - 2026-02-03
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Not passing a rounding mode to `BigInteger::sqrt()` and `BigDecimal::sqrt()` triggers a deprecation notice: **the default rounding mode will change from `Down` to `Unnecessary` in 0.15**
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::sqrt()` and `BigDecimal::sqrt()` now support rounding
|
||||||
|
- `abs()` and `negated()` methods are now available on the base `BigNumber` class
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Alphabet is now checked for duplicate characters in `BigInteger::(from|to)ArbitraryBase()`
|
||||||
|
- `BigNumber::ofNullable()` is now marked as `@pure`
|
||||||
|
|
||||||
|
## [0.14.4](https://github.com/brick/math/releases/tag/0.14.4) - 2026-02-02
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Passing a negative modulus to `BigInteger::mod()` is deprecated to align with Euclidean modulo semantics; it will throw `NegativeNumberException` in 0.15
|
||||||
|
- Method `BigDecimal::stripTrailingZeros()` is deprecated, use `strippedOfTrailingZeros()` instead
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::modPow()` now accepts negative bases
|
||||||
|
- New method: `BigDecimal::strippedOfTrailingZeros()` (replaces `stripTrailingZeros()`)
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `clamp()` methods are now marked as `@pure`
|
||||||
|
|
||||||
|
## [0.14.3](https://github.com/brick/math/releases/tag/0.14.3) - 2026-02-01
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigInteger::lcm()`
|
||||||
|
- New method: `BigInteger::lcmAll()`
|
||||||
|
- New method: `BigRational::toRepeatingDecimalString()`
|
||||||
|
|
||||||
|
🐛 **Bug fixes**
|
||||||
|
|
||||||
|
- `BigInteger::gcdAll()` / `gcdMultiple()` could return a negative result when used with a single negative number
|
||||||
|
|
||||||
|
## [0.14.2](https://github.com/brick/math/releases/tag/0.14.2) - 2026-01-30
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- **Passing `float` values to `of()` or arithmetic methods is deprecated** and will be removed in 0.15; cast to string explicitly to preserve the previous behaviour (#105)
|
||||||
|
- **Accessing `RoundingMode` enum cases through upper snake case (e.g. `HALF_UP`) is deprecated**, use the pascal case version (e.g. `HalfUp`) instead
|
||||||
|
- Method `BigInteger::gcdMultiple()` is deprecated, use `gcdAll()` instead
|
||||||
|
- Method `BigDecimal::exactlyDividedBy()` is deprecated, use `dividedByExact()` instead
|
||||||
|
- Method `BigDecimal::getIntegralPart()` is deprecated (will be removed in 0.15, and re-introduced as returning `BigInteger` in 0.16)
|
||||||
|
- Method `BigDecimal::getFractionalPart()` is deprecated (will be removed in 0.15, and re-introduced as returning `BigDecimal` with a different meaning in 0.16)
|
||||||
|
- Method `BigRational::nd()` is deprecated, use `ofFraction()` instead
|
||||||
|
- Method `BigRational::quotient()` is deprecated, use `getIntegralPart()` instead
|
||||||
|
- Method `BigRational::remainder()` is deprecated, use `$number->getNumerator()->remainder($number->getDenominator())` instead
|
||||||
|
- Method `BigRational::quotientAndRemainder()` is deprecated, use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigInteger::gcdAll()` (replaces `gcdMultiple()`)
|
||||||
|
- New method: `BigRational::clamp()`
|
||||||
|
- New method: `BigRational::ofFraction()` (replaces `nd()`)
|
||||||
|
- New method: `BigRational::getIntegralPart()` (replaces `quotient()`)
|
||||||
|
- New method: `BigRational::getFractionalPart()`
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `BigInteger::modInverse()` now accepts `BigNumber|int|float|string` instead of just `BigInteger`
|
||||||
|
- `BigInteger::gcdMultiple()` now accepts `BigNumber|int|float|string` instead of just `BigInteger`
|
||||||
|
|
||||||
|
🐛 **Bug fixes**
|
||||||
|
|
||||||
|
- `BigInteger::clamp()` and `BigDecimal::clamp()` now throw an exception on inverted bounds, instead of returning an incorrect result
|
||||||
|
|
||||||
|
## [0.14.1](https://github.com/brick/math/releases/tag/0.14.1) - 2025-11-24
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigNumber::ofNullable()` (#94 by @mrkh995)
|
||||||
|
|
||||||
|
✨ **Compatibility fixes**
|
||||||
|
|
||||||
|
- Fixed warnings on PHP 8.5 (#101 and #102 by @julien-boudry)
|
||||||
|
|
||||||
|
## [0.14.0](https://github.com/brick/math/releases/tag/0.14.0) - 2025-08-29
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New methods: `BigInteger::clamp()` and `BigDecimal::clamp()` (#96 by @JesterIruka)
|
||||||
|
|
||||||
|
✨ **Improvements**
|
||||||
|
|
||||||
|
- All pure methods in `BigNumber` classes are now marked as `@pure` for better static analysis
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- Minimum PHP version is now 8.2
|
||||||
|
- `BigNumber` classes are now `readonly`
|
||||||
|
- `BigNumber` is now marked as sealed: it must not be extended outside of this package
|
||||||
|
- Exception classes are now `final`
|
||||||
|
|
||||||
|
## [0.13.1](https://github.com/brick/math/releases/tag/0.13.1) - 2025-03-29
|
||||||
|
|
||||||
|
✨ **Improvements**
|
||||||
|
|
||||||
|
- `__toString()` methods of `BigInteger` and `BigDecimal` are now type-hinted as returning `numeric-string` instead of `string` (#90 by @vudaltsov)
|
||||||
|
|
||||||
|
## [0.13.0](https://github.com/brick/math/releases/tag/0.13.0) - 2025-03-03
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- `BigDecimal::ofUnscaledValue()` no longer throws an exception if the scale is negative
|
||||||
|
- `MathException` now extends `RuntimeException` instead of `Exception`; this reverts the change introduced in version `0.11.0` (#82)
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigDecimal::ofUnscaledValue()` allows a negative scale (and converts the values to create a zero scale number)
|
||||||
|
|
||||||
|
## [0.12.3](https://github.com/brick/math/releases/tag/0.12.3) - 2025-02-28
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigDecimal::getPrecision()` Returns the number of significant digits in a decimal number
|
||||||
|
|
||||||
|
## [0.12.2](https://github.com/brick/math/releases/tag/0.12.2) - 2025-02-26
|
||||||
|
|
||||||
|
⚡️ **Performance improvements**
|
||||||
|
|
||||||
|
- Division in `NativeCalculator` is now faster for small divisors, thanks to [@Izumi-kun](https://github.com/Izumi-kun) in [#87](https://github.com/brick/math/pull/87).
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Add missing `RoundingNecessaryException` to the `@throws` annotation of `BigNumber::of()`
|
||||||
|
|
||||||
|
## [0.12.1](https://github.com/brick/math/releases/tag/0.12.1) - 2023-11-29
|
||||||
|
|
||||||
|
⚡️ **Performance improvements**
|
||||||
|
|
||||||
|
- `BigNumber::of()` is now faster, thanks to [@SebastienDug](https://github.com/SebastienDug) in [#77](https://github.com/brick/math/pull/77).
|
||||||
|
|
||||||
|
## [0.12.0](https://github.com/brick/math/releases/tag/0.12.0) - 2023-11-26
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- Minimum PHP version is now 8.1
|
||||||
|
- `RoundingMode` is now an `enum`; if you're type-hinting rounding modes, you need to type-hint against `RoundingMode` instead of `int` now
|
||||||
|
- `BigNumber` classes do not implement the `Serializable` interface anymore (they use the [new custom object serialization mechanism](https://wiki.php.net/rfc/custom_object_serialization))
|
||||||
|
- The following breaking changes only affect you if you're creating your own `BigNumber` subclasses:
|
||||||
|
- the return type of `BigNumber::of()` is now `static`
|
||||||
|
- `BigNumber` has a new abstract method `from()`
|
||||||
|
- all `public` and `protected` functions of `BigNumber` are now `final`
|
||||||
|
|
||||||
|
## [0.11.0](https://github.com/brick/math/releases/tag/0.11.0) - 2023-01-16
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- Minimum PHP version is now 8.0
|
||||||
|
- Methods accepting a union of types are now strongly typed<sup>*</sup>
|
||||||
|
- `MathException` now extends `Exception` instead of `RuntimeException`
|
||||||
|
|
||||||
|
<sup>* You may now run into type errors if you were passing `Stringable` objects to `of()` or any of the methods
|
||||||
|
internally calling `of()`, with `strict_types` enabled. You can fix this by casting `Stringable` objects to `string`
|
||||||
|
first.</sup>
|
||||||
|
|
||||||
|
## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic
|
||||||
|
|
||||||
|
## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers
|
||||||
|
|
||||||
|
## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- Minimum PHP version is now 7.4
|
||||||
|
|
||||||
|
## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
|
||||||
|
|
||||||
|
🚀 **Compatibility with PHP 8.1**
|
||||||
|
|
||||||
|
- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham
|
||||||
|
|
||||||
|
## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
|
||||||
|
|
||||||
|
🐛 **Bug fix**
|
||||||
|
|
||||||
|
- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
|
||||||
|
|
||||||
|
## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::not()` returns the bitwise `NOT` value
|
||||||
|
|
||||||
|
🐛 **Bug fixes**
|
||||||
|
|
||||||
|
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
|
||||||
|
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
|
||||||
|
|
||||||
|
## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal`
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead
|
||||||
|
- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead
|
||||||
|
|
||||||
|
## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19
|
||||||
|
|
||||||
|
🐛 **Bug fix**
|
||||||
|
|
||||||
|
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
|
||||||
|
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
|
||||||
|
|
||||||
|
## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18
|
||||||
|
|
||||||
|
🚑 **Critical fix**
|
||||||
|
|
||||||
|
- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle.
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::modInverse()` calculates a modular multiplicative inverse
|
||||||
|
- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string
|
||||||
|
- `BigInteger::toBytes()` converts a `BigInteger` to a byte string
|
||||||
|
- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length
|
||||||
|
- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds
|
||||||
|
|
||||||
|
💩 **Deprecations**
|
||||||
|
|
||||||
|
- `BigInteger::powerMod()` is now deprecated in favour of `modPow()`
|
||||||
|
|
||||||
|
## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15
|
||||||
|
|
||||||
|
🐛 **Fixes**
|
||||||
|
|
||||||
|
- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable`
|
||||||
|
|
||||||
|
⚡️ **Optimizations**
|
||||||
|
|
||||||
|
- additional optimization in `BigInteger::remainder()`
|
||||||
|
|
||||||
|
## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit
|
||||||
|
|
||||||
|
## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::isEven()` tests whether the number is even
|
||||||
|
- `BigInteger::isOdd()` tests whether the number is odd
|
||||||
|
- `BigInteger::testBit()` tests if a bit is set
|
||||||
|
- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number
|
||||||
|
|
||||||
|
## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03
|
||||||
|
|
||||||
|
🛠️ **Maintenance release**
|
||||||
|
|
||||||
|
Classes are now annotated for better static analysis with [psalm](https://psalm.dev/).
|
||||||
|
|
||||||
|
This is a maintenance release: no bug fixes, no new features, no breaking changes.
|
||||||
|
|
||||||
|
## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23
|
||||||
|
|
||||||
|
✨ **New feature**
|
||||||
|
|
||||||
|
`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto.
|
||||||
|
|
||||||
|
## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21
|
||||||
|
|
||||||
|
✨ **New feature**
|
||||||
|
|
||||||
|
`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different.
|
||||||
|
|
||||||
|
## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08
|
||||||
|
|
||||||
|
⚡️ **Performance improvements**
|
||||||
|
|
||||||
|
A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24.
|
||||||
|
|
||||||
|
## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25
|
||||||
|
|
||||||
|
🐛 **Bug fixes**
|
||||||
|
|
||||||
|
- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected)
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet
|
||||||
|
- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number
|
||||||
|
|
||||||
|
These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation.
|
||||||
|
|
||||||
|
💩 **Deprecations**
|
||||||
|
|
||||||
|
- `BigInteger::parse()` is now deprecated in favour of `fromBase()`
|
||||||
|
|
||||||
|
`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences:
|
||||||
|
|
||||||
|
- the `$base` parameter is required, it does not default to `10`
|
||||||
|
- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed
|
||||||
|
|
||||||
|
## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20
|
||||||
|
|
||||||
|
**Improvements**
|
||||||
|
|
||||||
|
- Safer conversion from `float` when using custom locales
|
||||||
|
- **Much faster** `NativeCalculator` implementation 🚀
|
||||||
|
|
||||||
|
You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before.
|
||||||
|
|
||||||
|
## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11
|
||||||
|
|
||||||
|
**New method**
|
||||||
|
|
||||||
|
`BigNumber::sum()` returns the sum of one or more numbers.
|
||||||
|
|
||||||
|
## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12
|
||||||
|
|
||||||
|
**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20).
|
||||||
|
|
||||||
|
Thanks @manowark 👍
|
||||||
|
|
||||||
|
## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07
|
||||||
|
|
||||||
|
**New method**
|
||||||
|
|
||||||
|
`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale.
|
||||||
|
|
||||||
|
## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06
|
||||||
|
|
||||||
|
**New method**
|
||||||
|
|
||||||
|
`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k).
|
||||||
|
|
||||||
|
**New exception**
|
||||||
|
|
||||||
|
`NegativeNumberException` is thrown when calling `sqrt()` on a negative number.
|
||||||
|
|
||||||
|
## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08
|
||||||
|
|
||||||
|
**Performance update**
|
||||||
|
|
||||||
|
- Further improvement of `toInt()` performance
|
||||||
|
- `NativeCalculator` can now perform some multiplications more efficiently
|
||||||
|
|
||||||
|
## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07
|
||||||
|
|
||||||
|
Performance optimization of `toInt()` methods.
|
||||||
|
|
||||||
|
## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13
|
||||||
|
|
||||||
|
**Breaking changes**
|
||||||
|
|
||||||
|
The following deprecated methods have been removed. Use the new method name instead:
|
||||||
|
|
||||||
|
| Method removed | Replacement method |
|
||||||
|
| --- | --- |
|
||||||
|
| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` |
|
||||||
|
| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**New features**
|
||||||
|
|
||||||
|
`BigInteger` has been augmented with 5 new methods for bitwise operations:
|
||||||
|
|
||||||
|
| New method | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `and()` | performs a bitwise `AND` operation on two numbers |
|
||||||
|
| `or()` | performs a bitwise `OR` operation on two numbers |
|
||||||
|
| `xor()` | performs a bitwise `XOR` operation on two numbers |
|
||||||
|
| `shiftedLeft()` | returns the number shifted left by a number of bits |
|
||||||
|
| `shiftedRight()` | returns the number shifted right by a number of bits |
|
||||||
|
|
||||||
|
Thanks to @DASPRiD 👍
|
||||||
|
|
||||||
|
## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20
|
||||||
|
|
||||||
|
**New method:** `BigDecimal::hasNonZeroFractionalPart()`
|
||||||
|
|
||||||
|
**Renamed/deprecated methods:**
|
||||||
|
|
||||||
|
- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated
|
||||||
|
- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated
|
||||||
|
|
||||||
|
## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21
|
||||||
|
|
||||||
|
**Performance update**
|
||||||
|
|
||||||
|
`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available.
|
||||||
|
|
||||||
|
## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01
|
||||||
|
|
||||||
|
This is a maintenance release, no code has been changed.
|
||||||
|
|
||||||
|
- When installed with `--no-dev`, the autoloader does not autoload tests anymore
|
||||||
|
- Tests and other files unnecessary for production are excluded from the dist package
|
||||||
|
|
||||||
|
This will help make installations more compact.
|
||||||
|
|
||||||
|
## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02
|
||||||
|
|
||||||
|
Methods renamed:
|
||||||
|
|
||||||
|
- `BigNumber:sign()` has been renamed to `getSign()`
|
||||||
|
- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()`
|
||||||
|
- `BigDecimal::scale()` has been renamed to `getScale()`
|
||||||
|
- `BigDecimal::integral()` has been renamed to `getIntegral()`
|
||||||
|
- `BigDecimal::fraction()` has been renamed to `getFraction()`
|
||||||
|
- `BigRational::numerator()` has been renamed to `getNumerator()`
|
||||||
|
- `BigRational::denominator()` has been renamed to `getDenominator()`
|
||||||
|
|
||||||
|
Classes renamed:
|
||||||
|
|
||||||
|
- `ArithmeticException` has been renamed to `MathException`
|
||||||
|
|
||||||
|
## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02
|
||||||
|
|
||||||
|
The base class for all exceptions is now `MathException`.
|
||||||
|
`ArithmeticException` has been deprecated, and will be removed in 0.7.0.
|
||||||
|
|
||||||
|
## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02
|
||||||
|
|
||||||
|
A number of methods have been renamed:
|
||||||
|
|
||||||
|
- `BigNumber:sign()` is deprecated; use `getSign()` instead
|
||||||
|
- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead
|
||||||
|
- `BigDecimal::scale()` is deprecated; use `getScale()` instead
|
||||||
|
- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead
|
||||||
|
- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead
|
||||||
|
- `BigRational::numerator()` is deprecated; use `getNumerator()` instead
|
||||||
|
- `BigRational::denominator()` is deprecated; use `getDenominator()` instead
|
||||||
|
|
||||||
|
The old methods will be removed in version 0.7.0.
|
||||||
|
|
||||||
|
## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25
|
||||||
|
|
||||||
|
- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5`
|
||||||
|
- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead
|
||||||
|
- Method `BigNumber::toInteger()` has been renamed to `toInt()`
|
||||||
|
|
||||||
|
## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17
|
||||||
|
|
||||||
|
`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php).
|
||||||
|
The JSON output is always a string.
|
||||||
|
|
||||||
|
## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31
|
||||||
|
|
||||||
|
This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||||
|
|
||||||
|
## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06
|
||||||
|
|
||||||
|
The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again.
|
||||||
|
|
||||||
|
## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05
|
||||||
|
|
||||||
|
**New method: `BigNumber::toScale()`**
|
||||||
|
|
||||||
|
This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary.
|
||||||
|
|
||||||
|
## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04
|
||||||
|
|
||||||
|
**New features**
|
||||||
|
- Common `BigNumber` interface for all classes, with the following methods:
|
||||||
|
- `sign()` and derived methods (`isZero()`, `isPositive()`, ...)
|
||||||
|
- `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types
|
||||||
|
- `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods
|
||||||
|
- `toInteger()` and `toFloat()` conversion methods to native types
|
||||||
|
- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type
|
||||||
|
- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits
|
||||||
|
- New methods: `BigRational::quotient()` and `remainder()`
|
||||||
|
- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException`
|
||||||
|
- Factory methods `zero()`, `one()` and `ten()` available in all classes
|
||||||
|
- Rounding mode reintroduced in `BigInteger::dividedBy()`
|
||||||
|
|
||||||
|
This release also comes with many performance improvements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Breaking changes**
|
||||||
|
- `BigInteger`:
|
||||||
|
- `getSign()` is renamed to `sign()`
|
||||||
|
- `toString()` is renamed to `toBase()`
|
||||||
|
- `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour
|
||||||
|
- `BigDecimal`:
|
||||||
|
- `getSign()` is renamed to `sign()`
|
||||||
|
- `getUnscaledValue()` is renamed to `unscaledValue()`
|
||||||
|
- `getScale()` is renamed to `scale()`
|
||||||
|
- `getIntegral()` is renamed to `integral()`
|
||||||
|
- `getFraction()` is renamed to `fraction()`
|
||||||
|
- `divideAndRemainder()` is renamed to `quotientAndRemainder()`
|
||||||
|
- `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode
|
||||||
|
- `toBigInteger()` does not accept a `$roundingMode` parameter anymore
|
||||||
|
- `toBigRational()` does not simplify the fraction anymore; explicitly add `->simplified()` to get the previous behaviour
|
||||||
|
- `BigRational`:
|
||||||
|
- `getSign()` is renamed to `sign()`
|
||||||
|
- `getNumerator()` is renamed to `numerator()`
|
||||||
|
- `getDenominator()` is renamed to `denominator()`
|
||||||
|
- `of()` is renamed to `nd()`, while `parse()` is renamed to `of()`
|
||||||
|
- Miscellaneous:
|
||||||
|
- `ArithmeticException` is moved to an `Exception\` sub-namespace
|
||||||
|
- `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException`
|
||||||
|
|
||||||
|
## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31
|
||||||
|
|
||||||
|
Backport of two bug fixes from the 0.5 branch:
|
||||||
|
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
|
||||||
|
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||||
|
|
||||||
|
## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16
|
||||||
|
|
||||||
|
New method: `BigDecimal::stripTrailingZeros()`
|
||||||
|
|
||||||
|
## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12
|
||||||
|
|
||||||
|
Introducing a `BigRational` class, to perform calculations on fractions of any size.
|
||||||
|
|
||||||
|
## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12
|
||||||
|
|
||||||
|
Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`.
|
||||||
|
|
||||||
|
`BigInteger::dividedBy()` now always returns the quotient of the division.
|
||||||
|
|
||||||
|
## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31
|
||||||
|
|
||||||
|
Backport of two bug fixes from the 0.5 branch:
|
||||||
|
|
||||||
|
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
|
||||||
|
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||||
|
|
||||||
|
## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11
|
||||||
|
|
||||||
|
New methods:
|
||||||
|
- `BigInteger::remainder()` returns the remainder of a division only
|
||||||
|
- `BigInteger::gcd()` returns the greatest common divisor of two numbers
|
||||||
|
|
||||||
|
## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07
|
||||||
|
|
||||||
|
Fix `toString()` not handling negative numbers.
|
||||||
|
|
||||||
|
## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07
|
||||||
|
|
||||||
|
`BigInteger` and `BigDecimal` now have a `getSign()` method that returns:
|
||||||
|
- `-1` if the number is negative
|
||||||
|
- `0` if the number is zero
|
||||||
|
- `1` if the number is positive
|
||||||
|
|
||||||
|
## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05
|
||||||
|
|
||||||
|
Minor performance improvements
|
||||||
|
|
||||||
|
## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04
|
||||||
|
|
||||||
|
The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`.
|
||||||
|
|
||||||
|
## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04
|
||||||
|
|
||||||
|
Stronger immutability guarantee for `BigInteger` and `BigDecimal`.
|
||||||
|
|
||||||
|
So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that.
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02
|
||||||
|
|
||||||
|
Added `BigDecimal::divideAndRemainder()`
|
||||||
|
|
||||||
|
## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22
|
||||||
|
|
||||||
|
- `min()` and `max()` do not accept an `array` anymore, but a variable number of parameters
|
||||||
|
- **minimum PHP version is now 5.6**
|
||||||
|
- continuous integration with PHP 7
|
||||||
|
|
||||||
|
## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01
|
||||||
|
|
||||||
|
- Added `BigInteger::power()`
|
||||||
|
- Added HHVM support
|
||||||
|
|
||||||
|
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
|
||||||
|
|
||||||
|
First beta release.
|
||||||
Vendored
+20
@@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013-present Benjamin Morel
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
Vendored
+39
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "brick/math",
|
||||||
|
"description": "Arbitrary-precision arithmetic library",
|
||||||
|
"type": "library",
|
||||||
|
"keywords": [
|
||||||
|
"Brick",
|
||||||
|
"Math",
|
||||||
|
"Mathematics",
|
||||||
|
"Arbitrary-precision",
|
||||||
|
"Arithmetic",
|
||||||
|
"BigInteger",
|
||||||
|
"BigDecimal",
|
||||||
|
"BigRational",
|
||||||
|
"BigNumber",
|
||||||
|
"Bignum",
|
||||||
|
"Decimal",
|
||||||
|
"Rational",
|
||||||
|
"Integer"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^11.5",
|
||||||
|
"php-coveralls/php-coveralls": "^2.2",
|
||||||
|
"phpstan/phpstan": "2.1.22"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Brick\\Math\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Brick\\Math\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+975
@@ -0,0 +1,975 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math;
|
||||||
|
|
||||||
|
use Brick\Math\Exception\DivisionByZeroException;
|
||||||
|
use Brick\Math\Exception\MathException;
|
||||||
|
use Brick\Math\Exception\NegativeNumberException;
|
||||||
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
use Brick\Math\Internal\Calculator;
|
||||||
|
use Brick\Math\Internal\CalculatorRegistry;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LogicException;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
use function func_num_args;
|
||||||
|
use function in_array;
|
||||||
|
use function intdiv;
|
||||||
|
use function max;
|
||||||
|
use function rtrim;
|
||||||
|
use function sprintf;
|
||||||
|
use function str_pad;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
use function trigger_error;
|
||||||
|
|
||||||
|
use const E_USER_DEPRECATED;
|
||||||
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An arbitrarily large decimal number.
|
||||||
|
*
|
||||||
|
* This class is immutable.
|
||||||
|
*
|
||||||
|
* The scale of the number is the number of digits after the decimal point. It is always positive or zero.
|
||||||
|
*/
|
||||||
|
final readonly class BigDecimal extends BigNumber
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The unscaled value of this decimal number.
|
||||||
|
*
|
||||||
|
* This is a string of digits with an optional leading minus sign.
|
||||||
|
* No leading zero must be present.
|
||||||
|
* No leading minus sign must be present if the value is 0.
|
||||||
|
*/
|
||||||
|
private string $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scale (number of digits after the decimal point) of this decimal number.
|
||||||
|
*
|
||||||
|
* This must be zero or more.
|
||||||
|
*/
|
||||||
|
private int $scale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protected constructor. Use a factory method to obtain an instance.
|
||||||
|
*
|
||||||
|
* @param string $value The unscaled value, validated.
|
||||||
|
* @param int $scale The scale, validated.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
protected function __construct(string $value, int $scale = 0)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
$this->scale = $scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BigDecimal from an unscaled value and a scale.
|
||||||
|
*
|
||||||
|
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
|
||||||
|
*
|
||||||
|
* A negative scale is normalized to zero by appending zeros to the unscaled value.
|
||||||
|
*
|
||||||
|
* Example: `(12345, -3)` will result in the BigDecimal `12345000`.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
|
||||||
|
* @param int $scale The scale of the number. If negative, the scale will be set to zero
|
||||||
|
* and the unscaled value will be adjusted accordingly.
|
||||||
|
*
|
||||||
|
* @throws MathException If the value is not valid, or is not convertible to a BigInteger.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0): BigDecimal
|
||||||
|
{
|
||||||
|
$value = BigInteger::of($value)->toString();
|
||||||
|
|
||||||
|
if ($scale < 0) {
|
||||||
|
if ($value !== '0') {
|
||||||
|
$value .= str_repeat('0', -$scale);
|
||||||
|
}
|
||||||
|
$scale = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BigDecimal representing zero, with a scale of zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function zero(): BigDecimal
|
||||||
|
{
|
||||||
|
/** @var BigDecimal|null $zero */
|
||||||
|
static $zero;
|
||||||
|
|
||||||
|
if ($zero === null) {
|
||||||
|
$zero = new BigDecimal('0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BigDecimal representing one, with a scale of zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function one(): BigDecimal
|
||||||
|
{
|
||||||
|
/** @var BigDecimal|null $one */
|
||||||
|
static $one;
|
||||||
|
|
||||||
|
if ($one === null) {
|
||||||
|
$one = new BigDecimal('1');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $one;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BigDecimal representing ten, with a scale of zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function ten(): BigDecimal
|
||||||
|
{
|
||||||
|
/** @var BigDecimal|null $ten */
|
||||||
|
static $ten;
|
||||||
|
|
||||||
|
if ($ten === null) {
|
||||||
|
$ten = new BigDecimal('10');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ten;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sum of this number and the given one.
|
||||||
|
*
|
||||||
|
* The result has a scale of `max($this->scale, $that->scale)`.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function plus(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->value === '0' && $that->scale <= $this->scale) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->value === '0' && $this->scale <= $that->scale) {
|
||||||
|
return $that;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$a, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
|
$value = CalculatorRegistry::get()->add($a, $b);
|
||||||
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the difference of this number and the given one.
|
||||||
|
*
|
||||||
|
* The result has a scale of `max($this->scale, $that->scale)`.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function minus(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->value === '0' && $that->scale <= $this->scale) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$a, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
|
$value = CalculatorRegistry::get()->sub($a, $b);
|
||||||
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the product of this number and the given one.
|
||||||
|
*
|
||||||
|
* The result has a scale of `$this->scale + $that->scale`.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the multiplier is not valid, or is not convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function multipliedBy(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->value === '1' && $that->scale === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->value === '1' && $this->scale === 0) {
|
||||||
|
return $that;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
|
||||||
|
$scale = $this->scale + $that->scale;
|
||||||
|
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result of the division of this number by the given one, at the given scale.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
* @param int|null $scale The desired scale. Omitting this parameter is deprecated; it will be required in 0.15.
|
||||||
|
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the scale is negative.
|
||||||
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
|
||||||
|
* exactly at the given scale.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->isZero()) {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scale === null) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
trigger_error(
|
||||||
|
'Not passing a $scale to BigDecimal::dividedBy() is deprecated. ' .
|
||||||
|
'Use $a->dividedBy($b, $a->getScale(), $roundingMode) to retain current behavior.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
$scale = $this->scale;
|
||||||
|
} elseif ($scale < 0) {
|
||||||
|
throw new InvalidArgumentException('Scale must not be negative.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$p = $this->valueWithMinScale($that->scale + $scale);
|
||||||
|
$q = $that->valueWithMinScale($this->scale - $scale);
|
||||||
|
|
||||||
|
$result = CalculatorRegistry::get()->divRound($p, $q, $roundingMode);
|
||||||
|
|
||||||
|
return new BigDecimal($result, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exact result of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* The scale of the result is automatically calculated to fit all the fraction digits.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use dividedByExact() instead.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
|
||||||
|
* or the result yields an infinite number of digits.
|
||||||
|
*/
|
||||||
|
public function exactlyDividedBy(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::exactlyDividedBy() is deprecated and will be removed in 0.15. Use dividedByExact() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->dividedByExact($that);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exact result of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* The scale of the result is automatically calculated to fit all the fraction digits.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
* @throws RoundingNecessaryException If the result yields an infinite number of digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function dividedByExact(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->value === '0') {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
[, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
|
$d = rtrim($b, '0');
|
||||||
|
$scale = strlen($b) - strlen($d);
|
||||||
|
|
||||||
|
$calculator = CalculatorRegistry::get();
|
||||||
|
|
||||||
|
foreach ([5, 2] as $prime) {
|
||||||
|
for (; ;) {
|
||||||
|
$lastDigit = (int) $d[-1];
|
||||||
|
|
||||||
|
if ($lastDigit % $prime !== 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$d = $calculator->divQ($d, (string) $prime);
|
||||||
|
$scale++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this number exponentiated to the given value.
|
||||||
|
*
|
||||||
|
* The result has a scale of `$this->scale * $exponent`.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function power(int $exponent): BigDecimal
|
||||||
|
{
|
||||||
|
if ($exponent === 0) {
|
||||||
|
return BigDecimal::one();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exponent === 1) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'The exponent %d is not in the range 0 to %d.',
|
||||||
|
$exponent,
|
||||||
|
Calculator::MAX_POWER,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quotient of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* The quotient has a scale of `0`.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7.5` quotient `3` returns `2`
|
||||||
|
* - `7.5` quotient `-3` returns `-2`
|
||||||
|
* - `-7.5` quotient `3` returns `-2`
|
||||||
|
* - `-7.5` quotient `-3` returns `2`
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function quotient(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->isZero()) {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
$p = $this->valueWithMinScale($that->scale);
|
||||||
|
$q = $that->valueWithMinScale($this->scale);
|
||||||
|
|
||||||
|
$quotient = CalculatorRegistry::get()->divQ($p, $q);
|
||||||
|
|
||||||
|
return new BigDecimal($quotient, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the remainder of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* The remainder has a scale of `max($this->scale, $that->scale)`.
|
||||||
|
* The remainder, when non-zero, has the same sign as the dividend.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7.5` remainder `3` returns `1.5`
|
||||||
|
* - `7.5` remainder `-3` returns `1.5`
|
||||||
|
* - `-7.5` remainder `3` returns `-1.5`
|
||||||
|
* - `-7.5` remainder `-3` returns `-1.5`
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function remainder(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->isZero()) {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
$p = $this->valueWithMinScale($that->scale);
|
||||||
|
$q = $that->valueWithMinScale($this->scale);
|
||||||
|
|
||||||
|
$remainder = CalculatorRegistry::get()->divR($p, $q);
|
||||||
|
|
||||||
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
|
return new BigDecimal($remainder, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quotient and remainder of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7.5` quotientAndRemainder `3` returns [`2`, `1.5`]
|
||||||
|
* - `7.5` quotientAndRemainder `-3` returns [`-2`, `1.5`]
|
||||||
|
* - `-7.5` quotientAndRemainder `3` returns [`-2`, `-1.5`]
|
||||||
|
* - `-7.5` quotientAndRemainder `-3` returns [`2`, `-1.5`]
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function quotientAndRemainder(BigNumber|int|float|string $that): array
|
||||||
|
{
|
||||||
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
|
if ($that->isZero()) {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
$p = $this->valueWithMinScale($that->scale);
|
||||||
|
$q = $that->valueWithMinScale($this->scale);
|
||||||
|
|
||||||
|
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
|
||||||
|
|
||||||
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
|
$quotient = new BigDecimal($quotient, 0);
|
||||||
|
$remainder = new BigDecimal($remainder, $scale);
|
||||||
|
|
||||||
|
return [$quotient, $remainder];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the square root of this number, rounded to the given scale according to the given rounding mode.
|
||||||
|
*
|
||||||
|
* @param int $scale The target scale. Must be non-negative.
|
||||||
|
* @param RoundingMode $roundingMode The rounding mode to use, defaults to Down.
|
||||||
|
* ⚠️ WARNING: the default rounding mode was kept as Down for backward
|
||||||
|
* compatibility, but will change to Unnecessary in version 0.15. Pass a rounding
|
||||||
|
* mode explicitly to avoid this upcoming breaking change.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the scale is negative.
|
||||||
|
* @throws NegativeNumberException If this number is negative.
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
|
||||||
|
* exactly at the given scale.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function sqrt(int $scale, RoundingMode $roundingMode = RoundingMode::Down): BigDecimal
|
||||||
|
{
|
||||||
|
if (func_num_args() === 1) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
trigger_error(
|
||||||
|
'The default rounding mode of BigDecimal::sqrt() will change from Down to Unnecessary in version 0.15. ' .
|
||||||
|
'Pass a rounding mode explicitly to avoid this breaking change.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scale < 0) {
|
||||||
|
throw new InvalidArgumentException('Scale must not be negative.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->value === '0') {
|
||||||
|
return new BigDecimal('0', $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->value[0] === '-') {
|
||||||
|
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->value;
|
||||||
|
$inputScale = $this->scale;
|
||||||
|
|
||||||
|
if ($inputScale % 2 !== 0) {
|
||||||
|
$value .= '0';
|
||||||
|
$inputScale++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$calculator = CalculatorRegistry::get();
|
||||||
|
|
||||||
|
// Keep one extra digit for rounding.
|
||||||
|
$intermediateScale = max($scale, intdiv($inputScale, 2)) + 1;
|
||||||
|
$value .= str_repeat('0', 2 * $intermediateScale - $inputScale);
|
||||||
|
|
||||||
|
$sqrt = $calculator->sqrt($value);
|
||||||
|
$isExact = $calculator->mul($sqrt, $sqrt) === $value;
|
||||||
|
|
||||||
|
if (! $isExact) {
|
||||||
|
if ($roundingMode === RoundingMode::Unnecessary) {
|
||||||
|
throw RoundingNecessaryException::roundingNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-perfect-square sqrt is irrational, so the true value is strictly above this sqrt floor.
|
||||||
|
// Add one at the intermediate scale to guarantee Up/Ceiling round up at the target scale.
|
||||||
|
if (in_array($roundingMode, [RoundingMode::Up, RoundingMode::Ceiling], true)) {
|
||||||
|
$sqrt = $calculator->add($sqrt, '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Irrational sqrt cannot land exactly on a midpoint; treat tie-to-down modes as HalfUp.
|
||||||
|
elseif (in_array($roundingMode, [RoundingMode::HalfDown, RoundingMode::HalfEven, RoundingMode::HalfFloor], true)) {
|
||||||
|
$roundingMode = RoundingMode::HalfUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new BigDecimal($sqrt, $intermediateScale))->toScale($scale, $roundingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this BigDecimal with the decimal point moved to the left by the given number of places.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function withPointMovedLeft(int $n): BigDecimal
|
||||||
|
{
|
||||||
|
if ($n === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($n < 0) {
|
||||||
|
return $this->withPointMovedRight(-$n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigDecimal($this->value, $this->scale + $n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this BigDecimal with the decimal point moved to the right by the given number of places.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function withPointMovedRight(int $n): BigDecimal
|
||||||
|
{
|
||||||
|
if ($n === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($n < 0) {
|
||||||
|
return $this->withPointMovedLeft(-$n);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->value;
|
||||||
|
$scale = $this->scale - $n;
|
||||||
|
|
||||||
|
if ($scale < 0) {
|
||||||
|
if ($value !== '0') {
|
||||||
|
$value .= str_repeat('0', -$scale);
|
||||||
|
}
|
||||||
|
$scale = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
||||||
|
*
|
||||||
|
* @deprecated Use strippedOfTrailingZeros() instead.
|
||||||
|
*/
|
||||||
|
public function stripTrailingZeros(): BigDecimal
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::stripTrailingZeros() is deprecated, use strippedOfTrailingZeros() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->strippedOfTrailingZeros();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function strippedOfTrailingZeros(): BigDecimal
|
||||||
|
{
|
||||||
|
if ($this->scale === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trimmedValue = rtrim($this->value, '0');
|
||||||
|
|
||||||
|
if ($trimmedValue === '') {
|
||||||
|
return BigDecimal::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
$trimmableZeros = strlen($this->value) - strlen($trimmedValue);
|
||||||
|
|
||||||
|
if ($trimmableZeros === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($trimmableZeros > $this->scale) {
|
||||||
|
$trimmableZeros = $this->scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = substr($this->value, 0, -$trimmableZeros);
|
||||||
|
$scale = $this->scale - $trimmableZeros;
|
||||||
|
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function negated(): static
|
||||||
|
{
|
||||||
|
return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function compareTo(BigNumber|int|float|string $that): int
|
||||||
|
{
|
||||||
|
$that = BigNumber::of($that);
|
||||||
|
|
||||||
|
if ($that instanceof BigInteger) {
|
||||||
|
$that = $that->toBigDecimal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($that instanceof BigDecimal) {
|
||||||
|
[$a, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
|
return CalculatorRegistry::get()->cmp($a, $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -$that->compareTo($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function getSign(): int
|
||||||
|
{
|
||||||
|
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getUnscaledValue(): BigInteger
|
||||||
|
{
|
||||||
|
return self::newBigInteger($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getScale(): int
|
||||||
|
{
|
||||||
|
return $this->scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of significant digits in the number.
|
||||||
|
*
|
||||||
|
* This is the number of digits to both sides of the decimal point, stripped of leading zeros.
|
||||||
|
* The sign has no impact on the result.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* 0 => 0
|
||||||
|
* 0.0 => 0
|
||||||
|
* 123 => 3
|
||||||
|
* 123.456 => 6
|
||||||
|
* 0.00123 => 3
|
||||||
|
* 0.0012300 => 5
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getPrecision(): int
|
||||||
|
{
|
||||||
|
$value = $this->value;
|
||||||
|
|
||||||
|
if ($value === '0') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = strlen($value);
|
||||||
|
|
||||||
|
return ($value[0] === '-') ? $length - 1 : $length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representing the integral part of this decimal number.
|
||||||
|
*
|
||||||
|
* Example: `-123.456` => `-123`.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15 and re-introduced as returning BigInteger in 0.16.
|
||||||
|
*/
|
||||||
|
public function getIntegralPart(): string
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::getIntegralPart() is deprecated and will be removed in 0.15. It will be re-introduced as returning BigInteger in 0.16.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->scale === 0) {
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
|
return substr($value, 0, -$this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representing the fractional part of this decimal number.
|
||||||
|
*
|
||||||
|
* If the scale is zero, an empty string is returned.
|
||||||
|
*
|
||||||
|
* Examples: `-123.456` => '456', `123` => ''.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15 and re-introduced as returning BigDecimal with a different meaning in 0.16.
|
||||||
|
*/
|
||||||
|
public function getFractionalPart(): string
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::getFractionalPart() is deprecated and will be removed in 0.15. It will be re-introduced as returning BigDecimal with a different meaning in 0.16.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->scale === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
|
return substr($value, -$this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this decimal number has a non-zero fractional part.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function hasNonZeroFractionalPart(): bool
|
||||||
|
{
|
||||||
|
if ($this->scale === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
|
return substr($value, -$this->scale) !== str_repeat('0', $this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBigInteger(): BigInteger
|
||||||
|
{
|
||||||
|
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
|
||||||
|
|
||||||
|
return self::newBigInteger($zeroScaleDecimal->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBigDecimal(): BigDecimal
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBigRational(): BigRational
|
||||||
|
{
|
||||||
|
$numerator = self::newBigInteger($this->value);
|
||||||
|
$denominator = self::newBigInteger('1' . str_repeat('0', $this->scale));
|
||||||
|
|
||||||
|
return self::newBigRational($numerator, $denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
|
||||||
|
{
|
||||||
|
if ($scale === $this->scale) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toInt(): int
|
||||||
|
{
|
||||||
|
return $this->toBigInteger()->toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toFloat(): float
|
||||||
|
{
|
||||||
|
return (float) $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return numeric-string
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
if ($this->scale === 0) {
|
||||||
|
/** @var numeric-string */
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
|
/** @phpstan-ignore return.type */
|
||||||
|
return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @return array{value: string, scale: int}
|
||||||
|
*/
|
||||||
|
public function __serialize(): array
|
||||||
|
{
|
||||||
|
return ['value' => $this->value, 'scale' => $this->scale];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @param array{value: string, scale: int} $data
|
||||||
|
*
|
||||||
|
* @throws LogicException
|
||||||
|
*/
|
||||||
|
public function __unserialize(array $data): void
|
||||||
|
{
|
||||||
|
/** @phpstan-ignore isset.initializedProperty */
|
||||||
|
if (isset($this->value)) {
|
||||||
|
throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @phpstan-ignore deadCode.unreachable */
|
||||||
|
$this->value = $data['value'];
|
||||||
|
$this->scale = $data['scale'];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected static function from(BigNumber $number): static
|
||||||
|
{
|
||||||
|
return $number->toBigDecimal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the internal values of the given decimal numbers on the same scale.
|
||||||
|
*
|
||||||
|
* @return array{string, string} The scaled integer values of $x and $y.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function scaleValues(BigDecimal $x, BigDecimal $y): array
|
||||||
|
{
|
||||||
|
$a = $x->value;
|
||||||
|
$b = $y->value;
|
||||||
|
|
||||||
|
if ($b !== '0' && $x->scale > $y->scale) {
|
||||||
|
$b .= str_repeat('0', $x->scale - $y->scale);
|
||||||
|
} elseif ($a !== '0' && $x->scale < $y->scale) {
|
||||||
|
$a .= str_repeat('0', $y->scale - $x->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$a, $b];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function valueWithMinScale(int $scale): string
|
||||||
|
{
|
||||||
|
$value = $this->value;
|
||||||
|
|
||||||
|
if ($this->value !== '0' && $scale > $this->scale) {
|
||||||
|
$value .= str_repeat('0', $scale - $this->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function getUnscaledValueWithLeadingZeros(): string
|
||||||
|
{
|
||||||
|
$value = $this->value;
|
||||||
|
$targetLength = $this->scale + 1;
|
||||||
|
$negative = ($value[0] === '-');
|
||||||
|
$length = strlen($value);
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
$length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($length >= $targetLength) {
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
$value = substr($value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = str_pad($value, $targetLength, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
$value = '-' . $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1351
File diff suppressed because it is too large
Load Diff
Vendored
+712
@@ -0,0 +1,712 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math;
|
||||||
|
|
||||||
|
use Brick\Math\Exception\DivisionByZeroException;
|
||||||
|
use Brick\Math\Exception\MathException;
|
||||||
|
use Brick\Math\Exception\NumberFormatException;
|
||||||
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use JsonSerializable;
|
||||||
|
use Override;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
use function array_shift;
|
||||||
|
use function assert;
|
||||||
|
use function filter_var;
|
||||||
|
use function is_float;
|
||||||
|
use function is_int;
|
||||||
|
use function is_nan;
|
||||||
|
use function is_null;
|
||||||
|
use function ltrim;
|
||||||
|
use function preg_match;
|
||||||
|
use function str_contains;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
use function trigger_error;
|
||||||
|
|
||||||
|
use const E_USER_DEPRECATED;
|
||||||
|
use const FILTER_VALIDATE_INT;
|
||||||
|
use const PREG_UNMATCHED_AS_NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for arbitrary-precision numbers.
|
||||||
|
*
|
||||||
|
* This class is sealed: it is part of the public API but should not be subclassed in userland.
|
||||||
|
* Protected methods may change in any version.
|
||||||
|
*
|
||||||
|
* @phpstan-sealed BigInteger|BigDecimal|BigRational
|
||||||
|
*/
|
||||||
|
abstract readonly class BigNumber implements JsonSerializable, Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The regular expression used to parse integer or decimal numbers.
|
||||||
|
*/
|
||||||
|
private const PARSE_REGEXP_NUMERICAL =
|
||||||
|
'/^' .
|
||||||
|
'(?<sign>[\-\+])?' .
|
||||||
|
'(?<integral>[0-9]+)?' .
|
||||||
|
'(?<point>\.)?' .
|
||||||
|
'(?<fractional>[0-9]+)?' .
|
||||||
|
'(?:[eE](?<exponent>[\-\+]?[0-9]+))?' .
|
||||||
|
'$/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The regular expression used to parse rational numbers.
|
||||||
|
*/
|
||||||
|
private const PARSE_REGEXP_RATIONAL =
|
||||||
|
'/^' .
|
||||||
|
'(?<sign>[\-\+])?' .
|
||||||
|
'(?<numerator>[0-9]+)' .
|
||||||
|
'\/' .
|
||||||
|
'(?<denominator>[0-9]+)' .
|
||||||
|
'$/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BigNumber of the given value.
|
||||||
|
*
|
||||||
|
* When of() is called on BigNumber, the concrete return type is dependent on the given value, with the following
|
||||||
|
* rules:
|
||||||
|
*
|
||||||
|
* - BigNumber instances are returned as is
|
||||||
|
* - integer numbers are returned as BigInteger
|
||||||
|
* - floating point numbers are converted to a string then parsed as such (deprecated, will be removed in 0.15)
|
||||||
|
* - strings containing a `/` character are returned as BigRational
|
||||||
|
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
|
||||||
|
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
|
||||||
|
*
|
||||||
|
* When of() is called on BigInteger, BigDecimal, or BigRational, the resulting number is converted to an instance
|
||||||
|
* of the subclass when possible; otherwise a RoundingNecessaryException exception is thrown.
|
||||||
|
*
|
||||||
|
* @throws NumberFormatException If the format of the number is not valid.
|
||||||
|
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||||
|
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function of(BigNumber|int|float|string $value): static
|
||||||
|
{
|
||||||
|
$value = self::_of($value);
|
||||||
|
|
||||||
|
if (static::class === BigNumber::class) {
|
||||||
|
assert($value instanceof static);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::from($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BigNumber of the given value, or returns null if the input is null.
|
||||||
|
*
|
||||||
|
* Behaves like of() for non-null values.
|
||||||
|
*
|
||||||
|
* @see BigNumber::of()
|
||||||
|
*
|
||||||
|
* @throws NumberFormatException If the format of the number is not valid.
|
||||||
|
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||||
|
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function ofNullable(BigNumber|int|float|string|null $value): ?static
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::of($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum of the given values.
|
||||||
|
*
|
||||||
|
* If several values are equal and minimal, the first one is returned.
|
||||||
|
* This can affect the concrete return type when calling this method on BigNumber.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers must be convertible to an
|
||||||
|
* instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If no values are given.
|
||||||
|
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
|
||||||
|
* this method is called on.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function min(BigNumber|int|float|string ...$values): static
|
||||||
|
{
|
||||||
|
$min = null;
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = static::of($value);
|
||||||
|
|
||||||
|
if ($min === null || $value->isLessThan($min)) {
|
||||||
|
$min = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($min === null) {
|
||||||
|
throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum of the given values.
|
||||||
|
*
|
||||||
|
* If several values are equal and maximal, the first one is returned.
|
||||||
|
* This can affect the concrete return type when calling this method on BigNumber.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers must be convertible to an
|
||||||
|
* instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If no values are given.
|
||||||
|
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
|
||||||
|
* this method is called on.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function max(BigNumber|int|float|string ...$values): static
|
||||||
|
{
|
||||||
|
$max = null;
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = static::of($value);
|
||||||
|
|
||||||
|
if ($max === null || $value->isGreaterThan($max)) {
|
||||||
|
$max = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($max === null) {
|
||||||
|
throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sum of the given values.
|
||||||
|
*
|
||||||
|
* When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among
|
||||||
|
* the given values (BigInteger < BigDecimal < BigRational).
|
||||||
|
*
|
||||||
|
* When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that
|
||||||
|
* specific subclass, and returns a result of the same type.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers must be convertible to an
|
||||||
|
* instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If no values are given.
|
||||||
|
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
|
||||||
|
* this method is called on.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function sum(BigNumber|int|float|string ...$values): static
|
||||||
|
{
|
||||||
|
$first = array_shift($values);
|
||||||
|
|
||||||
|
if ($first === null) {
|
||||||
|
throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = static::of($first);
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$sum = self::add($sum, static::of($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($sum instanceof static);
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is equal to the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isEqualTo(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly less than the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isLessThan(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is less than or equal to the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isLessThanOrEqualTo(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly greater than the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isGreaterThan(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is greater than or equal to the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number equals zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isZero(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly negative.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isNegative(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is negative or zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isNegativeOrZero(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly positive.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isPositive(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is positive or zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isPositiveOrZero(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of this number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function abs(): static
|
||||||
|
{
|
||||||
|
return $this->isNegative() ? $this->negated() : $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the negated value of this number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function negated(): static;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sign of this number.
|
||||||
|
*
|
||||||
|
* Returns -1 if the number is negative, 0 if zero, 1 if positive.
|
||||||
|
*
|
||||||
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function getSign(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this number to the given one.
|
||||||
|
*
|
||||||
|
* Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
|
||||||
|
*
|
||||||
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @throws MathException If the number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function compareTo(BigNumber|int|float|string $that): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits (clamps) this number between the given minimum and maximum values.
|
||||||
|
*
|
||||||
|
* If the number is lower than $min, returns $min.
|
||||||
|
* If the number is greater than $max, returns $max.
|
||||||
|
* Otherwise, returns this number unchanged.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $min The minimum. Must be convertible to an instance of the class this method is called on.
|
||||||
|
* @param BigNumber|int|float|string $max The maximum. Must be convertible to an instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws MathException If min/max are not convertible to an instance of the class this method is called on.
|
||||||
|
* @throws InvalidArgumentException If min is greater than max.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): static
|
||||||
|
{
|
||||||
|
$min = static::of($min);
|
||||||
|
$max = static::of($max);
|
||||||
|
|
||||||
|
if ($min->isGreaterThan($max)) {
|
||||||
|
throw new InvalidArgumentException('Minimum value must be less than or equal to maximum value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isLessThan($min)) {
|
||||||
|
return $min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isGreaterThan($max)) {
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigInteger.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toBigInteger(): BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toBigDecimal(): BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigRational.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toBigRational(): BigRational;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
|
||||||
|
*
|
||||||
|
* @param int $scale The scale of the resulting `BigDecimal`. Must be non-negative.
|
||||||
|
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the scale is negative.
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used, and this number cannot be converted to
|
||||||
|
* the given scale without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exact value of this number as a native integer.
|
||||||
|
*
|
||||||
|
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
|
||||||
|
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
|
||||||
|
*
|
||||||
|
* @throws MathException If this number cannot be exactly converted to a native integer.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toInt(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an approximation of this number as a floating-point value.
|
||||||
|
*
|
||||||
|
* Note that this method can discard information as the precision of a floating-point value
|
||||||
|
* is inherently limited.
|
||||||
|
*
|
||||||
|
* If the number is greater than the largest representable floating point number, positive infinity is returned.
|
||||||
|
* If the number is less than the smallest representable floating point number, negative infinity is returned.
|
||||||
|
* This method never returns NaN.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toFloat(): float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this number.
|
||||||
|
*
|
||||||
|
* The output of this method can be parsed by the `of()` factory method; this will yield an object equal to this
|
||||||
|
* one, but possibly of a different type if instantiated through `BigNumber::of()`.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toString(): string;
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
final public function jsonSerialize(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden by subclasses to convert a BigNumber to an instance of the subclass.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If the value cannot be converted.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract protected static function from(BigNumber $number): static;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy method to access BigInteger's protected constructor from sibling classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function newBigInteger(string $value): BigInteger
|
||||||
|
{
|
||||||
|
return new BigInteger($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy method to access BigDecimal's protected constructor from sibling classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function newBigDecimal(string $value, int $scale = 0): BigDecimal
|
||||||
|
{
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy method to access BigRational's protected constructor from sibling classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator): BigRational
|
||||||
|
{
|
||||||
|
return new BigRational($numerator, $denominator, $checkDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws NumberFormatException If the format of the number is not valid.
|
||||||
|
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private static function _of(BigNumber|int|float|string $value): BigNumber
|
||||||
|
{
|
||||||
|
if ($value instanceof BigNumber) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_int($value)) {
|
||||||
|
return new BigInteger((string) $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_float($value)) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
trigger_error(
|
||||||
|
'Passing floats to BigNumber::of() and arithmetic methods is deprecated and will be removed in 0.15. ' .
|
||||||
|
'Cast the float to string explicitly to preserve the previous behaviour.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_nan($value)) {
|
||||||
|
$value = 'NAN';
|
||||||
|
} else {
|
||||||
|
$value = (string) $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($value, '/')) {
|
||||||
|
// Rational number
|
||||||
|
if (preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||||
|
throw NumberFormatException::invalidFormat($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sign = $matches['sign'];
|
||||||
|
$numerator = $matches['numerator'];
|
||||||
|
$denominator = $matches['denominator'];
|
||||||
|
|
||||||
|
$numerator = self::cleanUp($sign, $numerator);
|
||||||
|
$denominator = self::cleanUp(null, $denominator);
|
||||||
|
|
||||||
|
if ($denominator === '0') {
|
||||||
|
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigRational(
|
||||||
|
new BigInteger($numerator),
|
||||||
|
new BigInteger($denominator),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Integer or decimal number
|
||||||
|
if (preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||||
|
throw NumberFormatException::invalidFormat($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sign = $matches['sign'];
|
||||||
|
$point = $matches['point'];
|
||||||
|
$integral = $matches['integral'];
|
||||||
|
$fractional = $matches['fractional'];
|
||||||
|
$exponent = $matches['exponent'];
|
||||||
|
|
||||||
|
if ($integral === null && $fractional === null) {
|
||||||
|
throw NumberFormatException::invalidFormat($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($integral === null) {
|
||||||
|
$integral = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($point !== null || $exponent !== null) {
|
||||||
|
$fractional ??= '';
|
||||||
|
|
||||||
|
if ($exponent !== null) {
|
||||||
|
if ($exponent[0] === '-') {
|
||||||
|
$exponent = ltrim(substr($exponent, 1), '0') ?: '0';
|
||||||
|
$exponent = filter_var($exponent, FILTER_VALIDATE_INT);
|
||||||
|
if ($exponent !== false) {
|
||||||
|
$exponent = -$exponent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($exponent[0] === '+') {
|
||||||
|
$exponent = substr($exponent, 1);
|
||||||
|
}
|
||||||
|
$exponent = ltrim($exponent, '0') ?: '0';
|
||||||
|
$exponent = filter_var($exponent, FILTER_VALIDATE_INT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$exponent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exponent === false) {
|
||||||
|
throw new NumberFormatException('Exponent too large.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$unscaledValue = self::cleanUp($sign, $integral . $fractional);
|
||||||
|
|
||||||
|
$scale = strlen($fractional) - $exponent;
|
||||||
|
|
||||||
|
if ($scale < 0) {
|
||||||
|
if ($unscaledValue !== '0') {
|
||||||
|
$unscaledValue .= str_repeat('0', -$scale);
|
||||||
|
}
|
||||||
|
$scale = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigDecimal($unscaledValue, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
$integral = self::cleanUp($sign, $integral);
|
||||||
|
|
||||||
|
return new BigInteger($integral);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes optional leading zeros and applies sign.
|
||||||
|
*
|
||||||
|
* @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'.
|
||||||
|
* @param string $number The number, validated as a string of digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private static function cleanUp(string|null $sign, string $number): string
|
||||||
|
{
|
||||||
|
$number = ltrim($number, '0');
|
||||||
|
|
||||||
|
if ($number === '') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sign === '-' ? '-' . $number : $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private static function add(BigNumber $a, BigNumber $b): BigNumber
|
||||||
|
{
|
||||||
|
if ($a instanceof BigRational) {
|
||||||
|
return $a->plus($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b instanceof BigRational) {
|
||||||
|
return $b->plus($a);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a instanceof BigDecimal) {
|
||||||
|
return $a->plus($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b instanceof BigDecimal) {
|
||||||
|
return $b->plus($a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $a->plus($b);
|
||||||
|
}
|
||||||
|
}
|
||||||
+606
@@ -0,0 +1,606 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math;
|
||||||
|
|
||||||
|
use Brick\Math\Exception\DivisionByZeroException;
|
||||||
|
use Brick\Math\Exception\MathException;
|
||||||
|
use Brick\Math\Exception\NumberFormatException;
|
||||||
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LogicException;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
use function is_finite;
|
||||||
|
use function max;
|
||||||
|
use function min;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
use function trigger_error;
|
||||||
|
|
||||||
|
use const E_USER_DEPRECATED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An arbitrarily large rational number.
|
||||||
|
*
|
||||||
|
* This class is immutable.
|
||||||
|
*
|
||||||
|
* Fractions are automatically simplified to lowest terms. For example, `2/4` becomes `1/2`.
|
||||||
|
* The denominator is always strictly positive; the sign is carried by the numerator.
|
||||||
|
*/
|
||||||
|
final readonly class BigRational extends BigNumber
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The numerator.
|
||||||
|
*/
|
||||||
|
private BigInteger $numerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The denominator. Always strictly positive.
|
||||||
|
*/
|
||||||
|
private BigInteger $denominator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protected constructor. Use a factory method to obtain an instance.
|
||||||
|
*
|
||||||
|
* @param BigInteger $numerator The numerator.
|
||||||
|
* @param BigInteger $denominator The denominator.
|
||||||
|
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
|
||||||
|
*
|
||||||
|
* @throws DivisionByZeroException If the denominator is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
|
||||||
|
{
|
||||||
|
if ($checkDenominator) {
|
||||||
|
if ($denominator->isZero()) {
|
||||||
|
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($denominator->isNegative()) {
|
||||||
|
$numerator = $numerator->negated();
|
||||||
|
$denominator = $denominator->negated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->numerator = $numerator;
|
||||||
|
$this->denominator = $denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BigRational out of a numerator and a denominator.
|
||||||
|
*
|
||||||
|
* If the denominator is negative, the signs of both the numerator and the denominator
|
||||||
|
* will be inverted to ensure that the denominator is always positive.
|
||||||
|
*
|
||||||
|
* @deprecated Use ofFraction() instead.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
||||||
|
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
||||||
|
*
|
||||||
|
* @throws NumberFormatException If an argument does not represent a valid number.
|
||||||
|
* @throws RoundingNecessaryException If an argument represents a non-integer number.
|
||||||
|
* @throws DivisionByZeroException If the denominator is zero.
|
||||||
|
*/
|
||||||
|
public static function nd(
|
||||||
|
BigNumber|int|float|string $numerator,
|
||||||
|
BigNumber|int|float|string $denominator,
|
||||||
|
): BigRational {
|
||||||
|
trigger_error(
|
||||||
|
'The BigRational::nd() method is deprecated, use BigRational::ofFraction() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return self::ofFraction($numerator, $denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BigRational out of a numerator and a denominator.
|
||||||
|
*
|
||||||
|
* If the denominator is negative, the signs of both the numerator and the denominator
|
||||||
|
* will be inverted to ensure that the denominator is always positive.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
||||||
|
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
||||||
|
*
|
||||||
|
* @throws MathException If an argument is not valid, or is not convertible to a BigInteger.
|
||||||
|
* @throws DivisionByZeroException If the denominator is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function ofFraction(
|
||||||
|
BigNumber|int|float|string $numerator,
|
||||||
|
BigNumber|int|float|string $denominator,
|
||||||
|
): BigRational {
|
||||||
|
$numerator = BigInteger::of($numerator);
|
||||||
|
$denominator = BigInteger::of($denominator);
|
||||||
|
|
||||||
|
return new BigRational($numerator, $denominator, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BigRational representing zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function zero(): BigRational
|
||||||
|
{
|
||||||
|
/** @var BigRational|null $zero */
|
||||||
|
static $zero;
|
||||||
|
|
||||||
|
if ($zero === null) {
|
||||||
|
$zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BigRational representing one.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function one(): BigRational
|
||||||
|
{
|
||||||
|
/** @var BigRational|null $one */
|
||||||
|
static $one;
|
||||||
|
|
||||||
|
if ($one === null) {
|
||||||
|
$one = new BigRational(BigInteger::one(), BigInteger::one(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $one;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a BigRational representing ten.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function ten(): BigRational
|
||||||
|
{
|
||||||
|
/** @var BigRational|null $ten */
|
||||||
|
static $ten;
|
||||||
|
|
||||||
|
if ($ten === null) {
|
||||||
|
$ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ten;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getNumerator(): BigInteger
|
||||||
|
{
|
||||||
|
return $this->numerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getDenominator(): BigInteger
|
||||||
|
{
|
||||||
|
return $this->denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quotient of the division of the numerator by the denominator.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use getIntegralPart() instead.
|
||||||
|
*/
|
||||||
|
public function quotient(): BigInteger
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigRational::quotient() is deprecated and will be removed in 0.15. Use getIntegralPart() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->numerator->quotient($this->denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the remainder of the division of the numerator by the denominator.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use `$number->getNumerator()->remainder($number->getDenominator())` instead.
|
||||||
|
*/
|
||||||
|
public function remainder(): BigInteger
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigRational::remainder() is deprecated and will be removed in 0.15. Use `$number->getNumerator()->remainder($number->getDenominator())` instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->numerator->remainder($this->denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quotient and remainder of the division of the numerator by the denominator.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead.
|
||||||
|
*
|
||||||
|
* @return array{BigInteger, BigInteger}
|
||||||
|
*/
|
||||||
|
public function quotientAndRemainder(): array
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigRational::quotientAndRemainder() is deprecated and will be removed in 0.15. Use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->numerator->quotientAndRemainder($this->denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integral part of this rational number.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7/3` returns `2` (since 7/3 = 2 + 1/3)
|
||||||
|
* - `-7/3` returns `-2` (since -7/3 = -2 + (-1/3))
|
||||||
|
*
|
||||||
|
* The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getIntegralPart(): BigInteger
|
||||||
|
{
|
||||||
|
return $this->numerator->quotient($this->denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fractional part of this rational number.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7/3` returns `1/3` (since 7/3 = 2 + 1/3)
|
||||||
|
* - `-7/3` returns `-1/3` (since -7/3 = -2 + (-1/3))
|
||||||
|
*
|
||||||
|
* The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getFractionalPart(): BigRational
|
||||||
|
{
|
||||||
|
return new BigRational($this->numerator->remainder($this->denominator), $this->denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sum of this number and the given one.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The number to add.
|
||||||
|
*
|
||||||
|
* @throws MathException If the number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function plus(BigNumber|int|float|string $that): BigRational
|
||||||
|
{
|
||||||
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||||
|
$numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
|
||||||
|
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||||
|
|
||||||
|
return new BigRational($numerator, $denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the difference of this number and the given one.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The number to subtract.
|
||||||
|
*
|
||||||
|
* @throws MathException If the number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function minus(BigNumber|int|float|string $that): BigRational
|
||||||
|
{
|
||||||
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||||
|
$numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
|
||||||
|
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||||
|
|
||||||
|
return new BigRational($numerator, $denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the product of this number and the given one.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The multiplier.
|
||||||
|
*
|
||||||
|
* @throws MathException If the multiplier is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function multipliedBy(BigNumber|int|float|string $that): BigRational
|
||||||
|
{
|
||||||
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
$numerator = $this->numerator->multipliedBy($that->numerator);
|
||||||
|
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||||
|
|
||||||
|
return new BigRational($numerator, $denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not valid.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function dividedBy(BigNumber|int|float|string $that): BigRational
|
||||||
|
{
|
||||||
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
if ($that->isZero()) {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||||
|
$denominator = $this->denominator->multipliedBy($that->numerator);
|
||||||
|
|
||||||
|
return new BigRational($numerator, $denominator, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this number exponentiated to the given value.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function power(int $exponent): BigRational
|
||||||
|
{
|
||||||
|
if ($exponent === 0) {
|
||||||
|
return BigRational::one();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exponent === 1) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigRational(
|
||||||
|
$this->numerator->power($exponent),
|
||||||
|
$this->denominator->power($exponent),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reciprocal of this BigRational.
|
||||||
|
*
|
||||||
|
* The reciprocal has the numerator and denominator swapped.
|
||||||
|
*
|
||||||
|
* @throws DivisionByZeroException If the numerator is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function reciprocal(): BigRational
|
||||||
|
{
|
||||||
|
return new BigRational($this->denominator, $this->numerator, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function negated(): static
|
||||||
|
{
|
||||||
|
return new BigRational($this->numerator->negated(), $this->denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the simplified value of this BigRational.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function simplified(): BigRational
|
||||||
|
{
|
||||||
|
$gcd = $this->numerator->gcd($this->denominator);
|
||||||
|
|
||||||
|
$numerator = $this->numerator->quotient($gcd);
|
||||||
|
$denominator = $this->denominator->quotient($gcd);
|
||||||
|
|
||||||
|
return new BigRational($numerator, $denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function compareTo(BigNumber|int|float|string $that): int
|
||||||
|
{
|
||||||
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
if ($this->denominator->isEqualTo($that->denominator)) {
|
||||||
|
return $this->numerator->compareTo($that->numerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->numerator
|
||||||
|
->multipliedBy($that->denominator)
|
||||||
|
->compareTo($that->numerator->multipliedBy($this->denominator));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function getSign(): int
|
||||||
|
{
|
||||||
|
return $this->numerator->getSign();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBigInteger(): BigInteger
|
||||||
|
{
|
||||||
|
$simplified = $this->simplified();
|
||||||
|
|
||||||
|
if (! $simplified->denominator->isEqualTo(1)) {
|
||||||
|
throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $simplified->numerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBigDecimal(): BigDecimal
|
||||||
|
{
|
||||||
|
return $this->numerator->toBigDecimal()->dividedByExact($this->denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBigRational(): BigRational
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
|
||||||
|
{
|
||||||
|
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toInt(): int
|
||||||
|
{
|
||||||
|
return $this->toBigInteger()->toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toFloat(): float
|
||||||
|
{
|
||||||
|
$simplified = $this->simplified();
|
||||||
|
$numeratorFloat = $simplified->numerator->toFloat();
|
||||||
|
$denominatorFloat = $simplified->denominator->toFloat();
|
||||||
|
|
||||||
|
if (is_finite($numeratorFloat) && is_finite($denominatorFloat)) {
|
||||||
|
return $numeratorFloat / $denominatorFloat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one side overflows to INF; use a decimal approximation instead.
|
||||||
|
// We need ~17 significant digits for double precision (we use 20 for some margin). Since $scale controls
|
||||||
|
// decimal places (not significant digits), we subtract the estimated order of magnitude so that large results
|
||||||
|
// use fewer decimal places and small results use more (to look past leading zeros). Clamped to [0, 350] as
|
||||||
|
// doubles range from e-324 to e308 (350 ≈ 324 + 20 significant digits + margin).
|
||||||
|
$magnitude = strlen($simplified->numerator->abs()->toString()) - strlen($simplified->denominator->toString());
|
||||||
|
$scale = min(350, max(0, 20 - $magnitude));
|
||||||
|
|
||||||
|
return $simplified->numerator
|
||||||
|
->toBigDecimal()
|
||||||
|
->dividedBy($simplified->denominator, $scale, RoundingMode::HalfEven)
|
||||||
|
->toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
$numerator = $this->numerator->toString();
|
||||||
|
$denominator = $this->denominator->toString();
|
||||||
|
|
||||||
|
if ($denominator === '1') {
|
||||||
|
return $numerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $numerator . '/' . $denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decimal representation of this rational number, with repeating decimals in parentheses.
|
||||||
|
*
|
||||||
|
* WARNING: This method is unbounded.
|
||||||
|
* The length of the repeating decimal period can be as large as `denominator - 1`.
|
||||||
|
* For fractions with large denominators, this method can use excessive memory and CPU time.
|
||||||
|
* For example, `1/100019` has a repeating period of 100,018 digits.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `10/3` returns `3.(3)`
|
||||||
|
* - `171/70` returns `2.4(428571)`
|
||||||
|
* - `1/2` returns `0.5`
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function toRepeatingDecimalString(): string
|
||||||
|
{
|
||||||
|
if ($this->numerator->isZero()) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sign = $this->numerator->isNegative() ? '-' : '';
|
||||||
|
$numerator = $this->numerator->abs();
|
||||||
|
$denominator = $this->denominator;
|
||||||
|
|
||||||
|
$integral = $numerator->quotient($denominator);
|
||||||
|
$remainder = $numerator->remainder($denominator);
|
||||||
|
|
||||||
|
$integralString = $integral->toString();
|
||||||
|
|
||||||
|
if ($remainder->isZero()) {
|
||||||
|
return $sign . $integralString;
|
||||||
|
}
|
||||||
|
|
||||||
|
$digits = '';
|
||||||
|
$remainderPositions = [];
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
while (! $remainder->isZero()) {
|
||||||
|
$remainderString = $remainder->toString();
|
||||||
|
|
||||||
|
if (isset($remainderPositions[$remainderString])) {
|
||||||
|
$repeatIndex = $remainderPositions[$remainderString];
|
||||||
|
$nonRepeating = substr($digits, 0, $repeatIndex);
|
||||||
|
$repeating = substr($digits, $repeatIndex);
|
||||||
|
|
||||||
|
return $sign . $integralString . '.' . $nonRepeating . '(' . $repeating . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
$remainderPositions[$remainderString] = $index;
|
||||||
|
$remainder = $remainder->multipliedBy(10);
|
||||||
|
|
||||||
|
$digits .= $remainder->quotient($denominator)->toString();
|
||||||
|
$remainder = $remainder->remainder($denominator);
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sign . $integralString . '.' . $digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @return array{numerator: BigInteger, denominator: BigInteger}
|
||||||
|
*/
|
||||||
|
public function __serialize(): array
|
||||||
|
{
|
||||||
|
return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @param array{numerator: BigInteger, denominator: BigInteger} $data
|
||||||
|
*
|
||||||
|
* @throws LogicException
|
||||||
|
*/
|
||||||
|
public function __unserialize(array $data): void
|
||||||
|
{
|
||||||
|
/** @phpstan-ignore isset.initializedProperty */
|
||||||
|
if (isset($this->numerator)) {
|
||||||
|
throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @phpstan-ignore deadCode.unreachable */
|
||||||
|
$this->numerator = $data['numerator'];
|
||||||
|
$this->denominator = $data['denominator'];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected static function from(BigNumber $number): static
|
||||||
|
{
|
||||||
|
return $number->toBigRational();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a division by zero occurs.
|
||||||
|
*/
|
||||||
|
final class DivisionByZeroException extends MathException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function divisionByZero(): DivisionByZeroException
|
||||||
|
{
|
||||||
|
return new self('Division by zero.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function modulusMustNotBeZero(): DivisionByZeroException
|
||||||
|
{
|
||||||
|
return new self('The modulus must not be zero.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function denominatorMustNotBeZero(): DivisionByZeroException
|
||||||
|
{
|
||||||
|
return new self('The denominator of a rational number cannot be zero.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
use Brick\Math\BigInteger;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
use const PHP_INT_MAX;
|
||||||
|
use const PHP_INT_MIN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an integer overflow occurs.
|
||||||
|
*/
|
||||||
|
final class IntegerOverflowException extends MathException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function toIntOverflow(BigInteger $value): IntegerOverflowException
|
||||||
|
{
|
||||||
|
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
|
||||||
|
|
||||||
|
return new self(sprintf($message, $value->toString(), PHP_INT_MIN, PHP_INT_MAX));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all math exceptions.
|
||||||
|
*/
|
||||||
|
class MathException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
|
||||||
|
*/
|
||||||
|
final class NegativeNumberException extends MathException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
use function dechex;
|
||||||
|
use function ord;
|
||||||
|
use function sprintf;
|
||||||
|
use function strtoupper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when attempting to create a number from a string with an invalid format.
|
||||||
|
*/
|
||||||
|
final class NumberFormatException extends MathException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function invalidFormat(string $value): self
|
||||||
|
{
|
||||||
|
return new self(sprintf(
|
||||||
|
'The given value "%s" does not represent a valid number.',
|
||||||
|
$value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $char The failing character.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function charNotInAlphabet(string $char): self
|
||||||
|
{
|
||||||
|
return new self(sprintf(
|
||||||
|
'Character %s is not valid in the given alphabet.',
|
||||||
|
self::charToString($char),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private static function charToString(string $char): string
|
||||||
|
{
|
||||||
|
$ord = ord($char);
|
||||||
|
|
||||||
|
if ($ord < 32 || $ord > 126) {
|
||||||
|
$char = strtoupper(dechex($ord));
|
||||||
|
|
||||||
|
if ($ord < 16) {
|
||||||
|
$char = '0' . $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0x' . $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '"' . $char . '"';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a number cannot be represented at the requested scale without rounding.
|
||||||
|
*/
|
||||||
|
final class RoundingNecessaryException extends MathException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function roundingNecessary(): RoundingNecessaryException
|
||||||
|
{
|
||||||
|
return new self('Rounding is necessary to represent the result of the operation at this scale.');
|
||||||
|
}
|
||||||
|
}
|
||||||
+704
@@ -0,0 +1,704 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Internal;
|
||||||
|
|
||||||
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
use Brick\Math\RoundingMode;
|
||||||
|
|
||||||
|
use function chr;
|
||||||
|
use function ltrim;
|
||||||
|
use function ord;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
use function strpos;
|
||||||
|
use function strrev;
|
||||||
|
use function strtolower;
|
||||||
|
use function substr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs basic operations on arbitrary size integers.
|
||||||
|
*
|
||||||
|
* Unless otherwise specified, all parameters must be validated as non-empty strings of digits,
|
||||||
|
* without leading zero, and with an optional leading minus sign if the number is not zero.
|
||||||
|
*
|
||||||
|
* Any other parameter format will lead to undefined behaviour.
|
||||||
|
* All methods must return strings respecting this format, unless specified otherwise.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract readonly class Calculator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The maximum exponent value allowed for the pow() method.
|
||||||
|
*/
|
||||||
|
public const MAX_POWER = 1_000_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alphabet for converting from and to base 2 to 36, lowercase.
|
||||||
|
*/
|
||||||
|
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of a number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function abs(string $n): string
|
||||||
|
{
|
||||||
|
return ($n[0] === '-') ? substr($n, 1) : $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negates a number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function neg(string $n): string
|
||||||
|
{
|
||||||
|
if ($n === '0') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($n[0] === '-') {
|
||||||
|
return substr($n, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '-' . $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two numbers.
|
||||||
|
*
|
||||||
|
* Returns -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
|
||||||
|
*
|
||||||
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function cmp(string $a, string $b): int
|
||||||
|
{
|
||||||
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
|
if ($aNeg && ! $bNeg) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bNeg && ! $aNeg) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aLen = strlen($aDig);
|
||||||
|
$bLen = strlen($bDig);
|
||||||
|
|
||||||
|
if ($aLen < $bLen) {
|
||||||
|
$result = -1;
|
||||||
|
} elseif ($aLen > $bLen) {
|
||||||
|
$result = 1;
|
||||||
|
} else {
|
||||||
|
$result = $aDig <=> $bDig;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $aNeg ? -$result : $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds two numbers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function add(string $a, string $b): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts two numbers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function sub(string $a, string $b): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies two numbers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function mul(string $a, string $b): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quotient of the division of two numbers.
|
||||||
|
*
|
||||||
|
* @param string $a The dividend.
|
||||||
|
* @param string $b The divisor, must not be zero.
|
||||||
|
*
|
||||||
|
* @return string The quotient.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function divQ(string $a, string $b): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the remainder of the division of two numbers.
|
||||||
|
*
|
||||||
|
* @param string $a The dividend.
|
||||||
|
* @param string $b The divisor, must not be zero.
|
||||||
|
*
|
||||||
|
* @return string The remainder.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function divR(string $a, string $b): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quotient and remainder of the division of two numbers.
|
||||||
|
*
|
||||||
|
* @param string $a The dividend.
|
||||||
|
* @param string $b The divisor, must not be zero.
|
||||||
|
*
|
||||||
|
* @return array{string, string} An array containing the quotient and remainder.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function divQR(string $a, string $b): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exponentiates a number.
|
||||||
|
*
|
||||||
|
* @param string $a The base number.
|
||||||
|
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
|
||||||
|
*
|
||||||
|
* @return string The power.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function pow(string $a, int $e): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $b The modulus; must not be zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function mod(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->divR($this->add($this->divR($a, $b), $b), $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the modular multiplicative inverse of $x modulo $m.
|
||||||
|
*
|
||||||
|
* If $x has no multiplicative inverse mod m, this method must return null.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
|
||||||
|
*
|
||||||
|
* @param string $m The modulus; must not be negative or zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function modInverse(string $x, string $m): ?string
|
||||||
|
{
|
||||||
|
if ($m === '1') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$modVal = $x;
|
||||||
|
|
||||||
|
if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) {
|
||||||
|
$modVal = $this->mod($x, $m);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$g, $x] = $this->gcdExtended($modVal, $m);
|
||||||
|
|
||||||
|
if ($g !== '1') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mod($this->add($this->mod($x, $m), $m), $m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises a number into power with modulo.
|
||||||
|
*
|
||||||
|
* @param string $base The base number.
|
||||||
|
* @param string $exp The exponent; must be positive or zero.
|
||||||
|
* @param string $mod The modulus; must be strictly positive.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function modPow(string $base, string $exp, string $mod): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the greatest common divisor of the two numbers.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for GCD calculations.
|
||||||
|
*
|
||||||
|
* @return string The GCD, always positive, or zero if both arguments are zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function gcd(string $a, string $b): string
|
||||||
|
{
|
||||||
|
if ($a === '0') {
|
||||||
|
return $this->abs($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b === '0') {
|
||||||
|
return $this->abs($a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->gcd($b, $this->divR($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the least common multiple of the two numbers.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for LCM calculations.
|
||||||
|
*
|
||||||
|
* @return string The LCM, always positive, or zero if at least one argument is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function lcm(string $a, string $b): string
|
||||||
|
{
|
||||||
|
if ($a === '0' || $b === '0') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->divQ($this->abs($this->mul($a, $b)), $this->gcd($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the square root of the given number, rounded down.
|
||||||
|
*
|
||||||
|
* The result is the largest x such that x² ≤ n.
|
||||||
|
* The input MUST NOT be negative.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function sqrt(string $n): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a number from an arbitrary base.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for base conversion.
|
||||||
|
*
|
||||||
|
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base.
|
||||||
|
* @param int $base The base of the number, validated from 2 to 36.
|
||||||
|
*
|
||||||
|
* @return string The converted number, following the Calculator conventions.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function fromBase(string $number, int $base): string
|
||||||
|
{
|
||||||
|
return $this->fromArbitraryBase(strtolower($number), self::ALPHABET, $base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a number to an arbitrary base.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for base conversion.
|
||||||
|
*
|
||||||
|
* @param string $number The number to convert, following the Calculator conventions.
|
||||||
|
* @param int $base The base to convert to, validated from 2 to 36.
|
||||||
|
*
|
||||||
|
* @return string The converted number, lowercase.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function toBase(string $number, int $base): string
|
||||||
|
{
|
||||||
|
$negative = ($number[0] === '-');
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
$number = substr($number, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
return '-' . $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10.
|
||||||
|
*
|
||||||
|
* @param string $number The number to convert, validated as a non-empty string,
|
||||||
|
* containing only chars in the given alphabet/base.
|
||||||
|
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
|
||||||
|
* @param int $base The base of the number, validated from 2 to alphabet length.
|
||||||
|
*
|
||||||
|
* @return string The number in base 10, following the Calculator conventions.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function fromArbitraryBase(string $number, string $alphabet, int $base): string
|
||||||
|
{
|
||||||
|
// remove leading "zeros"
|
||||||
|
$number = ltrim($number, $alphabet[0]);
|
||||||
|
|
||||||
|
if ($number === '') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// optimize for "one"
|
||||||
|
if ($number === $alphabet[1]) {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = '0';
|
||||||
|
$power = '1';
|
||||||
|
|
||||||
|
$base = (string) $base;
|
||||||
|
|
||||||
|
for ($i = strlen($number) - 1; $i >= 0; $i--) {
|
||||||
|
$index = strpos($alphabet, $number[$i]);
|
||||||
|
|
||||||
|
if ($index !== 0) {
|
||||||
|
$result = $this->add(
|
||||||
|
$result,
|
||||||
|
($index === 1) ? $power : $this->mul($power, (string) $index),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($i !== 0) {
|
||||||
|
$power = $this->mul($power, $base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a non-negative number to an arbitrary base using a custom alphabet.
|
||||||
|
*
|
||||||
|
* @param string $number The number to convert, positive or zero, following the Calculator conventions.
|
||||||
|
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
|
||||||
|
* @param int $base The base to convert to, validated from 2 to alphabet length.
|
||||||
|
*
|
||||||
|
* @return string The converted number in the given alphabet.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function toArbitraryBase(string $number, string $alphabet, int $base): string
|
||||||
|
{
|
||||||
|
if ($number === '0') {
|
||||||
|
return $alphabet[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = (string) $base;
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
while ($number !== '0') {
|
||||||
|
[$number, $remainder] = $this->divQR($number, $base);
|
||||||
|
$remainder = (int) $remainder;
|
||||||
|
|
||||||
|
$result .= $alphabet[$remainder];
|
||||||
|
}
|
||||||
|
|
||||||
|
return strrev($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a rounded division.
|
||||||
|
*
|
||||||
|
* Rounding is performed when the remainder of the division is not zero.
|
||||||
|
*
|
||||||
|
* @param string $a The dividend.
|
||||||
|
* @param string $b The divisor, must not be zero.
|
||||||
|
* @param RoundingMode $roundingMode The rounding mode.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is provided but rounding is necessary.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function divRound(string $a, string $b, RoundingMode $roundingMode): string
|
||||||
|
{
|
||||||
|
[$quotient, $remainder] = $this->divQR($a, $b);
|
||||||
|
|
||||||
|
$hasDiscardedFraction = ($remainder !== '0');
|
||||||
|
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
|
||||||
|
|
||||||
|
$discardedFractionSign = function () use ($remainder, $b): int {
|
||||||
|
$r = $this->abs($this->mul($remainder, '2'));
|
||||||
|
$b = $this->abs($b);
|
||||||
|
|
||||||
|
return $this->cmp($r, $b);
|
||||||
|
};
|
||||||
|
|
||||||
|
$increment = false;
|
||||||
|
|
||||||
|
switch ($roundingMode) {
|
||||||
|
case RoundingMode::Unnecessary:
|
||||||
|
if ($hasDiscardedFraction) {
|
||||||
|
throw RoundingNecessaryException::roundingNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::Up:
|
||||||
|
$increment = $hasDiscardedFraction;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::Down:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::Ceiling:
|
||||||
|
$increment = $hasDiscardedFraction && $isPositiveOrZero;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::Floor:
|
||||||
|
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::HalfUp:
|
||||||
|
$increment = $discardedFractionSign() >= 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::HalfDown:
|
||||||
|
$increment = $discardedFractionSign() > 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::HalfCeiling:
|
||||||
|
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::HalfFloor:
|
||||||
|
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RoundingMode::HalfEven:
|
||||||
|
$lastDigit = (int) $quotient[-1];
|
||||||
|
$lastDigitIsEven = ($lastDigit % 2 === 0);
|
||||||
|
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($increment) {
|
||||||
|
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $quotient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates bitwise AND of two numbers.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for bitwise operations.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function and(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->bitwise('and', $a, $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates bitwise OR of two numbers.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for bitwise operations.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function or(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->bitwise('or', $a, $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates bitwise XOR of two numbers.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for bitwise operations.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function xor(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->bitwise('xor', $a, $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the sign & digits of the operands.
|
||||||
|
*
|
||||||
|
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function init(string $a, string $b): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$aNeg = ($a[0] === '-'),
|
||||||
|
$bNeg = ($b[0] === '-'),
|
||||||
|
|
||||||
|
$aNeg ? substr($a, 1) : $a,
|
||||||
|
$bNeg ? substr($b, 1) : $b,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{string, string, string} GCD, X, Y
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function gcdExtended(string $a, string $b): array
|
||||||
|
{
|
||||||
|
if ($a === '0') {
|
||||||
|
return [$b, '0', '1'];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
|
||||||
|
|
||||||
|
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
|
||||||
|
$y = $x1;
|
||||||
|
|
||||||
|
return [$gcd, $x, $y];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a bitwise operation on a decimal number.
|
||||||
|
*
|
||||||
|
* @param 'and'|'or'|'xor' $operator The operator to use.
|
||||||
|
* @param string $a The left operand.
|
||||||
|
* @param string $b The right operand.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function bitwise(string $operator, string $a, string $b): string
|
||||||
|
{
|
||||||
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
|
$aBin = $this->toBinary($aDig);
|
||||||
|
$bBin = $this->toBinary($bDig);
|
||||||
|
|
||||||
|
$aLen = strlen($aBin);
|
||||||
|
$bLen = strlen($bBin);
|
||||||
|
|
||||||
|
if ($aLen > $bLen) {
|
||||||
|
$bBin = str_repeat("\x00", $aLen - $bLen) . $bBin;
|
||||||
|
} elseif ($bLen > $aLen) {
|
||||||
|
$aBin = str_repeat("\x00", $bLen - $aLen) . $aBin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($aNeg) {
|
||||||
|
$aBin = $this->twosComplement($aBin);
|
||||||
|
}
|
||||||
|
if ($bNeg) {
|
||||||
|
$bBin = $this->twosComplement($bBin);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = match ($operator) {
|
||||||
|
'and' => $aBin & $bBin,
|
||||||
|
'or' => $aBin | $bBin,
|
||||||
|
'xor' => $aBin ^ $bBin,
|
||||||
|
};
|
||||||
|
|
||||||
|
$negative = match ($operator) {
|
||||||
|
'and' => $aNeg and $bNeg,
|
||||||
|
'or' => $aNeg or $bNeg,
|
||||||
|
'xor' => $aNeg xor $bNeg,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
$value = $this->twosComplement($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->toDecimal($value);
|
||||||
|
|
||||||
|
return $negative ? $this->neg($result) : $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $number A positive, binary number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function twosComplement(string $number): string
|
||||||
|
{
|
||||||
|
$xor = str_repeat("\xff", strlen($number));
|
||||||
|
|
||||||
|
$number ^= $xor;
|
||||||
|
|
||||||
|
for ($i = strlen($number) - 1; $i >= 0; $i--) {
|
||||||
|
$byte = ord($number[$i]);
|
||||||
|
|
||||||
|
if (++$byte !== 256) {
|
||||||
|
$number[$i] = chr($byte);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$number[$i] = "\x00";
|
||||||
|
|
||||||
|
if ($i === 0) {
|
||||||
|
$number = "\x01" . $number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a decimal number to a binary string.
|
||||||
|
*
|
||||||
|
* @param string $number The number to convert, positive or zero, only digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function toBinary(string $number): string
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
while ($number !== '0') {
|
||||||
|
[$number, $remainder] = $this->divQR($number, '256');
|
||||||
|
$result .= chr((int) $remainder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strrev($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the positive decimal representation of a binary number.
|
||||||
|
*
|
||||||
|
* @param string $bytes The bytes representing the number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function toDecimal(string $bytes): string
|
||||||
|
{
|
||||||
|
$result = '0';
|
||||||
|
$power = '1';
|
||||||
|
|
||||||
|
for ($i = strlen($bytes) - 1; $i >= 0; $i--) {
|
||||||
|
$index = ord($bytes[$i]);
|
||||||
|
|
||||||
|
if ($index !== 0) {
|
||||||
|
$result = $this->add(
|
||||||
|
$result,
|
||||||
|
($index === 1) ? $power : $this->mul($power, (string) $index),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($i !== 0) {
|
||||||
|
$power = $this->mul($power, '256');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Internal\Calculator;
|
||||||
|
|
||||||
|
use Brick\Math\Internal\Calculator;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
use function bcadd;
|
||||||
|
use function bcdiv;
|
||||||
|
use function bcmod;
|
||||||
|
use function bcmul;
|
||||||
|
use function bcpow;
|
||||||
|
use function bcpowmod;
|
||||||
|
use function bcsqrt;
|
||||||
|
use function bcsub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculator implementation built around the bcmath library.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class BcMathCalculator extends Calculator
|
||||||
|
{
|
||||||
|
#[Override]
|
||||||
|
public function add(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return bcadd($a, $b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function sub(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return bcsub($a, $b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function mul(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return bcmul($a, $b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divQ(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return bcdiv($a, $b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divR(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return bcmod($a, $b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divQR(string $a, string $b): array
|
||||||
|
{
|
||||||
|
$q = bcdiv($a, $b, 0);
|
||||||
|
$r = bcmod($a, $b, 0);
|
||||||
|
|
||||||
|
return [$q, $r];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function pow(string $a, int $e): string
|
||||||
|
{
|
||||||
|
return bcpow($a, (string) $e, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function modPow(string $base, string $exp, string $mod): string
|
||||||
|
{
|
||||||
|
// normalize to Euclidean representative so modPow() stays consistent with mod()
|
||||||
|
$base = $this->mod($base, $mod);
|
||||||
|
|
||||||
|
return bcpowmod($base, $exp, $mod, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function sqrt(string $n): string
|
||||||
|
{
|
||||||
|
return bcsqrt($n, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Internal\Calculator;
|
||||||
|
|
||||||
|
use Brick\Math\Internal\Calculator;
|
||||||
|
use GMP;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
use function gmp_add;
|
||||||
|
use function gmp_and;
|
||||||
|
use function gmp_div_q;
|
||||||
|
use function gmp_div_qr;
|
||||||
|
use function gmp_div_r;
|
||||||
|
use function gmp_gcd;
|
||||||
|
use function gmp_init;
|
||||||
|
use function gmp_invert;
|
||||||
|
use function gmp_lcm;
|
||||||
|
use function gmp_mul;
|
||||||
|
use function gmp_or;
|
||||||
|
use function gmp_pow;
|
||||||
|
use function gmp_powm;
|
||||||
|
use function gmp_sqrt;
|
||||||
|
use function gmp_strval;
|
||||||
|
use function gmp_sub;
|
||||||
|
use function gmp_xor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculator implementation built around the GMP library.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class GmpCalculator extends Calculator
|
||||||
|
{
|
||||||
|
#[Override]
|
||||||
|
public function add(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_add($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function sub(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_sub($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function mul(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_mul($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divQ(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_div_q($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divR(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_div_r($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divQR(string $a, string $b): array
|
||||||
|
{
|
||||||
|
[$q, $r] = gmp_div_qr($a, $b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var GMP $q
|
||||||
|
* @var GMP $r
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
gmp_strval($q),
|
||||||
|
gmp_strval($r),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function pow(string $a, int $e): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_pow($a, $e));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function modInverse(string $x, string $m): ?string
|
||||||
|
{
|
||||||
|
$result = gmp_invert($x, $m);
|
||||||
|
|
||||||
|
if ($result === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gmp_strval($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function modPow(string $base, string $exp, string $mod): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_powm($base, $exp, $mod));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function gcd(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_gcd($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function lcm(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_lcm($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function fromBase(string $number, int $base): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_init($number, $base));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function toBase(string $number, int $base): string
|
||||||
|
{
|
||||||
|
return gmp_strval($number, $base);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function and(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_and($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function or(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_or($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function xor(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_xor($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function sqrt(string $n): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_sqrt($n));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,616 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Internal\Calculator;
|
||||||
|
|
||||||
|
use Brick\Math\Internal\Calculator;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
use function assert;
|
||||||
|
use function in_array;
|
||||||
|
use function intdiv;
|
||||||
|
use function is_int;
|
||||||
|
use function ltrim;
|
||||||
|
use function str_pad;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strcmp;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
|
||||||
|
use const PHP_INT_SIZE;
|
||||||
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculator implementation using only native PHP code.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class NativeCalculator extends Calculator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
|
||||||
|
* For multiplication, this represents the max sum of the lengths of both operands.
|
||||||
|
*
|
||||||
|
* In addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
|
||||||
|
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
|
||||||
|
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
|
||||||
|
*/
|
||||||
|
private int $maxDigits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->maxDigits = match (PHP_INT_SIZE) {
|
||||||
|
4 => 9,
|
||||||
|
8 => 18,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function add(string $a, string $b): string
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var numeric-string $a
|
||||||
|
* @var numeric-string $b
|
||||||
|
*/
|
||||||
|
$result = $a + $b;
|
||||||
|
|
||||||
|
if (is_int($result)) {
|
||||||
|
return (string) $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a === '0') {
|
||||||
|
return $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b === '0') {
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
|
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
|
||||||
|
|
||||||
|
if ($aNeg) {
|
||||||
|
$result = $this->neg($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function sub(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->add($a, $this->neg($b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function mul(string $a, string $b): string
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var numeric-string $a
|
||||||
|
* @var numeric-string $b
|
||||||
|
*/
|
||||||
|
$result = $a * $b;
|
||||||
|
|
||||||
|
if (is_int($result)) {
|
||||||
|
return (string) $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a === '0' || $b === '0') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a === '1') {
|
||||||
|
return $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b === '1') {
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a === '-1') {
|
||||||
|
return $this->neg($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b === '-1') {
|
||||||
|
return $this->neg($a);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
|
$result = $this->doMul($aDig, $bDig);
|
||||||
|
|
||||||
|
if ($aNeg !== $bNeg) {
|
||||||
|
$result = $this->neg($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divQ(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->divQR($a, $b)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divR(string $a, string $b): string
|
||||||
|
{
|
||||||
|
return $this->divQR($a, $b)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function divQR(string $a, string $b): array
|
||||||
|
{
|
||||||
|
if ($a === '0') {
|
||||||
|
return ['0', '0'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a === $b) {
|
||||||
|
return ['1', '0'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b === '1') {
|
||||||
|
return [$a, '0'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($b === '-1') {
|
||||||
|
return [$this->neg($a), '0'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var numeric-string $a */
|
||||||
|
$na = $a * 1; // cast to number
|
||||||
|
|
||||||
|
if (is_int($na)) {
|
||||||
|
/** @var numeric-string $b */
|
||||||
|
$nb = $b * 1;
|
||||||
|
|
||||||
|
if (is_int($nb)) {
|
||||||
|
// the only division that may overflow is PHP_INT_MIN / -1,
|
||||||
|
// which cannot happen here as we've already handled a divisor of -1 above.
|
||||||
|
$q = intdiv($na, $nb);
|
||||||
|
$r = $na % $nb;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(string) $q,
|
||||||
|
(string) $r,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
|
[$q, $r] = $this->doDiv($aDig, $bDig);
|
||||||
|
|
||||||
|
if ($aNeg !== $bNeg) {
|
||||||
|
$q = $this->neg($q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($aNeg) {
|
||||||
|
$r = $this->neg($r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$q, $r];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function pow(string $a, int $e): string
|
||||||
|
{
|
||||||
|
if ($e === 0) {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e === 1) {
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
|
||||||
|
$odd = $e % 2;
|
||||||
|
$e -= $odd;
|
||||||
|
|
||||||
|
$aa = $this->mul($a, $a);
|
||||||
|
|
||||||
|
$result = $this->pow($aa, $e / 2);
|
||||||
|
|
||||||
|
if ($odd === 1) {
|
||||||
|
$result = $this->mul($result, $a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/.
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
public function modPow(string $base, string $exp, string $mod): string
|
||||||
|
{
|
||||||
|
// normalize to Euclidean representative so modPow() stays consistent with mod()
|
||||||
|
$base = $this->mod($base, $mod);
|
||||||
|
|
||||||
|
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
|
||||||
|
if ($exp === '0' && $mod === '1') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $base;
|
||||||
|
|
||||||
|
$res = '1';
|
||||||
|
|
||||||
|
// numbers are positive, so we can use remainder instead of modulo
|
||||||
|
$x = $this->divR($x, $mod);
|
||||||
|
|
||||||
|
while ($exp !== '0') {
|
||||||
|
if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd
|
||||||
|
$res = $this->divR($this->mul($res, $x), $mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
$exp = $this->divQ($exp, '2');
|
||||||
|
$x = $this->divR($this->mul($x, $x), $mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html.
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
public function sqrt(string $n): string
|
||||||
|
{
|
||||||
|
if ($n === '0') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// initial approximation
|
||||||
|
$x = str_repeat('9', intdiv(strlen($n), 2) ?: 1);
|
||||||
|
|
||||||
|
$decreased = false;
|
||||||
|
|
||||||
|
for (; ;) {
|
||||||
|
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
|
||||||
|
|
||||||
|
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decreased = $this->cmp($nx, $x) < 0;
|
||||||
|
$x = $nx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the addition of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function doAdd(string $a, string $b): string
|
||||||
|
{
|
||||||
|
[$a, $b, $length] = $this->pad($a, $b);
|
||||||
|
|
||||||
|
$carry = 0;
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
for ($i = $length - $this->maxDigits; ; $i -= $this->maxDigits) {
|
||||||
|
$blockLength = $this->maxDigits;
|
||||||
|
|
||||||
|
if ($i < 0) {
|
||||||
|
$blockLength += $i;
|
||||||
|
$i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var numeric-string $blockA */
|
||||||
|
$blockA = substr($a, $i, $blockLength);
|
||||||
|
|
||||||
|
/** @var numeric-string $blockB */
|
||||||
|
$blockB = substr($b, $i, $blockLength);
|
||||||
|
|
||||||
|
$sum = (string) ($blockA + $blockB + $carry);
|
||||||
|
$sumLength = strlen($sum);
|
||||||
|
|
||||||
|
if ($sumLength > $blockLength) {
|
||||||
|
$sum = substr($sum, 1);
|
||||||
|
$carry = 1;
|
||||||
|
} else {
|
||||||
|
if ($sumLength < $blockLength) {
|
||||||
|
$sum = str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||||
|
}
|
||||||
|
$carry = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $sum . $result;
|
||||||
|
|
||||||
|
if ($i === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($carry === 1) {
|
||||||
|
$result = '1' . $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the subtraction of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function doSub(string $a, string $b): string
|
||||||
|
{
|
||||||
|
if ($a === $b) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we always subtract to a positive result: biggest minus smallest.
|
||||||
|
$cmp = $this->doCmp($a, $b);
|
||||||
|
|
||||||
|
$invert = ($cmp === -1);
|
||||||
|
|
||||||
|
if ($invert) {
|
||||||
|
$c = $a;
|
||||||
|
$a = $b;
|
||||||
|
$b = $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$a, $b, $length] = $this->pad($a, $b);
|
||||||
|
|
||||||
|
$carry = 0;
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
$complement = 10 ** $this->maxDigits;
|
||||||
|
|
||||||
|
for ($i = $length - $this->maxDigits; ; $i -= $this->maxDigits) {
|
||||||
|
$blockLength = $this->maxDigits;
|
||||||
|
|
||||||
|
if ($i < 0) {
|
||||||
|
$blockLength += $i;
|
||||||
|
$i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var numeric-string $blockA */
|
||||||
|
$blockA = substr($a, $i, $blockLength);
|
||||||
|
|
||||||
|
/** @var numeric-string $blockB */
|
||||||
|
$blockB = substr($b, $i, $blockLength);
|
||||||
|
|
||||||
|
$sum = $blockA - $blockB - $carry;
|
||||||
|
|
||||||
|
if ($sum < 0) {
|
||||||
|
$sum += $complement;
|
||||||
|
$carry = 1;
|
||||||
|
} else {
|
||||||
|
$carry = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = (string) $sum;
|
||||||
|
$sumLength = strlen($sum);
|
||||||
|
|
||||||
|
if ($sumLength < $blockLength) {
|
||||||
|
$sum = str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $sum . $result;
|
||||||
|
|
||||||
|
if ($i === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carry cannot be 1 when the loop ends, as a > b
|
||||||
|
assert($carry === 0);
|
||||||
|
|
||||||
|
$result = ltrim($result, '0');
|
||||||
|
|
||||||
|
if ($invert) {
|
||||||
|
$result = $this->neg($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the multiplication of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function doMul(string $a, string $b): string
|
||||||
|
{
|
||||||
|
$x = strlen($a);
|
||||||
|
$y = strlen($b);
|
||||||
|
|
||||||
|
$maxDigits = intdiv($this->maxDigits, 2);
|
||||||
|
$complement = 10 ** $maxDigits;
|
||||||
|
|
||||||
|
$result = '0';
|
||||||
|
|
||||||
|
for ($i = $x - $maxDigits; ; $i -= $maxDigits) {
|
||||||
|
$blockALength = $maxDigits;
|
||||||
|
|
||||||
|
if ($i < 0) {
|
||||||
|
$blockALength += $i;
|
||||||
|
$i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockA = (int) substr($a, $i, $blockALength);
|
||||||
|
|
||||||
|
$line = '';
|
||||||
|
$carry = 0;
|
||||||
|
|
||||||
|
for ($j = $y - $maxDigits; ; $j -= $maxDigits) {
|
||||||
|
$blockBLength = $maxDigits;
|
||||||
|
|
||||||
|
if ($j < 0) {
|
||||||
|
$blockBLength += $j;
|
||||||
|
$j = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockB = (int) substr($b, $j, $blockBLength);
|
||||||
|
|
||||||
|
$mul = $blockA * $blockB + $carry;
|
||||||
|
$value = $mul % $complement;
|
||||||
|
$carry = ($mul - $value) / $complement;
|
||||||
|
|
||||||
|
$value = (string) $value;
|
||||||
|
$value = str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
$line = $value . $line;
|
||||||
|
|
||||||
|
if ($j === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($carry !== 0) {
|
||||||
|
$line = $carry . $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = ltrim($line, '0');
|
||||||
|
|
||||||
|
if ($line !== '') {
|
||||||
|
$line .= str_repeat('0', $x - $blockALength - $i);
|
||||||
|
$result = $this->add($result, $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($i === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the division of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @return string[] The quotient and remainder.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function doDiv(string $a, string $b): array
|
||||||
|
{
|
||||||
|
$cmp = $this->doCmp($a, $b);
|
||||||
|
|
||||||
|
if ($cmp === -1) {
|
||||||
|
return ['0', $a];
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = strlen($a);
|
||||||
|
$y = strlen($b);
|
||||||
|
|
||||||
|
// we now know that a >= b && x >= y
|
||||||
|
|
||||||
|
$q = '0'; // quotient
|
||||||
|
$r = $a; // remainder
|
||||||
|
$z = $y; // focus length, always $y or $y+1
|
||||||
|
|
||||||
|
/** @var numeric-string $b */
|
||||||
|
$nb = $b * 1; // cast to number
|
||||||
|
// performance optimization in cases where the remainder will never cause int overflow
|
||||||
|
if (is_int(($nb - 1) * 10 + 9)) {
|
||||||
|
$r = (int) substr($a, 0, $z - 1);
|
||||||
|
|
||||||
|
for ($i = $z - 1; $i < $x; $i++) {
|
||||||
|
$n = $r * 10 + (int) $a[$i];
|
||||||
|
/** @var int $nb */
|
||||||
|
$q .= intdiv($n, $nb);
|
||||||
|
$r = $n % $nb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ltrim($q, '0') ?: '0', (string) $r];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; ;) {
|
||||||
|
$focus = substr($a, 0, $z);
|
||||||
|
|
||||||
|
$cmp = $this->doCmp($focus, $b);
|
||||||
|
|
||||||
|
if ($cmp === -1) {
|
||||||
|
if ($z === $x) { // remainder < dividend
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$z++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$zeros = str_repeat('0', $x - $z);
|
||||||
|
|
||||||
|
$q = $this->add($q, '1' . $zeros);
|
||||||
|
$a = $this->sub($a, $b . $zeros);
|
||||||
|
|
||||||
|
$r = $a;
|
||||||
|
|
||||||
|
if ($r === '0') { // remainder == 0
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = strlen($a);
|
||||||
|
|
||||||
|
if ($x < $y) { // remainder < dividend
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$z = $y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$q, $r];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two non-signed large numbers.
|
||||||
|
*
|
||||||
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function doCmp(string $a, string $b): int
|
||||||
|
{
|
||||||
|
$x = strlen($a);
|
||||||
|
$y = strlen($b);
|
||||||
|
|
||||||
|
$cmp = $x <=> $y;
|
||||||
|
|
||||||
|
if ($cmp !== 0) {
|
||||||
|
return $cmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcmp($a, $b) <=> 0; // enforce -1|0|1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
|
||||||
|
*
|
||||||
|
* The numbers must only consist of digits, without leading minus sign.
|
||||||
|
*
|
||||||
|
* @return array{string, string, int}
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function pad(string $a, string $b): array
|
||||||
|
{
|
||||||
|
$x = strlen($a);
|
||||||
|
$y = strlen($b);
|
||||||
|
|
||||||
|
if ($x > $y) {
|
||||||
|
$b = str_repeat('0', $x - $y) . $b;
|
||||||
|
|
||||||
|
return [$a, $b, $x];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($x < $y) {
|
||||||
|
$a = str_repeat('0', $y - $x) . $a;
|
||||||
|
|
||||||
|
return [$a, $b, $y];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$a, $b, $x];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Internal;
|
||||||
|
|
||||||
|
use function extension_loaded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the current Calculator instance used by BigNumber classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class CalculatorRegistry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Calculator instance in use.
|
||||||
|
*/
|
||||||
|
private static ?Calculator $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Calculator instance to use.
|
||||||
|
*
|
||||||
|
* An instance is typically set only in unit tests: autodetect is usually the best option.
|
||||||
|
*
|
||||||
|
* @param Calculator|null $calculator The calculator instance, or null to revert to autodetect.
|
||||||
|
*/
|
||||||
|
final public static function set(?Calculator $calculator): void
|
||||||
|
{
|
||||||
|
self::$instance = $calculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Calculator instance to use.
|
||||||
|
*
|
||||||
|
* If none has been explicitly set, the fastest available implementation will be returned.
|
||||||
|
*
|
||||||
|
* Note: even though this method is not technically pure, it is considered pure when used in a normal context, when
|
||||||
|
* only relying on autodetect.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function get(): Calculator
|
||||||
|
{
|
||||||
|
/** @phpstan-ignore impure.staticPropertyAccess */
|
||||||
|
if (self::$instance === null) {
|
||||||
|
/** @phpstan-ignore impure.propertyAssign */
|
||||||
|
self::$instance = self::detect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @phpstan-ignore impure.staticPropertyAccess */
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fastest available Calculator implementation.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private static function detect(): Calculator
|
||||||
|
{
|
||||||
|
if (extension_loaded('gmp')) {
|
||||||
|
return new Calculator\GmpCalculator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension_loaded('bcmath')) {
|
||||||
|
return new Calculator\BcMathCalculator();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Calculator\NativeCalculator();
|
||||||
|
}
|
||||||
|
}
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies rounding behavior by defining how discarded digits affect the returned result when an exact value cannot
|
||||||
|
* be represented at the requested scale.
|
||||||
|
*/
|
||||||
|
enum RoundingMode
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Asserts that the requested operation has an exact result, hence no rounding is necessary.
|
||||||
|
*
|
||||||
|
* If this rounding mode is specified on an operation that yields a result that
|
||||||
|
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
|
||||||
|
*/
|
||||||
|
case Unnecessary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds away from zero.
|
||||||
|
*
|
||||||
|
* Always increments the digit prior to a nonzero discarded fraction.
|
||||||
|
* Note that this rounding mode never decreases the magnitude of the calculated value.
|
||||||
|
*/
|
||||||
|
case Up;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards zero.
|
||||||
|
*
|
||||||
|
* Never increments the digit prior to a discarded fraction (i.e., truncates).
|
||||||
|
* Note that this rounding mode never increases the magnitude of the calculated value.
|
||||||
|
*/
|
||||||
|
case Down;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards positive infinity.
|
||||||
|
*
|
||||||
|
* If the result is positive, behaves as for Up; if negative, behaves as for Down.
|
||||||
|
* Note that this rounding mode never decreases the calculated value.
|
||||||
|
*/
|
||||||
|
case Ceiling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards negative infinity.
|
||||||
|
*
|
||||||
|
* If the result is positive, behaves as for Down; if negative, behaves as for Up.
|
||||||
|
* Note that this rounding mode never increases the calculated value.
|
||||||
|
*/
|
||||||
|
case Floor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
|
||||||
|
*
|
||||||
|
* Behaves as for Up if the discarded fraction is >= 0.5; otherwise, behaves as for Down.
|
||||||
|
* Note that this is the rounding mode commonly taught at school.
|
||||||
|
*/
|
||||||
|
case HalfUp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
|
||||||
|
*
|
||||||
|
* Behaves as for Up if the discarded fraction is > 0.5; otherwise, behaves as for Down.
|
||||||
|
*/
|
||||||
|
case HalfDown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
|
||||||
|
*
|
||||||
|
* If the result is positive, behaves as for HalfUp; if negative, behaves as for HalfDown.
|
||||||
|
*/
|
||||||
|
case HalfCeiling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
|
||||||
|
*
|
||||||
|
* If the result is positive, behaves as for HalfDown; if negative, behaves as for HalfUp.
|
||||||
|
*/
|
||||||
|
case HalfFloor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
|
||||||
|
*
|
||||||
|
* Behaves as for HalfUp if the digit to the left of the discarded fraction is odd;
|
||||||
|
* behaves as for HalfDown if it's even.
|
||||||
|
*
|
||||||
|
* Note that this is the rounding mode that statistically minimizes
|
||||||
|
* cumulative error when applied repeatedly over a sequence of calculations.
|
||||||
|
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
|
||||||
|
*/
|
||||||
|
case HalfEven;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Unnecessary instead.
|
||||||
|
*/
|
||||||
|
public const UNNECESSARY = self::Unnecessary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Up instead.
|
||||||
|
*/
|
||||||
|
public const UP = self::Up;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Down instead.
|
||||||
|
*/
|
||||||
|
public const DOWN = self::Down;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Ceiling instead.
|
||||||
|
*/
|
||||||
|
public const CEILING = self::Ceiling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Floor instead.
|
||||||
|
*/
|
||||||
|
public const FLOOR = self::Floor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfUp instead.
|
||||||
|
*/
|
||||||
|
public const HALF_UP = self::HalfUp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfDown instead.
|
||||||
|
*/
|
||||||
|
public const HALF_DOWN = self::HalfDown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfCeiling instead.
|
||||||
|
*/
|
||||||
|
public const HALF_CEILING = self::HalfCeiling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfFloor instead.
|
||||||
|
*/
|
||||||
|
public const HALF_FLOOR = self::HalfFloor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfEven instead.
|
||||||
|
*/
|
||||||
|
public const HALF_EVEN = self::HalfEven;
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Carbon
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# carbonphp/carbon-doctrine-types
|
||||||
|
|
||||||
|
Types to use Carbon in Doctrine
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/)
|
||||||
|
|
||||||
|
This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine)
|
||||||
|
from `nestbot/carbon` package.
|
||||||
|
|
||||||
|
Externalization allows to better deal with different versions of dbal. With
|
||||||
|
version 4.0 of dbal, it no longer sustainable to be compatible with all version
|
||||||
|
using a single code.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user