phax/phax-bundle
Phax Bundle is a Symfony integration package for the Phax library. It registers services, handles configuration, and connects Phax components to Symfony’s dependency injection and framework features, reducing boilerplate for developers. Use it to wire Phax into Symfony apps with minimal setup and co...
Phax is a Symfony 2.x-only bundle (last release April 2014, archived) that gives AJAX calls a structure: you define a named server-side "Phax" action, the bundle exposes a single dispatch route, and a small JS library calls that action by name and hands you a normalized JSON envelope. If you inherit an app that uses it, the mental model is "one class per AJAX endpoint + a dispatcher route," not "one controller action per URL."
Minimum steps to get it running:
getRequest(), app/console, old DI XML/YAML, possibly each()).composer require phax/phax-bundle if Packagist still serves it; otherwise add a repositories VCS entry pointing at the GitHub tag, or vendor the bundle into src/ and register it manually.app/AppKernel.php: new Phax\Bundle\PhaxBundle\PhaxBundle() (check PhaxBundle.php in the repo root for the exact namespace/class — it matters, the repo layout is unusual).app/config/routing.yml:
phax:
resource: "@PhaxBundle/Resources/config/routing.yml"
prefix: /phax
This is what creates the single endpoint every call goes through.php app/console assets:install web --symlink, then include the bundle's script (look in Resources/public/js/) after jQuery in your base layout. There may also be a Twig helper — grep Resources/views/ for it.Resources/doc/ (if present), then the shipped phax.js — the JS file is the specification for the wire format. Read it before the PHP; it tells you what keys the response envelope must contain and what defaults (redirect, reload, flash) are handled for you.First use case — a form submit that returns validation errors or a success payload without writing a controller:
// src/Acme/DemoBundle/Phax/SaveCommentPhax.php
namespace Acme\DemoBundle\Phax;
use Symfony\Component\HttpFoundation\Request;
class SaveCommentPhax extends BasePhax // extend the bundle's base class
{
public function execute(Request $request)
{
$comment = new Comment();
$comment->setContent($request->request->get('content'));
$this->getDoctrine()->getManager()->persist($comment);
$this->getDoctrine()->getManager()->flush();
return array('id' => $comment->getId()); // becomes the `data` key
}
}
Register it as a service with the bundle's tag and a short alias (save_comment), then from JS call that alias and handle the envelope. Adjust names to whatever the bundle's DependencyInjection/ and tag definitions actually use — the shape is stable, the identifiers are not well documented.
One Phax class per action, thin controllers. Treat each class like a single-use-case command object reachable over HTTP. Resist putting three operations behind one alias with an action parameter — you lose the per-action DI and tagging that makes the bundle worth using at all.
Keep the response envelope canonical. The JS layer auto-handles a subset of keys (success flag, message, redirect, reload, DOM target). Use the bundle's response object rather than hand-rolling new JsonResponse([...]), or the default client-side handlers silently stop working.
Form validation as structured errors. Inject form.factory into the Phax service, build the form, call handleRequest($request), and on failure return the errors keyed by field name inside data so the JS can paint them next to inputs:
if (!$form->isValid()) {
return $this->error($this->getFormErrors($form)); // field => [messages]
}
This is the single biggest real-world win — you get one error format for every form in the app.
Namespace your aliases. If the dispatcher supports dots (comment.save, comment.delete, user.avatar.upload), use them. A flat namespace of 40 aliases becomes unsearchable fast.
Security must be explicit. The dispatch route is a generic entry point; controller-level @Security annotations won't cover it. Either put the whole /phax prefix behind a firewall with access_control, or add an isGranted() check in a base Phax class that every action extends.
Progressive enhancement. Keep a normal HTML form action/method as the fallback and layer phax.call() on top. When the JS or the dispatcher breaks (and on an archived bundle, it will), the app still works.
Return scalars, not entities. The bundle predates robust circular-reference handling. Map entities to small arrays/DTOs inside execute().
Testability is the redeeming feature. A Phax class is a plain service: unit-test execute() with a Request stub and a mocked entity manager. For integration, POST to the /phax/{alias} route with the kernel test client and assert on the envelope.
Testing the client side: stub phax.call (or your own wrapper around it) in JS tests and assert on the payload shape you send, not on jQuery internals.
Cross-cutting concerns belong in a base class or a kernel event listener: CSRF verification, JSON decoding, logging every call with its alias and duration, and global exception-to-envelope conversion.
$request->request for application/x-www-form-urlencoded. If the JS sends application/json, read json_decode($request->getContent(), true) yourself — otherwise every field looks empty and you'll blame the bundle.data- attribute on <body> is the usual trick) and validated in the base Phax class. Forget it and you get opaque 403/empty responses that don't appear in the app log unless you check the security channel.execute() or check app/logs/dev.log; a stack trace is usually there even when the browser shows nothing./phax) and constrain the alias parameter with a regex; otherwise it can match unrelated URLs imported later.assets:install web without --symlink, and re-run after every bundle update — stale copies of phax.js cause maddening "the fix didn't take" moments.success → ok, message → msg) breaks default handlers invisibly. Pin the envelope in one PHP constant and one JS constant, and grep for both when refactoring.getRequest() / service location. Older Symfony 2 code here may call $this->getRequest() or reach into the container. It's deprecated in 2.8 and gone in 3.x — this is the main thing that makes migration a rewrite rather than a bump.getDoctrine(), translator, security context); add a kernel listener for authentication so you don't repeat checks in 40 classes.How can I help you explore Laravel packages today?