Download the PHP package pinoox/pinion without Composer
On this page you can find all versions of the php package pinoox/pinion. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download pinoox/pinion
More information about pinoox/pinion
Files in pinoox/pinion
Informations about the package pinion
Pinion — Resumable Upload Protocol for PHP
Pinion (pinoox/pinion) lets users upload large files in small parts — even when PHP upload_max_filesize / post_max_size are low.
Protocol id: pinion · protocol version: 2 · PHP 8.2+
| Layer | Registry | Package | Release |
|---|---|---|---|
| Server (PHP) | Packagist | pinoox/pinion |
1.1.0 |
| Browser (JS) | npm | @pinooxhq/pinion-client |
1.2.0 |
Protocol version (
2) is the wire format (protocol_versionin API responses). Release columns are separate semver for the PHP and npm packages.
Quick start
1 — Install
2 — Server: three HTTP steps
3 — Browser: one function
That is the whole idea: init → upload parts → complete. The client does the loop for you.
Table of contents
- Quick start
- What is Pinion
- How it works
- Install
- Level 1 — Simple
- Browser: upload one file
- Server: wire HTTP routes
- Routing & baseURL
- Level 2 — Practical patterns
- Reusable uploader
- Progress, retry, parallel
- Small files vs large files
- Auth headers & CORS
- Level 3 — Framework integration
- Plain PHP
- Pinoox
- Laravel
- Level 4 — Advanced
- Programmatic API (no HTTP)
- Resume & fingerprint
- Checksums & integrity
- Configuration reference
- Storage strategies
- Custom destinations
- Maintenance & CLI
- HTTP protocol (full reference)
- Browser client (full reference)
- PHP API surface
- Troubleshooting
- Package structure
- License
What is Pinion
The problem
On shared hosting or default PHP configs you often hit limits like:
A 500 MB video cannot be sent in one multipart/form-data request. Pinion splits the file into parts (default 5 MB), uploads them one by one, and assembles the final file on disk.
What Pinion is — and is not
| Pinion is | Pinion is not |
|---|---|
| A resumable chunked upload protocol | Object storage (S3, MinIO) |
| A PHP library + JS client | A CDN or media pipeline |
A stable HTTP contract (pinion v2) |
A single /upload one-shot endpoint |
When to use it
| Scenario | Why Pinion |
|---|---|
| Shared hosting (20 MB cap) | Send 5 MB parts instead of one huge POST |
| Slow or mobile networks | Resume after disconnect via fingerprint |
| Video / archive uploads | Hundreds of MB or GB without raising php.ini limits |
| Admin panels & CMS | Progress bar with parallel parts |
| API file intake | Same contract across PHP stacks |
| Integrity-sensitive files | SHA-256 per part (chunk_hash) + optional whole-file hash |
How it works
Key concepts:
| Concept | Role |
|---|---|
upload_id |
Server-side session UUID |
fingerprint |
Client key name:size:lastModified:type — resume same file |
chunk_size |
Bytes per part (negotiated at init) |
missing_indexes |
Which parts still need uploading |
chunk_hash |
SHA-256 of each part — verified if verify_chunks is on |
destination |
Logical folder on server (uploads/videos) |
Install
PHP — Packagist
Current release: 1.1.0 (pinoox/pinion)
Pinoox monorepo (local path):
JavaScript — npm
Current release: 1.2.0 (@pinooxhq/pinion-client)
Without npm (vendor copy)
Level 1 — Simple
Browser: upload one file
The fastest API — no setup object, no Axios:
unwrapPreset: 'pinoox' unwraps responses shaped like { data: { … } } (Pinoox / Laravel envelope). Use 'flat' if your API returns the payload directly.
Server: wire HTTP routes
Pinion needs five endpoints under one prefix. You choose the prefix; the client appends /init, /upload, etc.
Example prefix: /api/v1/upload
| Method | Route | Handler |
|---|---|---|
POST |
/api/v1/upload/init |
$handler->init($input) |
POST |
/api/v1/upload/upload |
$handler->upload($input, $chunkFile) |
POST |
/api/v1/upload/complete |
$handler->complete($input) |
GET |
/api/v1/upload/status/{id} |
$handler->status($id) |
POST |
/api/v1/upload/abort/{id} |
$handler->abort($id) |
Minimal PHP router (no framework):
Return JSON from HttpHandler as-is, or map success / data / error to your framework response helper.
Routing & baseURL
baseURL in the JS client is the prefix only — not a single upload URL.
| Your routes | baseURL in client |
|---|---|
/api/v1/upload/init, /upload, … |
'/api/v1/upload' |
/api/pinion/init, … |
'/api/pinion' |
/app/pinion/init, … (Pinoox manager) |
'/app/pinion' |
The client calls:
If you only have one endpoint POST /api/v1/upload for a regular single-file upload, that is not Pinion — use FormData + fetch instead.
Level 2 — Practical patterns
Reusable uploader
Create one client, upload many files:
Progress, retry, parallel
With Axios (optional) you also get raw per-chunk upload events:
Small files vs large files
Use auto to skip Pinion for files below a threshold (default 8 MB):
Auth headers & CORS
Pass headers once on the client — they apply to every step (init, upload, complete):
On the server, ensure CORS allows POST + GET on all five routes if the frontend is on another origin.
Cancel an upload
Batch upload
Level 3 — Framework integration
Plain PHP
HTTP handler — recommended for web apps:
Fluent builder — for scripts and jobs:
Pinoox (HMVC)
Inside a Pinoox project use Pinoox\Portal\Pinion — config, temp storage, Flysystem/S3, and pinx_file integration are wired in pincore.
| Piece | Path | Role |
|---|---|---|
| Portal | Pinoox\Portal\Pinion |
App-facing API |
| Bridge | pincore/Component/Pinion/ |
ProtocolManager, StorageCompletion, HttpHandler |
| Config | pincore/config/pinion.config.php |
Chunk TTL, PINION_MODE, storage defaults |
| Trait | PinionUploadActions |
Drop-in controller actions per app |
| CLI | php pinoox pinion:list |
Ops on temp sessions |
Storage modes (pinion.config.php → defaults.mode):
| Mode | Behaviour |
|---|---|
auto |
Use Flysystem (Portal\File) when app filesystem.disk is not local (e.g. S3) |
storage |
Always publish through managed file storage + optional FileModel row |
local |
Assemble to project path via path() (legacy / manual folders) |
Chunks always stage under storage/pinion. On complete, managed mode streams the assembled file to the app disk (local or S3) via UploadBuilder.
App controller (HMVC) — use the trait:
Local-only upload (no Flysystem — e.g. manager .pinx packages):
Browser client — npm @pinooxhq/pinion-client:
On complete with managed storage, the API returns file_id, url, and thumb (when applicable).
Routes (per app under apps/{package}/routes/):
→ client baseURL: '/app/pinion'
Template: pincore/config/pinion.routes.template.php
Config: pincore/config/pinion.config.php · temp: storage/pinion (pinion_uploads)
CLI:
Laravel
Laravel 10+ — auto-discovery via PinionServiceProvider.
1. Publish config
2. Routes (routes/api.php)
3. Controller
4. Facade (server-side jobs)
Level 4 — Advanced
Programmatic API (no HTTP)
Use Manager directly when chunks arrive from CLI, queue workers, or non-HTTP sources:
Low-level browser control (manual steps):
Resume & fingerprint
The client builds a fingerprint from file metadata:
On init, if the same fingerprint already has a pending session, the server returns it with resumed: true and missing_indexes — only missing parts are uploaded.
The JS client caches upload_id in localStorage (key: pinion_sessions) until complete succeeds.
Force resume explicitly:
Check stored session:
Checksums & integrity
| Field | When | Purpose |
|---|---|---|
chunk_hash |
Each POST /upload |
SHA-256 of that part — verified when verify_chunks: true |
file_hash |
POST /init or /complete |
Optional whole-file hash — verified when verify_file_hash: true |
Client sends chunk_hash automatically. To send whole-file hash on complete:
Configuration reference
Pass to Pinion::configure() or copy config/pinion.php.
| Key | Default | Description |
|---|---|---|
protocol |
pinion |
Protocol identifier (read-only in responses) |
protocol_version |
2 |
Protocol version |
chunk_size |
5242880 (5 MB) |
Default part size |
min_chunk_size |
1048576 (1 MB) |
Lower clamp |
max_chunk_size |
10485760 (10 MB) |
Upper clamp |
ttl |
86400 |
Session lifetime (seconds) |
max_file_size |
2147483648 (2 GB) |
Max declared file size |
storage_path |
/tmp/pinion |
Temp workspace for in-progress uploads |
storage_strategy |
parts |
parts or sparse |
verify_chunks |
true |
Require matching chunk_hash |
verify_file_hash |
false |
Require file_hash on complete |
Laravel .env:
Per-request defaults via HttpHandler:
Storage strategies
| Strategy | On disk | Best for |
|---|---|---|
parts |
{id}/parts/0.part, 1.part, … |
Parallel client uploads (default) |
sparse |
Single {id}/blob.part with offset writes |
Fewer files, sequential writes |
Temp files live under storage_path. On complete, the assembled file moves to the resolved destination. Expired pending sessions are cleaned via cleanExpired().
Custom destinations
By default NativePathResolver maps to('uploads/videos') relative to a base path. For multi-root apps implement PathResolverInterface:
Maintenance & CLI
Pinoox CLI:
HTTP protocol (full reference)
Endpoints
| Step | Method | Path | Body |
|---|---|---|---|
| Init / resume | POST |
{prefix}/init |
JSON |
| Upload part | POST |
{prefix}/upload |
multipart/form-data |
| Complete | POST |
{prefix}/complete |
JSON |
| Status | GET |
{prefix}/status/{upload_id} |
— |
| Abort | POST |
{prefix}/abort/{upload_id} |
JSON (optional) |
Init request
Init response
Same fingerprint on a pending session → resumed: true, missing_indexes only lists gaps.
Upload part request
multipart/form-data fields:
| Field | Required | Notes |
|---|---|---|
upload_id |
yes | Session UUID |
index |
yes | Zero-based part index |
chunk |
yes | Binary file field |
chunk_hash |
recommended | SHA-256 hex of chunk |
Do not set Content-Type: multipart/form-data manually — let the client / browser set the boundary.
Complete request
Error envelope
HttpHandler returns:
Common codes: PINION_INIT_FAILED, PINION_INVALID, PINION_CHUNK_HASH_MISMATCH, PINION_SESSION_EXPIRED, PINION_FILE_TOO_LARGE.
Browser client (full reference)
Published as @pinooxhq/pinion-client 1.2.0. Detailed npm guide: client/README.md
Usage levels
| Level | API | When |
|---|---|---|
| 1 — Fastest | uploadFile(file, options) |
Single button |
| 2 — Fluent | pinion({ baseURL }).for(file).upload() |
Reusable instance |
| 3 — Full | createPinionFetch(options) |
Batch, hooks, cancel |
| 4 — Manual | client.api.init() / uploadPart() / complete() |
Custom flow |
| 5 — Axios | pinion(axios, options) |
Per-chunk onUploadProgress |
| 6 — Custom | createPinionClient({ transport }) |
Own HTTP layer |
Transport
| Mode | How | client.transport.kind |
|---|---|---|
| fetch (default) | pinion({ baseURL }) |
'fetch' |
| Axios | pinion(axios, { baseURL }) |
'axios' |
Unwrap presets
| Preset | Unwraps to |
|---|---|
pinoox |
response.data.data |
laravel |
same envelope |
flat |
response.data |
raw |
full response object |
Set unwrapPreset: 'pinoox' when your PHP API wraps data in { data: … }.
Key exports
| Export | Role |
|---|---|
uploadFile(file, options) |
One-shot upload |
pinion(options) |
Fluent factory |
createPinionFetch(options) |
Explicit fetch client |
createPinionAxios(axios, options) |
{ axios, client } |
client.upload(file, opts) |
Full flow with parallel + retry |
client.uploadMany(files, opts) |
Batch |
client.api |
Low-level HTTP steps |
buildFingerprint(file) |
Resume key |
shouldUsePinion(file, threshold?) |
Skip Pinion for small files |
sha256Hex(blob) |
Part checksum |
PinionError |
Typed error with .code |
PHP API surface
| Class / method | Role |
|---|---|
Pinion::configure($config, $pathResolver?) |
Boot once |
Pinion::manager() |
Manager singleton |
Pinion::begin() |
Fluent Builder → init() |
Pinion::http($defaults) |
HttpHandler for HTTP apps |
Manager::init(...) |
Create or resume session |
Manager::receive(...) |
Store one part |
Manager::complete(...) |
Assemble final file |
Manager::status(...) |
Progress + missing_indexes |
Manager::abort(...) |
Cancel session |
Manager::list($status) |
List sessions |
Manager::cleanExpired() |
Purge expired pending |
HttpHandler |
HTTP → { success, data?, error? } |
Builder |
filename(), size(), to(), extensions(), fingerprint(), chunkSize(), init() |
Session |
missingIndexes(), progress() |
Result |
success, session, path, error, resumed |
PathResolverInterface |
Map logical destination → absolute path |
Pinoox (pincore): Pinoox\Portal\Pinion, Pinoox\Component\Pinion\HttpHandler, CLI commands.
Laravel: PinionServiceProvider, Pinion facade.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
PINION_INIT_FAILED in browser |
Wrong unwrapPreset |
Try pinoox, flat, or custom unwrap |
404 on /upload |
Wrong baseURL |
baseURL = prefix only, not full file URL |
| Multipart rejected | Manual Content-Type |
Let client set boundary on FormData |
| Upload stuck at N% | Missing parts | client.api.status(uploadId) → missing_indexes |
chunk_hash mismatch |
Corrupt part or wrong hash | Client computes SHA-256 per slice — don't transform binary |
| Session expired | ttl exceeded |
Re-init; same fingerprint may start fresh |
| Small file uses Pinion unnecessarily | No auto threshold |
uploadFile(file, { auto: true }) |
| CORS error | Preflight blocked | Allow POST/GET on all five routes + credentials if needed |
PINION_NO_FETCH (Node) |
No global fetch | Node 18+ or pass options.fetch |
Package structure
License
MIT — Pinoox