PHP code example of nexara / api-platform-voter

1. Go to this page and download the library: Download nexara/api-platform-voter library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

nexara / api-platform-voter example snippets


use ApiPlatform\Metadata\ApiResource;
use Nexara\ApiPlatformVoter\Attribute\Secured;

#[ApiResource]
#[Secured(prefix: 'article', voter: ArticleVoter::class)]
class Article
{
    // Your entity properties...
}

namespace App\Security\Voter;

use App\Entity\Article;
use Nexara\ApiPlatformVoter\Voter\CrudVoter;
use Symfony\Bundle\SecurityBundle\Security;

final class ArticleVoter extends CrudVoter
{
    public function __construct(private readonly Security $security)
    {
        $this->autoConfigure(); // ✨ Zero config!
    }

    protected function canCreate(): bool
    {
        return $this->security->isGranted('ROLE_USER');
    }

    protected function canUpdate(mixed $object, mixed $previousObject): bool
    {
        return $object->getAuthor() === $this->security->getUser();
    }

    protected function canDelete(mixed $object): bool
    {
        return $this->security->isGranted('ROLE_ADMIN');
    }
}

final class ArticleVoter extends CrudVoter
{
    public function __construct(private readonly Security $security)
    {
        $this->configure()
            ->prefix('article')
            ->resource(Article::class)
            ->autoDiscoverOperations(); // Auto-finds can* methods
    }

    protected function canUpdate(mixed $object, mixed $previousObject): bool
    {
        return $object->getAuthor() === $this->security->getUser();
    }
}

final class ArticleVoter extends CrudVoter
{
    public function __construct(private readonly Security $security)
    {
        $this->setPrefix('article');
        $this->setResourceClasses(Article::class);
    }

    protected function canUpdate(mixed $object, mixed $previousObject): bool
    {
        return $object->getAuthor() === $this->security->getUser();
    }
}

#[ApiResource(
    operations: [
        new Post(
            uriTemplate: '/articles/{id}/publish',
            name: 'publish',
            // ... other config
        ),
    ]
)]
#[Secured(voter: ArticleVoter::class)]
class Article
{
    // ...
}

final class ArticleVoter extends CrudVoter
{
    // ... other methods

    protected function canPublish(mixed $object, mixed $previousObject): bool
    {
        // Custom logic for publish operation
        return $this->security->isGranted('ROLE_MODERATOR')
            && $object->getStatus() === 'draft';
    }
}

final class ArticleVoter extends CrudVoter
{
    public function __construct(private readonly Security $security)
    {
        $this->autoConfigure(); // Reads from #[Secured] + VoterRegistry
    }
}

final class ArticleVoter extends CrudVoter
{
    public function __construct(private readonly Security $security)
    {
        $this->configure()
            ->prefix('article')
            ->resource(Article::class)
            ->operations('publish', 'archive')
            ->autoDiscoverOperations(); // Auto-finds can* methods
    }
}

final class ArticleVoter extends CrudVoter
{
    public function __construct(private readonly Security $security)
    {
        $this->setPrefix('article');
        $this->setResourceClasses(Article::class);
    }
}

#[Secured(
    prefix: 'blog_post',
    voter: BlogPostVoter::class
)]
class Article { }

public function __construct(
    private readonly Security $security,
) {
    $this->setPrefix('article');
    $this->setResourceClasses(Article::class);
}

protected function canUpdate(mixed $object, mixed $previousObject): bool
{
    $user = $this->security->getUser();
    return $user && $object->getAuthor() === $user;
}

protected function canUpdate(mixed $object, mixed $previousObject): bool
{
    // Prevent changing the author
    if ($object->getAuthor() !== $previousObject->getAuthor()) {
        return $this->security->isGranted('ROLE_ADMIN');
    }
    
    return $object->getAuthor() === $this->security->getUser();
}

public function __construct()
{
    $this->setPrefix('content');
    $this->setResourceClasses(Article::class, BlogPost::class, Page::class);
}

use Nexara\ApiPlatformVoter\GraphQL\GraphQLCrudVoter;

final class ArticleVoter extends GraphQLCrudVoter
{
    protected function canAccessField(string $fieldName, mixed $object): bool
    {
        return match ($fieldName) {
            'email' => $this->security->isGranted('ROLE_ADMIN'),
            'internalNotes' => $object->getAuthor() === $this->security->getUser(),
            default => true,
        };
    }
    
    protected function canModifyField(string $fieldName, mixed $object, mixed $newValue): bool
    {
        return match ($fieldName) {
            'author' => $this->security->isGranted('ROLE_ADMIN'),
            'publishedAt' => $this->security->isGranted('ROLE_MODERATOR'),
            default => true,
        };
    }
}

use Nexara\ApiPlatformVoter\Voter\CrudVoter;
use Nexara\ApiPlatformVoter\MultiTenancy\TenantAwareVoterTrait;

final class ArticleVoter extends CrudVoter
{
    use TenantAwareVoterTrait;
    
    protected function canUpdate(mixed $object, mixed $previousObject): bool
    {
        // TenantContext is automatically injected
        if (!$this->belongsToCurrentTenant($object)) {
            return false;
        }
        
        return $object->getAuthor() === $this->security->getUser();
    }
}

use Nexara\ApiPlatformVoter\Debug\VoterChainVisualizer;

$visualizer = new VoterChainVisualizer($debugger);

// Text visualization
echo $visualizer->visualize('article:update');

// Tree visualization
echo $visualizer->visualizeAsTree('article:update');

// Summary
echo $visualizer->summarize('article:update');

use Nexara\ApiPlatformVoter\Testing\VoterTestTrait;
use PHPUnit\Framework\TestCase;

class ArticleVoterTest extends TestCase
{
    use VoterTestTrait;
    
    public function testModeratorCanPublish(): void
    {
        $user = $this->createUser(['ROLE_MODERATOR']);
        
        // Creates Security with proper role hierarchy
        $security = $this->createSecurityWithRoleHierarchy([
            'ROLE_ADMIN' => ['ROLE_MODERATOR', 'ROLE_USER'],
            'ROLE_MODERATOR' => ['ROLE_USER'],
        ], $user);
        
        $voter = new ArticleVoter($security);
        
        // Now $security->isGranted('ROLE_USER') returns true for MODERATOR
        $article = new Article();
        $this->assertTrue($voter->canPublish($article, null));
    }
}

use Nexara\ApiPlatformVoter\Testing\SecurityBuilder;

$security = SecurityBuilder::create()
    ->withRoleHierarchy([
        'ROLE_ADMIN' => ['ROLE_MODERATOR', 'ROLE_USER'],
        'ROLE_MODERATOR' => ['ROLE_USER'],
    ])
    ->withUser($user)
    ->build();

$voter = new ArticleVoter($security);

use Nexara\ApiPlatformVoter\Testing\VoterTestCase;

class ArticleVoterTest extends VoterTestCase
{
    protected function createVoter(): VoterInterface
    {
        return new ArticleVoter($this->createMock(Security::class));
    }
    
    public function testGrantsAccess(): void
    {
        $this->mockUser(['ROLE_USER']);
        $this->assertVoterGrants('article:create', new Article());
    }
    
    public function testDeniesAccess(): void
    {
        $this->mockAnonymousUser();
        $this->assertVoterDenies('article:delete', new Article());
    }
}
bash
php bin/console make:api-resource-voter
bash
php bin/console voter:analyze-migration