theodo/accent-bundle
A small Symfony bundle from Theodo for handling accented characters in PHP applications. It provides utilities to strip or convert diacritics, useful for generating clean slugs, URLs, and search-friendly text.
Accent is a Symfony bundle that centralizes authorization (not authentication) around attribute/annotation-driven checks and pluggable voters. As of v3.1.0 it is explicitly an API Platform authorization layer — API Platform is a hard dependency, not an optional integration. For a Laravel developer, think of it as a thin, annotation-first layer you attach to API Platform operations (and controllers), closer to @can than to Auth::check().
api-platform/symfony (recommended for Symfony apps), orapi-platform/core (framework-agnostic).
Do not install both — v3.1.0 prevents api-platform/core from being pulled in as a transitive dependency of theodo/accent-bundle to avoid conflicting setups, so pick one deliberately in your composer.json.composer require theodo/accent-bundle
config/bundles.php (Flex may not have a recipe for a small, low-visibility package):
Accent\AccentBundle::class => ['all' => true],
use Accent\Annotation\IsGranted;
class PostController
{
#[IsGranted('ROLE_ADMIN')]
public function delete(int $id) { /* ... */ }
}
The bundle listens on kernel.controller and throws an access-denied exception before your action body runs, mirroring Laravel middleware but scoped to a single method. For API Platform, attach the same attribute to operations — this is the primary integration surface the v3.1.0 release reinforces.Where to look first (the README is thin, so read the source):
vendor/theodo/accent-bundle/composer.json — check the API Platform version constraint; v3.1.0 narrows this.Security/ — start here (SecurityChecker, voter interface).Annotation/ — the attributes you can use.DependencyInjection/ — shows the accent: config tree and which services are tagged automatically, plus where the API Platform package requirement is asserted.EventListener/ — the exact lifecycle hook where checks are enforced; this tells you what is not covered.1. Declarative checks on API Platform operations and controllers. The default workflow: put #[IsGranted('ROLE_X')] on the operation/action and let the listener enforce it. Do not mix this with Symfony's native #[IsGranted] on the same method — pick one system per endpoint to avoid confusing 403s.
2. Programmatic checks via the checker. Inject SecurityCheckerInterface and call it from services, CLI commands, or message handlers, where the controller listener never fires:
final class DeletePost
{
public function __construct(private SecurityCheckerInterface $checker) {}
public function __invoke(Post $post): void
{
$this->checker->check('POST_DELETE', $post); // throws on denial
}
}
This is the Laravel $this->authorize(...) equivalent and is the pattern you will reach for most often in real applications — especially now that API Platform is guaranteed to be present and API Platform's own security expressions may not cover every decision your voters encode.
3. Subject-based (object-level) checks. Pass a subject so a voter can decide on the instance, not just the role — e.g. #[IsGranted('EDIT', subject: 'post')], where post names a controller argument. Treat this as the equivalent of a Laravel Policy method (update(User $user, Post $post)).
4. Custom voters as the extension point. Business rules belong in voters registered as services; the bundle discovers them via a tag. Keep each voter small and single-attribute (EDIT_POST, VIEW_REPORT) so unit tests stay trivial — one voter test class per voter, no kernel boot needed.
5. Testing strategy. Test voters with plain PHPUnit (they are pure decision logic). Test enforcement with a functional test hitting the endpoint and asserting 403 — this is where you catch misconfigured tags or a listener that silently isn't registered. Since API Platform is a hard dependency, also cover at least one API Platform operation in your functional suite so an upgrade that changes the API Platform version constraint is caught at CI time.
6. Layering. Leave authentication to Symfony's firewall / security.yaml; use Accent only for the authorization decision. In a Laravel mental model: firewall = auth middleware, Accent = Gate. On the API Platform side, this means Accent sits alongside API Platform's own security attribute — pick one per operation to avoid two independent authorization layers disagreeing.
api-platform/symfony nor api-platform/core is installed. If you have historically used Accent purely for controller actions and are upgrading from a v3.0 install, verify your composer.json still includes one of these packages — an unrelated composer remove can now break boot instead of silently disabling the integration.api-platform/core from being pulled in transitively when Accent is a dependency, precisely to avoid duplicate API Platform service definitions. Declare your chosen package explicitly in composer.json and let Composer resolve it as a direct dependency.@Accent\IsGranted); doctrine/annotations is deprecated on PHP 8. Use the PHP attribute form and confirm the installed version supports it.Symfony\Component\Security\Core\Exception\AccessDeniedException. Without an exception listener mapping it to 403, you may get a 500. Verify, and add a mapping if needed — on API Platform this manifests as a 500 on an operation rather than the clean 403 you expect.role_hierarchy, IS_AUTHENTICATED_REMEMBERED, and the security profiler panel may not behave as they do with the native checker. If you depend on hierarchy, test it explicitly or delegate inside your voter to security.authorization_checker.bin/console debug:container --tag=<accent voter tag>) before reaching for a debugger.bin/console cache:clear in dev, and remember deploy-time cache warmup.composer update resolves. Pin theodo/accent-bundle and review the composer.json diff of any upgrade — the v3.1.0 change of preventing api-platform/core installation is exactly the kind of dependency-graph edit that surprises CI.composer.json, read the code before upgrading Symfony or API Platform majors, and prefer wrapping the checker behind your own small interface so you can swap the bundle out later without touching every call site.SecurityCheckerInterface. Avoid subclassing bundle internals or relying on the listener's ordering — those are the pieces most likely to break across versions, and v3.1.0's tighter coupling to API Platform shows how quickly that surface can shift.How can I help you explore Laravel packages today?