Download the PHP package bogdankharchenko/laravel-pdfmill without Composer
On this page you can find all versions of the php package bogdankharchenko/laravel-pdfmill. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download bogdankharchenko/laravel-pdfmill
More information about bogdankharchenko/laravel-pdfmill
Files in bogdankharchenko/laravel-pdfmill
Package laravel-pdfmill
Short Description Laravel client for the pdfmill API: merge, edit, fill, stamp and inspect PDFs on Cloudflare Workers. Generated from the API's OpenAPI spec.
License MIT
Informations about the package laravel-pdfmill
Laravel client for pdfmill
Merge, fill, stamp, split and read PDFs from Laravel, using your own deployment of pdfmill on Cloudflare Workers.
Every endpoint, operation, option and reply is generated from the API's OpenAPI spec as typed laravel-data classes. Your IDE and PHPStan know every field, and the docblocks repeat the API's own documentation.
Install
Add your deployment's address and API key to .env:
PDFMILL_TIMEOUT (seconds, default 120) and PDFMILL_CONNECT_TIMEOUT (default 10) are optional. To change the config file itself: php artisan vendor:publish --tag=pdfmill-config.
Use
Call the PdfMill facade, or inject BogdanKharchenko\PdfMill\Client.
Build a PDF step by step
create(), edit() and merge() return a PendingPdf. Each operation is a method on it, and nothing is sent until you end the chain:
| End with | You get |
|---|---|
->store(), or ->store('contracts/42.pdf') |
StoredPdf: the PDF saved in R2, with key, url, expiresAt, size and pageCount |
->put($url) |
UploadedPdf: the PDF uploaded to your URL instead of R2, with size and pageCount. See Upload straight to S3 |
->file() |
FileResponse: the PDF itself. ->file(storeAs: 'contracts/42.pdf') also saves it in R2 |
->download('contract.pdf') |
A response that makes the browser save it |
| Returning it from a route or controller | The PDF, shown in the browser |
Before that, ->filename('Contract 42.pdf') names the file, ->linkTtl(86400) sets how long store()'s link works, and ->withoutObjectStreams() writes a file that old tools can read.
Show a filled form in the browser
A FileResponse from ->file() can also be saved to a disk: ->file()->save('w9/ada.pdf', 's3').
Upload straight to S3
->file()->save() brings the whole PDF into your app's memory first. With ->put(), the Worker uploads it to your bucket itself. Spread in a presigned upload URL:
put() takes a URL and the headers it was signed with, as temporaryUploadUrl() returns them. It needs pdfmill 0.4 or later. An older deployment would keep the PDF in R2 instead, so put() throws an UnexpectedValueException rather than report an upload that didn't happen.
Merge uploads with a stored PDF
Add steps only sometimes
Read a PDF
Sources
Wherever the API takes a file, pass a string (an R2 key, or an http(s) URL) or a source object:
| Source | |
|---|---|
Source::key('templates/w9.pdf') |
A file in your R2 bucket |
Source::url($url, headers: ['authorization' => 'Bearer …']) |
The Worker downloads it |
Source::base64($data) |
The bytes, base64-encoded |
Source::file($path) or Source::file($request->file('pdf')) |
Sent with the request |
Source::contents($bytes, 'logo.png') |
Sent with the request |
Source::disk('logos/acme.png', 's3') |
Read from a Laravel disk, then sent |
PdfSource adds password and preserveXFA, MergeSource adds pages, size and margin, and FontSource adds postscriptName. Requests that carry files go out as multipart; a source object used twice in one request is uploaded once.
Operations
Each operation is a PendingPdf method named after its op, run in the order you call them:
- Pages:
addPage,removePages,selectPages,duplicatePage,rotatePages,resizePages,cropPages,setPageBoxes,scalePages,translateContent,insertPdf - Drawing:
drawText,drawImage,drawRectangle,drawEllipse,drawLine,drawSvgPath,drawSvg,drawPdfPage,watermark,pageNumbers - Forms:
fillForm,flattenForm,addFormField,setFieldProperties,removeFormFields,setFieldScript - Scripts:
addJavaScript,setXFAJavaScript,deleteXFA - Document:
setLayerVisibility,setViewerPreferences,setMetadata,attachFile,detachFile,convertToPDFA,embedFacturX,encrypt
Arguments you leave out are not sent, so the API's defaults apply. An explicit null is sent as null. Choices are enums in BogdanKharchenko\PdfMill\Enums, e.g. BuiltInFont::HelveticaBold or PaperSize::Letter. The API's README describes each operation in full.
Each operation is also a class in BogdanKharchenko\PdfMill\Operations, with the same arguments. Build a list of them elsewhere and add it with ->apply(...$operations).
Results
| Method | Returns |
|---|---|
create(), edit(), merge() |
A PendingPdf; see Build a PDF step by step |
download($key) |
FileResponse for any stored result |
info() |
InfoResponse, or LockedInfoResponse for an encrypted PDF sent without its password |
text(), extract(), scripts(), split(), measureText() |
TextResponse, ExtractResponse, ScriptsResponse, SplitResponse, MeasureResponse |
PDFs stored under the default outputs/ keys are deleted after 7 days if you set up the API's recommended R2 rule.
Errors
Every API error throws a subclass of BogdanKharchenko\PdfMill\Exceptions\ApiException. Each has the HTTP $status, the API's reply in $error, and a message that names the failing input, e.g. operations[2] (removePages): Page 9 is out of range.
| Exception | Status | When |
|---|---|---|
InvalidRequestException |
400 | Invalid options (fieldErrors() lists each field), a page out of range, an unknown form field |
UnauthorizedException |
401 | Wrong or missing API key |
NotFoundException |
404 | No file at an R2 key |
SourceTooLargeException |
413 | A URL source is too large |
UnprocessablePdfException |
422 | Not a PDF, a damaged PDF, or a wrong password |
SourceUnavailableException |
502 | A URL source, or the upload to put()'s URL, failed or couldn't be reached |
SourceTimeoutException |
504 | A URL source, or the upload to put()'s URL, timed out |
ServerException |
500 and others | The API failed unexpectedly |
Testing your app
PdfMill::fake() stands in for the API. Nothing is sent, every endpoint replies as the API would, and you assert on what your code asked for. It needs no PDFMILL_URL or key, and fakes a Client injected into your code too.
Only the network is replaced: your options are encoded and your files read just as in production.
By default every PDF is one blank A4 page. store() saves it under your key, or outputs/<uuid>.pdf. file(), download() and routes return a real PDF, and info(), text(), extract() and split() describe that page. To know keys and link expiry times in advance, use Str::freezeUuids() and $this->freezeTime().
put() gets that page's size and page count, but nothing is uploaded. If your code reads the file back, write it from a closure reply:
Set a reply
Give an endpoint its reply by name:
| Reply | |
|---|---|
| An array | Fields to change in the default reply. Objects are merged and lists replaced |
A result object, such as InfoResponse |
The whole reply. create, edit and merge take a StoredPdf, an UploadedPdf or a FileResponse, and answer store(), put() and file() from it |
An ApiException |
The API replies with that error, so your code gets it |
Any other exception, such as a ConnectionException |
Thrown as if sending failed |
Http::response(…) |
Exactly that HTTP reply |
| A closure | Gets the SentRequest and returns any of these, or null for the default |
Assert
| Assertion | Passes when |
|---|---|
PdfMill::assertSent('merge') |
At least one merge request was sent |
PdfMill::assertSent('edit', 2) |
Exactly two edit requests were sent |
PdfMill::assertSent('edit', fn (SentRequest $pdf) => …) |
The callback accepts an edit request. Leave out 'edit' to check every request |
PdfMill::assertNotSent('split') |
No split request was sent. It also takes a callback |
PdfMill::assertSentCount(3), PdfMill::assertNothingSent() |
Three requests in all, or none |
A failed assertion lists what was sent. PdfMill::sent('edit') returns the requests, to inspect yourself.
Each SentRequest has:
endpointdata: the options sent, decoded from JSONfiles: the uploadsoperations()hasOperation()
hasOperation() finds an operation by name, hasOperation('watermark', ['text' => 'DRAFT']), or by an object built the way your code builds it, hasOperation(new Watermark(text: 'DRAFT')). It matches when the operation has at least the fields you give. Uploaded files match by filename and contents.
Without PdfMill::fake(), requests go through Laravel's HTTP client, so Http::fake() works too.
Working on this package
Everything in generated/ is produced from openapi.json, a copy of the API's spec, so don't edit it by hand. To update to a new API version:
composer test runs the tests, which use replies captured from the real API (tests/fixtures) and fail if generated/ is out of date. composer analyse runs PHPStan. To also run against a deployment:
License
MIT
All versions of laravel-pdfmill with dependencies
illuminate/contracts Version ^12.0|^13.0
illuminate/http Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
spatie/laravel-data Version ^4.23