laravel-workersv0.4.10

the census

118 constructs, 47 of them compile

tools/constructs.json is the complete list of PHP constructs the audit covers. Each one lands in exactly one bucket, and the three tallies below are recomputed from that file every time this audit is built.

47compile to JS
64fail the build, loudly
7documented build-level gap
All batches118 rows · 47 compile · 64 fail loud · 7 documented
One cell per construct, in file order. Hover for the label; the table below carries the detail.
Every construct in tools/constructs.json, with the batch it was proven in.
constructverdictbatchnote
now()compilesM33Carbon; the compiled side follows the request clock (M33) - the wall clock diverged.
today()compilesM33Carbon at midnight (UTC); the request clock again.
abort_if()compilesM33Answers null unless it aborts; the status + the message are Laravel's.
abort_unless()compilesM33The mirror of abort_if.
Log::info()compilesM33The platform log (the Workers console) is the padanan for the app log file.
new RuntimeException()compilesM34The built-in exceptions only; the class name + the message are kept, so the 500 matches.
throw_if()compilesM34Throws the given error when the condition says so.
redirect()->with()compilesM36Flashes into the session for the next request; the proof is the session payload + the Location.
redirect()->withInput()compilesM36Flashes _old_input (everything except the dontFlash fields).
redirect()->withErrors()compilesM36Flashes the error bag in the ViewErrorBag shape (the M19 validation flow writes the same shape).
redirect()->away()compilesM37An absolute external URL (the compiled redirect keeps it absolute).
back()compilesM37The Referer, or the session's url.previous, or /.
Str::slug()compilesM38The PHP-exact subset (slug/limit/upper/lower/studly/camel/snake/kebab/title/contains/startsWith/endsWith/after/before/length/replace*/squish/ucfirst/ucwords/ascii/is/finish/start/random/isUrl) - every value is verified against the real Laravel. The transliteration is portable-ascii's own table (kernel/ascii-map.data.mjs, generated by tools/ascii-map.php; tools/ascii-map-check.mjs keeps it honest), so Str::ascii/Str::slug are byte-identical to PHP 8.3 over the whole BMP.
env()compilesM37The build-time config dump is the padanan (the edge has no .env); only the app.* keys are mapped.
data_get()compilesM37Dot/array access on an array or an object (the * wildcard is not ported).
tap()compilesM37Passes the value through a closure (the closure support exists).
report()compilesM37Reports an exception to the platform log (it does not answer it).
cache() (the helper)compilesM37The helper form of the Cache facade (the facade compiles).
cookie() (the helper)compilesaudit #722CookieJar::make() through the request's jar; withCookie() queues it and the response pipeline emits set-cookie. The factory form (cookie() with no name) still fails loud.
session([...]) (the array form)compilesM37The array form writes many keys: session()->put([...]).
Event::dispatch() (an event object)compilesM39cThe listener is a class, so the build compiles it (the event classes + the listeners, dist/src/classes.generated.mjs). The dispatch is synchronous, like Laravel's.
Event::dispatch() (an event name)fails loudM39cAn event name has no class: the edge cannot know which listener answers, so the dispatch would be a silent no-op - dispatch the event object instead.
Gate::allows()compilesM39The policy is compiled at build time: the abilities, the guest rules, the before() hook.
Gate::allows() (the class-level check)compilesM39Laravel shifts the class name away before the ability is called.
Gate::authorize()fails loudM39It throws an AuthorizationException (a 403). The edge renders the app's own resources/views/errors/403.blade.php when it exists (the same as every other status error), so the working path is Gate::allows() + abort(403).
Gate::any()fails loudM39Only allows() and denies() compile.
Gate::define()fails loudM39A closure is not compiled - the global Gate registrations stay a port.
Gate::before()fails loudM39The global Gate callbacks are not compiled (the policies are).
Mail::to()->send()compilesM60A mailable is a class: the build compiles it (the dump takes the app's Mail classes, the class compiler emits the class and the kernel's Mailable base carries Laravel's fluent surface) and the mailer sends the payload Laravel sends - the recipients, the subject and the rendered view (the mailable's data goes into the view). The byte-for-byte proof is the shadow: the PHP oracle captures Laravel's MessageSent event (the from/to/cc/bcc/replyTo/subject/html/text) and the harness compares those fields with the kernel mailer's outbox - all 8 fields are identical, the log line is not compared. The methods the kernel mailer does not implement (raw, html, queue, later) fail loud.
Mail::send() (a view name)fails loudM60Laravel's Mailer::send() has two shapes: a Mailable INSTANCE (it is sent as it is) and a VIEW NAME (Laravel renders the view with the data and sends THAT - the route answers 200 and the mail goes out). The edge has only the first shape: the compiled mailer carries a payload (a compiled Mailable, or an object with to/subject/html), so a view name throws at request time ("mail(): the message is not a payload") and the route answers 500 - the build refuses the view shape instead of shipping a route that 500s where Laravel sends the mail. A mailable still compiles (Mail::send(new OrderMail()) and Mail::to($user)->send(new OrderMail())); a dynamic first argument is NOT refused (it can hold a mailable and the build cannot prove which it is - refusing it would refuse a legal send).
Queue::push()fails loudM39The FACADE is what stays refused here: Queue::push() takes a class-name string the edge cannot resolve to a compiled class, so nothing would consume the row - Job::dispatch() on a compiled job class is the supported form (M63). Without a consumer the job would never run at the edge: the worker has no queue handler, so nothing consumes the queue binding, and the binding carries a JSON body, not a PHP job. The paths that work: a Cloudflare Queues binding with a consumer you write (a limited version is on the Workers free plan; the paid plan charges per operation), or a D1 table the cron trigger reads (a compiled job class writes the row through Job::dispatch() - M63 - or a port handler does). In Laravel the job is queued (or run inline by the sync driver) and the route answers 200.
Job::dispatch() on a compiled job classcompilesM63A job class the build compiles (app/Jobs): dispatch() writes a row into the D1 lw_jobs table and the CRON TRIGGER drains it, so the job runs after the response (Laravel's database driver). The build refuses the push when the worker has no cron trigger - the job would never run - and it also refuses a push whose job class did NOT compile: the drain builds its handler map from the compiled classes, so a job the class compiler refused has no handle() for the drain and the row would be dropped unseen. Laravel's own job traits (Dispatchable, Queueable, InteractsWithQueue, SerializesModels) are allowed - their members are answered at the call site or are PHP-serializer-only - but a body that CALLS one of their members ($this->release(3)) is refused. The Queue FACADE (Queue::push) stays refused: it takes a class-name string the edge cannot resolve to a compiled class.
Storage::disk() (the default disk)compilesM39The default disk is the compiled object store (the D1 blob store, or R2 when bound).
Storage::disk() (a named disk)fails loudM39A named disk needs a bucket of its own (R2 asks for a credit card); the compiled rung has one store.
Cache::tags()fails loudM39The compiled cache store is the D1 database store, which has no tags: in Laravel this route 500s with BadMethodCallException("This cache store does not support tagging."); the build refuses the route before it ships (stricter than Laravel, not a parity), and a taggable store (redis/memcached) is not compiled.
Blade @extends (a layout)compilesM40The sections travel to the layout (the shadow harness proves the bytes).
Model::paginate()compilesM40The LengthAwarePaginator (the items, the total, the pages, the URLs) + the pagination links (the framework's pagination::tailwind view is compiled at build time).
The translation helper (__())compilesM40The strings are dumped at build time (the app's lang/ files + the framework's fallback); the place-holders are replaced like Laravel does.
A PHP cast ((string) $x)compilesM40The scalar casts with PHP's own semantics: (string), (int), (bool), (float).
The (object) cast ((object) $x)compilesM40PHP's cast to stdClass (rt.objectCast, oracle-probed): a list becomes an object keyed by the stringified indices, an object is answered as-is, null is the empty object, a scalar wraps under scalar. The result carries the stdClass marker, so json_encode() answers {}/{"0":..} and never the array shape ([]) - json_decode() with $assoc = false and the object model cast mark the same way
Null-safe access ($a?->b)compilesM40PHP's ?->: a null/undefined receiver answers null, and that null propagates through any later -> links in the same chain (rt.nsprop, rt.nsmethod for the method form).
Route-model binding (a typed param)compilesM40The model comes from the route segment (Laravel's implicit binding); a missing model is a 404.
Eloquent relations ($post->comments())fails loudM41A relation is not compiled: the build stops (LW003). The eager loading (with()) is not compiled at the edge either - the relations are a planned milestone (M64), so a route that reads one needs a port handler until then.
File upload ($request->file())compilesM40The multipart body is parsed at the edge: the file object carries the metadata (the name, the type the browser declared, the size) and the store (the app's default disk). Byte-proven by the shadow harness.
The upload is stored ($file->storeAs())compilesM40The object store (the D1 blob store on the free plan, R2 with a bucket of its own): the path is the disk-relative one Laravel returns (trim($path.'/'.$name, '/')).
The upload is stored under a random name ($file->store())compilesM40The name is a random hash (Str::random(40)) + the extension; the extension comes from the type the browser declared (Laravel sniffs the content with finfo).
The content type of the upload ($file->getMimeType())fails loudM40The content type is read from the file's bytes (finfo): the edge cannot sniff the content like PHP does, so the build stops (LW003).
A mimes/mimetypes rule on an uploadcompilesM40PHP compares what finfo sniffs; the edge reads the image HEADER instead (kernel/image.mjs) and answers with the same guessExtension() map Symfony uses - so mimes:jpg,png,webp (and mimetypes:image/jpeg,...) answer Laravel's verdict, including the jpg/jpeg merge: guessExtension() answers jpg for image/jpeg and the vendor adds BOTH names, so mimes:jpeg ACCEPTS a real JPEG (ValidatesAttributes::validateMimes() :1764-1766). A parameter naming any other content (pdf, svg, docx, bmp) still fails loud (LW002): that verdict would be a guess.
A mimes rule naming a format the edge cannot snifffails loudM40mimes compiles only for the formats the header read can name (jpg, jpeg, png, gif, webp, avif, heic, heif): a parameter naming any other content (pdf, svg, docx, bmp) stops the build (LW002) and NAMES the parameter, because the verdict PHP reaches comes from finfo.
An image/dimensions rule on an uploadcompilesM69image and dimensions are getimagesize(), a HEADER read (ValidatesAttributes::validateImage/validateDimensions): the edge reads the same header (kernel/image.mjs) and applies the same named checks, so the verdict is Laravel's. A mimes/mimetypes parameter naming a format that read cannot sniff stays refused, like any other finfo rule.
The files bag as a method ($request->files())fails loudM40In Laravel $request->files is a property (Symfony's FileBag), not a method: the build stops (LW002) and points at $request->file() / $request->allFiles().
An OPTIONS routedocumentedM40The compiled side answers Laravel's automatic OPTIONS response (200 + the Allow header - byte-proven); a CORS config makes the build skip the route.
A relation read as a property ($post->comments)fails loudM42A relation (or an accessor) read as a property is not compiled: the build stops (LW003). It used to answer null - a wrong answer, silently.
A rule object (Rule::file()->max())fails loudM43A rule object in a CONTROLLER's validate() call is a class: the compiler cannot evaluate it at build time (the rules live in the controller's source), so the rule would be silently ignored and the validation would differ. The build stops and names what it would take. (In a FormRequest the dump normalizes the object to its string form - the row below.)
A rule object in a FormRequestdocumentedM43/M62The FormRequest dump reads the rules Laravel itself builds and NORMALIZES a rule object to its string form (Laravel hands the validator the object and the validator reads that string): Rule::in(['a','b']) becomes in:"a","b" - which the kernel parses exactly like Laravel (M61a). A CONDITIONAL rule object is refused (the row below).
A conditional rule object in a FormRequestdocumentedM62A conditional rule object (Rule::requiredIf(true), Rule::when(...)) is refused: its string form is the CONDITION evaluated at build time (RequiredIf::__toString() returns 'required' or ''), so the edge would bake the build-time branch into the rule - a request Laravel accepts could be rejected (or the other way round). The dump stops with that reason (LW003).
prepareForValidation() (overridden)documentedM62dThe hook is COMPILED: the build compiles the FormRequest class (the dump registers it), the compiled handler runs the hook BEFORE the validation (Laravel's order - ValidatesWhenResolvedTrait::validateResolved), and the input the hook prepares is what the rules see. The compiled class extends the kernel's kernel/form-request.mjs - the request surface the hook uses (merge(), input(), all()).
A FormRequest that overrides after() / validationData() / passedValidation()documentedM62fAll three hooks run in the compiled lifecycle, in Laravel's order: validationData() is the data source, after() registers its RETURN VALUE as a validator callback, and passedValidation() runs after the rules pass. Proven by the shadow (POST /hooked-request-demo 422/422 for the after() refusal and 200/200 carrying the passedValidation marker). The hook parameter needs its TYPE HINT (Validator $validator, and the same on the callbacks the hook registers through $validator->after(...)): an untyped parameter is LW002 (the class compiler cannot infer the validator), and the route that injects the class is then STOPPED with that reason instead of shipping a handler that answers 500 (audit #891). after() returns a single callable in the proven shape.
A FormRequest that overrides withValidator()documentedM62fLaravel calls the hook with the validator right after it is built (Foundation/Http/FormRequest.php:110), so the app's custom messages and its after() callbacks run. The compiled class carries the hook and the lifecycle calls it with the kernel's validator, in Laravel's order. Proven by the shadow (POST /hooked-request-demo 422/422 byte-identical when withValidator() refuses the flag) and by the compiled handler's hooks: [...] list. The hook parameter needs its TYPE HINT (Validator $validator, and the same on the callbacks the hook registers through $validator->after(...)): an untyped parameter is LW002 (the class compiler cannot infer the validator), and the route that injects the class is then STOPPED with that reason instead of shipping a handler that answers 500 (audit #891). after() returns a single callable in the proven shape.
A listener that injects a servicefails loudM43A service the build does not compile: the edge has no container and no file for it, so the constructor dependency is a build error (the build names the class). A service the build DOES compile is injected (the edge builds it) and a framework service is adapted (the worker hands the live one down).
A queued listener (ShouldQueue)fails loudM43Laravel pushes a queued listener to the queue (it runs later); the edge's queue path runs a JOB class (app/Jobs) through the cron drain, and a queued listener is not compiled as one, so running it inline would be a different behaviour. The build stops and the route that dispatches the event names the reason.
A listener that injects an app service the build compilescompilesM44The edge builds the service itself: the build compiles it (the class + the services it injects) and the runtime hands it to the listener. The dependency is recorded in the compiled class, so the edge never guesses.
A listener that injects a framework service (the mailer)compilesM44The edge has no container: the worker hands the live service down (the mailer) and the compiled listener gets it. Its async methods are awaited in the compiled body (a promise must never leak into the response).
A listener that pushes to the queuefails loudM45The refusal holds in a compiled class too: Queue::push() names the job as a class-name string, which the edge cannot resolve to a compiled class, so nothing would consume the row (the compiled path is Job::dispatch(), M63).
A listener that uses a tagged cachefails loudM45The D1 database store has no tags: in Laravel this route 500s with BadMethodCallException("This cache store does not support tagging."); the build refuses the route before it ships (stricter than Laravel, not a parity).
A broadcast listener (ShouldBroadcast)fails loudM49Laravel broadcasts the event to the channels after the response (the broadcast itself is queued). The edge has no broadcast driver, so the notification would never be sent - a silent no-op, so the build stops and the route that dispatches the event names the reason.
A listener dispatched after the commit (ShouldDispatchAfterCommit)fails loudM49Laravel defers the listener until the database transaction is committed. The edge has no transaction, so the timing would differ - the build stops and the route that dispatches the event names the reason.
Event::dispatchUntil() (the halt variant)fails loudM49Laravel's dispatchUntil() stops the chain at the first non-null answer and returns it. The compiled dispatch runs EVERY listener and answers the array of the responses, so the compiled rung cannot answer Laravel's value here - it fails loud with the halt semantics named.
The App facadefails loudM49The App facade is not compiled at the edge (Laravel 13 lists 142 methods): the build refuses it with that reason - a port handler answers it.
The Artisan facadefails loudM49The Artisan facade is not compiled at the edge (Laravel 13 lists 18 methods): the build refuses it with that reason - a port handler answers it.
The Auth facadecompilesM49The Auth facade has a compiled surface (Laravel 13 lists 58 methods; the probe uses a method that compiles).
The Blade facadefails loudM49The Blade facade is not compiled at the edge (Laravel 13 lists 43 methods): the build refuses it with that reason - a port handler answers it.
The Broadcast facadefails loudM49The Broadcast facade is not compiled at the edge (Laravel 13 lists 30 methods): the build refuses it with that reason - a port handler answers it.
The Bus facadefails loudM49The Bus facade is not compiled at the edge (Laravel 13 lists 48 methods): the build refuses it with that reason - a port handler answers it.
The Cache facadecompilesM49The Cache facade has a compiled surface (Laravel 13 lists 65 methods; the probe uses a method that compiles).
The Cloud facadefails loudM49The Cloud facade is not compiled at the edge (Laravel 13 lists 8 methods): the build refuses it with that reason - a port handler answers it.
The Concurrency facadefails loudM49The Concurrency facade is not compiled at the edge (Laravel 13 lists 14 methods): the build refuses it with that reason - a port handler answers it.
The Config facadefails loudM49The Config facade is not compiled at the edge (Laravel 13 lists 17 methods): the build refuses it with that reason - a port handler answers it.
The Context facadefails loudM49The Context facade is not compiled at the edge (Laravel 13 lists 43 methods): the build refuses it with that reason - a port handler answers it.
The Cookie facadefails loudM49The Cookie facade is not compiled at the edge (Laravel 13 lists 15 methods): the build refuses it with that reason - a port handler answers it.
The Crypt facadefails loudM49The Crypt facade is not compiled at the edge (Laravel 13 lists 11 methods): the build refuses it with that reason - a port handler answers it.
The DB facadefails loudM49The DB facade is not compiled at the edge (Laravel 13 lists 113 methods): the build refuses it with that reason - a port handler answers it.
The Date facadefails loudM49The Date facade is not compiled at the edge (Laravel 13 lists 95 methods): the build refuses it with that reason - a port handler answers it.
The Event facadefails loudM49The Event::dispatch() form IS compiled (the event class and its listeners), but it needs an INSTANCE of a compiled event class - the build refuses it with that reason. The other 32 methods of the facade (Laravel 13 lists 33) are not compiled.
The Exceptions facadefails loudM49The Exceptions facade is not compiled at the edge (Laravel 13 lists 34 methods): the build refuses it with that reason - a port handler answers it.
The Facade facadefails loudM49The Facade facade is not compiled at the edge (Laravel 13 lists 0 methods): the build refuses it with that reason - a port handler answers it.
The File facadefails loudM49The File facade is not compiled at the edge (Laravel 13 lists 53 methods): the build refuses it with that reason - a port handler answers it.
The Gate facadefails loudM49The Gate::allows()/Gate::denies() forms ARE compiled (the compiled policy decides), but this construct builds an exception object - the build refuses it with that reason. The other 25 methods of the facade (Laravel 13 lists 26) are not compiled.
The Hash facadecompilesM49The Hash facade has a compiled surface (Laravel 13 lists 15 methods; the probe uses a method that compiles).
The Http facadefails loudM49The Http facade is not compiled at the edge (Laravel 13 lists 99 methods): the build refuses it with that reason - a port handler answers it.
The Image facadefails loudM49The Image facade is not compiled at the edge (Laravel 13 lists 15 methods): the build refuses it with that reason - a port handler answers it.
The Lang facadefails loudM49The Lang facade is not compiled at the edge (Laravel 13 lists 30 methods): the build refuses it with that reason - a port handler answers it.
The Log facadecompilesM49The Log facade has a compiled surface (Laravel 13 lists 31 methods; the probe uses a method that compiles).
The Mail facadecompilesM60The Mail facade is compiled: the kernel's mailer implements Laravel's PendingMail (to/cc/bcc/replyTo + send()), so the fluent form compiles. The shadow proves the payload against the real app (the MessageSent fields vs the kernel outbox - all 8 fields identical). The methods it does not implement (raw, html, queue, later) fail loud - a mail that silently lost its cc would be a wrong answer.
The MaintenanceMode facadefails loudM49The MaintenanceMode facade is not compiled at the edge (Laravel 13 lists 7 methods): the build refuses it with that reason - a port handler answers it.
The Notification facadefails loudM49The Notification facade is not compiled at the edge (Laravel 13 lists 34 methods): the build refuses it with that reason - a port handler answers it.
The ParallelTesting facadefails loudM49The ParallelTesting facade is not compiled at the edge (Laravel 13 lists 16 methods): the build refuses it with that reason - a port handler answers it.
The Password facadefails loudM49The Password facade is not compiled at the edge (Laravel 13 lists 11 methods): the build refuses it with that reason - a port handler answers it.
The Pipeline facadefails loudM49The Pipeline facade is not compiled at the edge (Laravel 13 lists 15 methods): the build refuses it with that reason - a port handler answers it.
The Process facadefails loudM49The Process facade is not compiled at the edge (Laravel 13 lists 39 methods): the build refuses it with that reason - a port handler answers it.
The Queue facadefails loudM49The Queue facade is not compiled at the edge (Laravel 13 lists 85 methods): the build refuses every method with its own reason - the queue would never run at the edge (the worker exports no consumer for a Cloudflare Queues binding, and the binding carries a JSON body, not a PHP job; the compiled path is the D1 lw_jobs table the cron trigger drains, M63). A port handler (or a Cloudflare Queues binding) answers it.
The RateLimiter facadefails loudM49The RateLimiter facade is not compiled at the edge (Laravel 13 lists 18 methods): the build refuses it with that reason - a port handler answers it.
The Redirect facadefails loudM49The Redirect facade is not compiled at the edge (Laravel 13 lists 19 methods): the build refuses it with that reason - a port handler answers it.
The Redis facadefails loudM49The Redis facade is not compiled at the edge (Laravel 13 lists 302 methods): the build refuses it with that reason - a port handler answers it.
The Request facadefails loudM49The Request facade is not compiled at the edge (Laravel 13 lists 192 methods): the build refuses it with that reason - a port handler answers it.
The Response facadefails loudM49The Response facade is not compiled at the edge (Laravel 13 lists 21 methods): the build refuses it with that reason - a port handler answers it.
The Route facadefails loudM49The Route facade is not compiled at the edge (Laravel 13 lists 101 methods): the build refuses it with that reason - a port handler answers it.
The Schedule facadefails loudM49The Schedule facade is not compiled at the edge (Laravel 13 lists 84 methods): the build refuses it with that reason - a port handler answers it.
The Schema facadefails loudM49The Schema facade is not compiled at the edge (Laravel 13 lists 52 methods): the build refuses it with that reason - a port handler answers it.
The Session facadefails loudM49The Session facade is not compiled at the edge (Laravel 13 lists 70 methods): the build refuses it with that reason - a port handler answers it.
The Storage facadecompilesM49The Storage facade has a compiled surface (Laravel 13 lists 84 methods; the probe uses a method that compiles).
The URL facadefails loudM49The URL facade is not compiled at the edge (Laravel 13 lists 46 methods): the build refuses it with that reason - a port handler answers it.
The Validator facadefails loudM49The Validator facade is not compiled at the edge (Laravel 13 lists 15 methods): the build refuses it with that reason - a port handler answers it.
The View facadefails loudM49The View facade is not compiled at the edge (Laravel 13 lists 79 methods): the build refuses it with that reason - a port handler answers it.
The Vite facadefails loudM49The Vite facade is not compiled at the edge (Laravel 13 lists 32 methods): the build refuses it with that reason - a port handler answers it.
an API resource (return new PostResource($post))compilesM66The controller answers a JsonResource; the Router's prepareResponse() wraps it in a ResourceResponse - {"data": {...}, ...with($request)} (JsonResource::toArray + the $wrap key, ResourceResponse.php:31-52). The app declares only toArray()/with(); the rest is the kernel base. Byte-proven by the shadow (GET /resource-demo identical) and by compiler/tests/resource-class.test.mjs; the audit probes it as a controller fragment with the class registry the build passes. A body that needs an await fails loud (the response path is synchronous).
A FormRequest that overrides validator()documentedM62fLaravel asks the app's own factory for the validator (FormRequest::getValidatorInstance()), so an override decides the rules, the messages and the factory itself. The edge has ONE validator factory, so the dump refuses the class (LW003, tools/formrequest-dump.php) and the build stops instead of validating with different rules than Laravel.
A try/catch/finally statementfails loudGAPThe transpiler has no try handler, so the whole route (and any class method that carries one) fails the build with LW002 (MAP.md 6b lists try/catch/finally as UNSUPPORTED - matches). The tracked gap is the missing APP-SOURCE SWEEP rule: a try/catch that no compiled route touches is silent until it is reached. Rewrite without try/catch, or port the route.