everlutionsk/ajaxcom-php
PHP library for building operations arrays that JavaScript can handle, the backend counterpart to @everlutionsk/ajaxcom. Ships with a Symfony bundle for easy integration. Install via Composer: everlutionsk/ajaxcom-php.
Install and pin. There is no Laravel service provider, facade, or config file to publish — this is a plain Composer library that builds an array. Install it and immediately pin the exact version in composer.json (the last release is 2021-05-14):
composer require everlutionsk/ajaxcom-php:1.x.y
Read in this order before writing code:
src/ — the builder class is the package. It is small; read all of it. The fluent method names (redirects, HTML swaps, notifications) are not documented in the README, so the source is the API reference. Confirm each method's signature and the exact key names it emits.@everlutionsk/ajaxcom (JS) repo — this is the real specification. The PHP side only produces keys the client understands; the client's README/examples tell you what an operation actually looks like and how it is applied.AjaxcomBundle repo — even though you are on Laravel, it shows a tested wiring of this library into a framework (service definition, response listener) you can mirror with a small service provider of your own.First use case. Pick one endpoint that today returns bespoke JSON, e.g. a "save profile" AJAX call doing return response()->json(['redirect' => ..., 'message' => ...]). Replace the ad-hoc payload with the builder:
public function update(ProfileRequest $request, OperationBuilder $ops)
{
$user = $request->user()->fill($request->validated())->save();
return response()->json(
$ops->toArray() // confirm actual serialization method in src/
);
}
Then confirm the browser actually performs the redirect/toast. If that round-trip works, the contract is understood and you can scale to more operations.
Wire it through the container once. Bind the builder (or your own wrapper) as a singleton/scoped instance in a SupportServiceProvider so controllers never new it. You will replace this binding if you later fork or reimplement, so make it the only place the package class name appears.
Adapter-first, always. Do not let Everlution\AjaxCom\... types leak into controllers, jobs, or domain code. Define one interface — App\Support\Operations\OperationBuilder — with methods you actually use (redirect(), toast(), replaceHtml(), dispatch()), implement it once against the package, and bind it. This is a two-hour investment that turns a fork or reimplementation into a one-file change rather than a grep-and-replace across the app.
Response-shaping in one place. Build a small trait or RespondsWithOperations concern:
trait RespondsWithOperations
{
protected function operations(): OperationBuilder
{
return app(OperationBuilder::class);
}
protected function operationsResponse(OperationBuilder $ops, int $status = 200)
{
return response()->json($ops->toArray(), $status);
}
}
Validation and error responses. The highest-leverage pattern is routing 422/419/500 through the same protocol so the frontend has one code path. Override failedValidation() in your FormRequest (or the exception handler's render() for ValidationException) to return operations that mark fields invalid and push a toast. This removes dozens of ad-hoc {errors: {...}} handlers from Blade/JS.
Blade partials as operations. For table refresh, inline edit, or modal content, render server-side and hand the HTML over:
$ops->replaceHtml('#user-row-'.$user->id, view('users._row', compact('user'))->render());
This is the pattern that makes server-driven UI pay off — you keep Blade, keep authorization in policies, and skip an SPA.
Compose in presenters, not controllers. For anything beyond two operations, put the composition in a dedicated class (App\Presenters\UserRowPresenter) that returns a serialized array. Controllers stay thin, and the presenter is your unit-test seam.
Testing. Assert on the JSON structure, not the package internals:
$response->assertJsonPath('0.a', 'html'); // use the real key names from src/
Keep one golden-file fixture of a realistic operations array and assert the builder still produces it — that fixture is your compatibility contract with the JS client, and it is what you diff against when you fork or reimplement.
Introduce behind a flag. Because the protocol is bespoke, cut over endpoint-by-endpoint behind a feature flag with the legacy JSON path intact. The JS client applies operations sequentially and can fail mid-list; you want the old endpoint one config flip away.
Assume PHP 8.x incompatibility until you prove otherwise. The last release predates 8.1/8.2/8.3 adoption. Run your test suite on your target runtime before committing. Watch for implicit-nullable parameters, dynamic properties, and ${var} interpolation in strings. If it breaks, either --ignore-platform-reqs (document the smell in the ADR) or fork into packages/ajaxcom-php with a path repository. Prefer the fork — the workaround will bite on the next upgrade.
Empty arrays serialize as [], not {}. PHP's json_encode([]) yields []. If the JS client iterates expecting an array of operations you are fine, but if it treats the payload as an object or a keyed map, an empty result will surprise it. Check the client's expectation and cast accordingly ((object), or JSON_FORCE_OBJECT where appropriate).
No escaping guarantees on HTML operations. Whatever you pass to an HTML/replace operation is injected into the DOM by the client. Blade's auto-escaping does not apply to a string you built with sprintf. Never interpolate request input into an operation payload — always render a view or use e(). This is your primary XSS surface and there is no upstream security process to catch it for you.
Order matters and redirects terminate. Operations execute sequentially on the client. A redirect followed by DOM updates is a race — put terminal operations last and never assume code after a client-side redirect runs.
Silent client-side failure is the default failure mode. The JS side has no shared error semantics with the PHP side, so a malformed operation throws in the browser console and the user sees nothing happen. Wrap the client's apply loop in a try/catch that logs the failing operation and surfaces a generic error toast; add a debug mode that console.logs the raw payload. On the PHP side, Log::debug('ops', $ops->toArray()) in a middleware when a debug header is present is worth the ten lines.
Debug by diffing the contract. When "the modal doesn't open," dump the array and compare key-by-key against the JS repo's examples. Nine times out of ten it is a misspelled operation name or a scalar where the client expects an array.
Never let the package touch domain or queue code. Operations are a presentation concern. Building them in a job or model couples your persistence layer to an abandoned wire format.
Extend by composition, not by editing vendor/. If you need a new operation type, subclass or decorate the builder behind your adapter, and confirm the JS client can handle the new operation — the two halves are released independently and there is no version negotiation.
Record the compatibility matrix. In your README or ADR, note the PHP package version and the exact @everlutionsk/ajaxcom JS version it was validated against, plus your PHP version and the date. With no upstream releases since 2021, that line is the only thing that will tell a future engineer whether an upgrade is safe.
Budget for ownership. Everything above is cheap; the maintenance is not. If the spike shows the builder is a few hundred lines, reimplementing it internally (app/Support/Operations/) is usually cheaper within the first year than carrying a fork through two PHP upgrades — and it removes the dependency from your security triage forever.
How can I help you explore Laravel packages today?