Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Api Scope Bundle Laravel Package

bartlomiejbeta/api-scope-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require bartlomiejbeta/api-scope-bundle
    

    Register the bundle in config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 3):

    BartB\APIScopeBundle\APIScopeBundle::class => ['all' => true],
    
  2. Basic Configuration: Add to config/packages/api_scope.yaml:

    api_scope:
        scopes:
            api.get_item:  # Route name
                always_included:
                    - 'group1'
                    - 'group2'
                supported_key_map:
                    external1: { internal_name: 'scope.internal_name1' }
    
  3. First Use Case: Annotate your API controller method with @ScopeConverter() and @Rest\Route:

    use BartB\APIScopeBundle\Annotation\ScopeConverter;
    use Nelmio\ApiDocBundle\Annotation\Rest;
    
    /**
     * @ScopeConverter()
     * @Rest\Route("/items", name="api.get_items")
     */
    public function getItems(Request $request, ScopeCollection $scopeCollection): Response
    {
        // $scopeCollection now contains merged scopes from query params and config
        return $this->json($this->serializer->serialize($items, 'json', $scopeCollection));
    }
    
  4. Trigger Scopes via Query: Call your API with query params like:

    /items?external1=true
    

    This will add scope.internal_name1 to the serialization groups.


Implementation Patterns

Workflows

  1. Dynamic Serialization Groups: Use supported_key_map to map query params to Doctrine serialization groups. Example:

    supported_key_map:
        user_details: { internal_name: 'user.details' }
        admin_data:   { internal_name: 'admin.data', security: 'ROLE_ADMIN' }
    

    Call with ?user_details=true&admin_data=true to include both groups.

  2. Security-Constrained Scopes: Leverage Symfony’s security voters to restrict sensitive scopes:

    admin_data:
        internal_name: 'admin.data'
        security: 'can_access_admin_data'  # Custom voter
    

    The bundle checks voters before applying the scope.

  3. Always-Included Groups: Define groups that are always included, regardless of query params:

    always_included:
        - 'public.metadata'
    
  4. Integration with Serializer: Inject ScopeCollection into your controller/action and pass it to the serializer:

    $data = $this->serializer->serialize($entity, 'json', $scopeCollection);
    
  5. Route-Specific Scopes: Configure scopes per route in api_scope.scopes. Example for nested routes:

    api.get_user:
        always_included: ['user.basic']
        supported_key_map:
            full_profile: { internal_name: 'user.full_profile' }
    

Tips for Daily Use

  • Query Param Naming: Use kebab-case for external keys (e.g., user-details) for consistency.
  • Default Values: Set always_included for mandatory groups (e.g., ['public.id']).
  • Debugging: Check ScopeCollection contents in your controller to verify merged scopes:
    dump($scopeCollection->getAll());
    
  • Caching: If using Symfony’s serializer cache, scopes are automatically cached per request.

Gotchas and Tips

Pitfalls

  1. Route Name Mismatch:

    • The bundle matches scopes by route name, not URL path. Ensure @Rest\Route names match your config.yaml keys.
    • Fix: Verify route names with php bin/console debug:router.
  2. Security Voter Failures:

    • If a scope requires a voter (e.g., security: 'ROLE_ADMIN'), the bundle silently ignores it if the voter fails.
    • Debug: Check Symfony’s security events or log voter results:
      $token = $this->get('security.token_storage')->getToken();
      $voter = $this->get('security.authorization_checker');
      $voter->isGranted('can_access_admin_data', $entity);
      
  3. Query Param Case Sensitivity:

    • Query keys are case-sensitive (e.g., ?External1=true won’t match external1).
    • Fix: Normalize keys in supported_key_map or use a router event listener.
  4. Serializer Group Conflicts:

    • If internal_name in supported_key_map conflicts with existing groups, the bundle appends them without merging.
    • Fix: Use unique prefixes (e.g., user.details.v1).
  5. Legacy Symfony Versions:

    • The bundle was last updated in 2018 and may lack Symfony 5/6 compatibility.
    • Workaround: Fork and update dependencies (e.g., symfony/serializer, symfony/security).

Debugging Tips

  • Log Scope Merging: Override the ScopeCollection service to log merged scopes:

    # config/services.yaml
    BartB\APIScopeBundle\Scope\ScopeCollection:
        class: App\Service\DebugScopeCollection
        decorates: 'bartb_api_scope.scope_collection'
    
    // src/Service/DebugScopeCollection.php
    class DebugScopeCollection extends ScopeCollection
    {
        public function add($scope)
        {
            $this->logger->info('Adding scope: '.$scope);
            parent::add($scope);
        }
    }
    
  • Validate Config: Use Symfony’s config validator to catch YAML errors early:

    php bin/console config:validate api_scope
    

Extension Points

  1. Custom Scope Providers: Extend the ScopeProviderInterface to add dynamic scopes (e.g., from headers or JWT claims):

    class HeaderScopeProvider implements ScopeProviderInterface
    {
        public function getScopes(Request $request): array
        {
            return ['header.group' => $request->headers->get('X-Group')];
        }
    }
    

    Register as a service and tag it with bartb_api_scope.scope_provider.

  2. Override Scope Merging: Decorate the ScopeCollection service to modify merging logic:

    // src/Service/CustomScopeCollection.php
    class CustomScopeCollection extends ScopeCollection
    {
        public function merge(array $scopes)
        {
            // Custom logic (e.g., prioritize certain groups)
            parent::merge($scopes);
        }
    }
    
  3. Add Security Voters: Create a voter for scope-specific permissions:

    class CanAccessAdminScopeVoter implements VoterInterface
    {
        public function vote(TokenInterface $token, $scope, array $attributes)
        {
            return $token->getUser()->hasRole('ROLE_ADMIN');
        }
    }
    

    Reference it in supported_key_map:

    admin_scope:
        internal_name: 'admin.data'
        security: 'can_access_admin_scope'
    
  4. Event Listeners: Listen to api_scope.scopes_merged to react to scope changes:

    // src/EventListener/ScopeListener.php
    class ScopeListener
    {
        public function onScopesMerged(ScopesMergedEvent $event)
        {
            if ($event->hasScope('user.details')) {
                $event->addScope('user.metadata'); // Auto-add related groups
            }
        }
    }
    

    Tag the listener with kernel.event_listener and set the event to bartb_api_scope.scopes_merged.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware