Weave 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

Parameter Objects Bundle Laravel Package

c33s/parameter-objects-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require c33s/parameter-objects-bundle
    

    Add the bundle to config/bundles.php in Symfony:

    return [
        // ...
        C33s\ParameterObjectsBundle\C33sParameterObjectsBundle::class => ['all' => true],
    ];
    
  2. Define a Parameter Object Create a class in src/ParameterObject/ (or your preferred namespace) with a constructor accepting the raw parameter value:

    namespace App\ParameterObject;
    
    class UserLimitParameters
    {
        public function __construct(
            private int $maxResults,
            private ?int $offset = null,
        ) {}
    }
    
  3. First Use Case Replace a raw Symfony parameter (e.g., in config/packages/app.yaml):

    # Before
    services:
        App\Service\UserService:
            arguments:
                $maxResults: '%app.user_limit%'
    
    # After
    services:
        App\Service\UserService:
            arguments:
                $userLimitParameters: '@App\ParameterObject\UserLimitParameters'
    

    Define the parameter in config/packages/app.yaml:

    parameters:
        app.user_limit.max_results: 100
        app.user_limit.offset: null
    

Implementation Patterns

1. Autowiring Integration

  • Constructor Injection: Leverage Symfony’s autowiring to inject parameter objects directly:
    class UserService
    {
        public function __construct(
            private UserLimitParameters $userLimitParameters,
        ) {}
    }
    
  • Service Configuration: Use the bundle’s parameter_objects key in config/services.yaml:
    services:
        App\ParameterObject\UserLimitParameters:
            arguments:
                $maxResults: '%app.user_limit.max_results%'
                $offset: '%app.user_limit.offset%'
    

2. Validation and Type Safety

  • Runtime Validation: Use PHP 8 attributes (e.g., #[Assert\Type]) or custom logic in the constructor:
    public function __construct(
        private int $maxResults,
        private ?int $offset = null,
    ) {
        if ($maxResults <= 0) {
            throw new \InvalidArgumentException('Max results must be positive.');
        }
    }
    
  • Symfony Validator Integration: Combine with Symfony’s validator for form/parameter validation:
    # config/validator/validation.yaml
    App\ParameterObject\UserLimitParameters:
        properties:
            maxResults:
                - Positive:
                    message: 'Max results must be greater than 0.'
    

3. Dynamic Parameter Resolution

  • Environment-Aware Objects: Use %kernel.environment% to switch parameter sets:
    class DatabaseConfig
    {
        public function __construct(
            private string $dsn,
            private array $options,
        ) {}
    
        public static function createFromEnvironment(): self
        {
            return new self(
                dsn: '%env(DB_DSN)%',
                options: [
                    'driver' => '%env(DB_DRIVER)%',
                    'port' => '%env(int:DB_PORT)%',
                ],
            );
        }
    }
    
    Register as a service:
    services:
        App\ParameterObject\DatabaseConfig:
            factory: ['App\ParameterObject\DatabaseConfig', 'createFromEnvironment']
    

4. Legacy Parameter Migration

  • Hybrid Approach: Gradually replace raw parameters by introducing parameter objects as new services:
    # config/packages/app.yaml
    parameters:
        app.legacy_max_results: 50  # Old way
        app.user_limit.max_results: 100  # New way
    
    services:
        App\Service\UserService:
            arguments:
                $legacyMaxResults: '%app.legacy_max_results%'  # Temporary
                $userLimitParameters: '@App\ParameterObject\UserLimitParameters'  # New
    

Gotchas and Tips

Pitfalls

  1. Circular Dependencies:

    • Avoid injecting parameter objects into other parameter objects during construction. Use lazy loading or factories if needed.
    • Example of bad practice:
      class PaginationParameters {
          public function __construct(private UserLimitParameters $userLimit) {}
      }
      
    • Fix: Use a factory method or resolve dependencies via the container.
  2. Parameter Name Mismatches:

    • Ensure YAML parameter names (e.g., %app.user_limit.max_results%) match the constructor argument names exactly. The bundle does not auto-map by type alone.
    • Debug Tip: Use bin/console debug:container App\ParameterObject\* to verify service configuration.
  3. Immutable Objects:

    • If your parameter objects are immutable (no setters), ensure all required values are provided in the constructor. Missing optional values (e.g., null) may lead to ArgumentCountError.
    • Workaround: Provide default values in the constructor:
      public function __construct(
          private int $maxResults = 10,
          private ?int $offset = null,
      ) {}
      
  4. Caching Quirks:

    • Parameter objects are compiled into services. Changes to their constructor arguments require a cache clear:
      php bin/console cache:clear
      
    • Tip: Use dump-autoload after modifying parameter objects:
      composer dump-autoload
      

Debugging Tips

  1. Service Dumping: Inspect the resolved parameter object:

    php bin/console debug:container App\ParameterObject\UserLimitParameters
    

    Look for arguments to verify injected values.

  2. Parameter Validation: Use Symfony’s parameter validator to catch issues early:

    php bin/console debug:config parameter_objects
    
  3. Logging: Add temporary logging in the constructor to trace values:

    public function __construct(private int $maxResults) {
        \Symfony\Component\Debug\Debug::assert(
            $maxResults > 0,
            sprintf('Invalid maxResults: %d', $maxResults)
        );
    }
    

Extension Points

  1. Custom Factories: Override parameter object creation via a factory service:

    services:
        App\ParameterObject\DynamicUserLimitParameters:
            factory: ['App\Factory\UserLimitFactory', 'createFromRequest']
            arguments:
                $requestStack: '@request_stack'
    
  2. Event Listeners: Validate or modify parameter objects before injection using Symfony events:

    namespace App\EventListener;
    
    use Symfony\Component\HttpKernel\Event\ControllerEvent;
    
    class ParameterValidatorListener
    {
        public function onKernelController(ControllerEvent $event): void
        {
            $userLimit = $event->getController()[0]?->getUserLimitParameters();
            if ($userLimit->getMaxResults() > 1000) {
                throw new \RuntimeException('Max results too high!');
            }
        }
    }
    

    Register the listener in config/services.yaml:

    services:
        App\EventListener\ParameterValidatorListener:
            tags:
                - { name: kernel.event_listener, event: kernel.controller }
    
  3. Compiler Passes: Dynamically generate parameter objects at compile time using Symfony’s compiler passes. Example:

    namespace App\DependencyInjection\Compiler;
    
    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    
    class ParameterObjectPass implements CompilerPassInterface
    {
        public function process(ContainerBuilder $container): void
        {
            $definition = $container->findDefinition('App\ParameterObject\DynamicConfig');
            $definition->addMethodCall('setEnvironment', ['%kernel.environment%']);
        }
    }
    

    Register the pass in src/Kernel.php:

    protected function build(ContainerBuilder $container): void
    {
        $container->addCompilerPass(new ParameterObjectPass());
    }
    
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core