Installation
composer require edweld/aclbundle
Ensure your composer.json includes "minimum-stability": "dev" if the package is only available in @dev.
Register Bundle
Add to app/AppKernel.php:
new Edweld\AclBundle\EdweldAclBundle(),
Configure Doctrine Mappings
Update app/config/config.yml:
doctrine:
orm:
entity_managers:
default:
mappings:
EdweldAclBundle: ~
First Use Case: Define ACL Entities
Create a User and Circle entity with ACL annotations:
// src/AppBundle/Entity/User.php
use Edweld\AclBundle\Model\AclAwareInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User implements AclAwareInterface
{
// ... fields ...
/**
* @ORM\ManyToMany(targetEntity="Circle", inversedBy="users")
*/
private $circles;
// Implement AclAwareInterface methods
public function getAclIdentifier() { /* ... */ }
public function getAclObjectIdentifier() { /* ... */ }
}
/**
* @ORM\Entity
* @ORM\Table(name="circles")
*/
class Circle implements AclAwareInterface
{
// ... fields ...
/**
* @ORM\ManyToMany(targetEntity="User", mappedBy="circles")
*/
private $users;
// Implement AclAwareInterface methods
public function getAclIdentifier() { /* ... */ }
public function getAclObjectIdentifier() { /* ... */ }
}
Run Migrations
php bin/console doctrine:schema:update --force
Basic ACL Check
use Edweld\AclBundle\Acl\AclProvider;
$aclProvider = $this->get('edweld_acl.acl_provider');
$user = $this->getUser(); // Your logged-in user
$circle = $entityManager->getRepository('AppBundle:Circle')->find(1);
if ($aclProvider->isGranted('VIEW', $user, $circle)) {
// User can view the circle
}
Define Permissions
Extend the Permission class or use constants:
// src/AppBundle/Entity/Permission.php
use Edweld\AclBundle\Model\Permission;
class AppPermission extends Permission
{
const VIEW_EVENT = 'VIEW_EVENT';
const CREATE_EVENT = 'CREATE_EVENT';
}
Assign Permissions via Groups (Circles)
Use the AclProvider to attach permissions to groups:
$circle = $entityManager->find('AppBundle:Circle', 1);
$user = $entityManager->find('AppBundle:User', 1);
$aclProvider->attachPermission($circle, AppPermission::VIEW_EVENT, $user);
Query-Level Filtering
Use the QueryHelper to filter entities in repositories:
// src/AppBundle/Repository/EventRepository.php
use Edweld\AclBundle\Doctrine\Query\AclQueryHelper;
class EventRepository extends ServiceEntityRepository
{
public function findVisibleEvents(User $user)
{
$qb = $this->createQueryBuilder('e');
$aclHelper = $this->getDoctrine()->get('edweld_acl.query_helper');
return $aclHelper->addAclFilter($qb, $user, 'VIEW_EVENT')
->getQuery()
->getResult();
}
}
Controller Integration
// src/AppBundle/Controller/EventController.php
public function indexAction(User $user)
{
$events = $this->getDoctrine()
->getRepository('AppBundle:Event')
->findVisibleEvents($user);
return $this->render('event/index.html.twig', ['events' => $events]);
}
Symfony Security Integration
Override AccessDecisionManager to use AclProvider for authorization:
# app/config/security.yml
security:
access_decision_manager:
strategy: affirmative
allow_if_all_abstain: false
Create a custom AclVoter:
use Edweld\AclBundle\Acl\AclProvider;
class AclVoter extends AbstractVote
{
private $aclProvider;
public function __construct(AclProvider $aclProvider)
{
$this->aclProvider = $aclProvider;
}
protected function supports($attribute, $object)
{
return $attribute === 'VIEW_EVENT' && $object instanceof Event;
}
protected function voteOnAttribute($attribute, $object, TokenInterface $token)
{
return $this->aclProvider->isGranted($attribute, $token->getUser(), $object);
}
}
Event Listeners for Dynamic Permissions Use listeners to update ACLs when entities change:
// src/AppBundle/EventListener/AclListener.php
use Edweld\AclBundle\Acl\AclProvider;
use Doctrine\Common\EventSubscriber;
class AclListener implements EventSubscriber
{
private $aclProvider;
public function __construct(AclProvider $aclProvider)
{
$this->aclProvider = $aclProvider;
}
public function getSubscribedEvents()
{
return [
'postPersist',
'postUpdate',
];
}
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if ($entity instanceof Circle) {
$this->aclProvider->attachPermission($entity, AppPermission::VIEW_EVENT, $entity->getCreator());
}
}
}
SQL Query Complexity
DISTINCT or optimize joins if queries become slow:
$qb->distinct()->leftJoin('e.circle', 'c');
Circular Dependencies
User and Circle have bidirectional relationships, ensure AclAwareInterface methods (getAclIdentifier, getAclObjectIdentifier) return consistent values to avoid infinite loops in ACL checks.id or composite keys:
public function getAclIdentifier() { return $this->id; }
public function getAclObjectIdentifier() { return $this->circle->id; }
Permission Inheritance
public function isGranted($permission, User $user, $object)
{
if ($permission === 'EDIT' && $this->isAdmin($user)) {
return true;
}
return parent::isGranted($permission, $user, $object);
}
Debugging ACL Checks
# app/config/config.yml
doctrine:
dbal:
logging: true
logging_format: '%%sql%%'
AclProvider::debug() to log ACL decisions:
$aclProvider->debug(true);
Use Enums for Permissions Define permissions as constants or enums to avoid typos:
final class EventPermission
{
public const VIEW = 'VIEW_EVENT';
public const CREATE = 'CREATE_EVENT';
}
Batch Permission Updates For bulk operations (e.g., adding users to a circle), use transactions:
$entityManager->beginTransaction();
try {
foreach ($users as $user) {
$aclProvider->attachPermission($circle, EventPermission::VIEW, $user);
}
$entityManager->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Cache ACL Decisions
Cache frequent ACL checks (e.g., in a Symfony\Component\HttpKernel\CacheWarmer):
$cache = $this->get('cache.app');
$cacheKey = sprintf('acl_%s_%s_%s', $user->getId(), get_class
How can I help you explore Laravel packages today?