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

Action Bundle Laravel Package

dunglas/action-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require dunglas/action-bundle
    

    Register the bundle in config/bundles.php:

    return [
        // ...
        Dunglas\ActionBundle\DunglasActionBundle::class => ['all' => true],
    ];
    
  2. First Action Class: Create a class in src/Action/ (e.g., src/Action/HelloAction.php):

    namespace App\Action;
    
    use Symfony\Component\HttpFoundation\Response;
    
    class HelloAction
    {
        public function __invoke(): Response
        {
            return new Response('Hello, Action!');
        }
    }
    
  3. Routing: Define a route in config/routes.yaml:

    hello:
        path: /hello
        controller: App\Action\HelloAction
    
  4. Autowiring Dependencies: Inject services via constructor (e.g., src/Action/UserAction.php):

    namespace App\Action;
    
    use App\Service\UserService;
    use Symfony\Component\HttpFoundation\Response;
    
    class UserAction
    {
        private $userService;
    
        public function __invoke(UserService $userService): Response
        {
            $this->userService = $userService;
            return new Response($this->userService->getName());
        }
    }
    

First Use Case: REST API Endpoint

Replace a traditional controller with an action:

// src/Action/Api/UserAction.php
namespace App\Action\Api;

use App\Service\UserRepository;
use Symfony\Component\HttpFoundation\JsonResponse;

class UserAction
{
    public function __invoke(UserRepository $userRepo): JsonResponse
    {
        return new JsonResponse($userRepo->findAll());
    }
}

Route:

api_users:
    path: /api/users
    controller: App\Action\Api\UserAction

Implementation Patterns

1. Action Composition

Break down logic into smaller, reusable actions:

// src/Action/User/ShowAction.php
namespace App\Action\User;

use App\Service\UserFinder;
use Symfony\Component\HttpFoundation\Response;

class ShowAction
{
    public function __invoke(UserFinder $finder, int $id): Response
    {
        $user = $finder->find($id);
        return new Response($user->getName());
    }
}

Route with parameters:

user_show:
    path: /users/{id}
    controller: App\Action\User\ShowAction

2. Command-Line Actions

Leverage the same pattern for console commands:

// src/Action/Console/GenerateSitemapAction.php
namespace App\Action\Console;

use App\Service\SitemapGenerator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GenerateSitemapAction extends Command
{
    protected static $defaultName = 'app:generate-sitemap';

    public function __invoke(SitemapGenerator $generator, InputInterface $input, OutputInterface $output): int
    {
        $generator->generate();
        $output->writeln('Sitemap generated!');
        return Command::SUCCESS;
    }
}

Register as a service (automatically handled by the bundle).


3. Middleware Integration

Use Symfony’s middleware stack with actions:

// src/Action/Api/PostAction.php
namespace App\Action\Api;

use App\Middleware\AuthMiddleware;
use Symfony\Component\HttpFoundation\Response;

class PostAction
{
    public function __invoke(AuthMiddleware $auth): Response
    {
        $auth->check();
        return new Response('Authenticated!');
    }
}

Middleware is injected like any other service.


4. Event Dispatching

Dispatch events within actions:

// src/Action/User/RegisterAction.php
namespace App\Action\User;

use App\Event\UserRegisteredEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response;

class RegisterAction
{
    public function __invoke(EventDispatcherInterface $dispatcher, array $data): Response
    {
        $event = new UserRegisteredEvent($data);
        $dispatcher->dispatch($event);
        return new Response('User registered!');
    }
}

5. Form Handling

Integrate with Symfony Forms:

// src/Action/User/CreateAction.php
namespace App\Action\User;

use App\Form\UserType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class CreateAction
{
    public function __invoke(FormFactoryInterface $factory, Request $request): Response
    {
        $form = $factory->create(UserType::class);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            return new Response('Success!');
        }

        return new Response($form->createView());
    }
}

Gotchas and Tips

1. Service Autowiring

  • Pitfall: Forgetting to enable autowiring in config/services.yaml:
    # config/services.yaml
    parameters:
        container.autowiring.strict_mode: true
    
  • Tip: Use #[Autowire] attribute (PHP 8+) or annotations (@Autowire) for clarity:
    use Symfony\Component\DependencyInjection\Attribute\Autowire;
    
    class UserAction
    {
        #[Autowire]
        private UserService $userService;
    }
    

2. Route Configuration

  • Pitfall: Incorrect route controller syntax. The bundle expects class names as strings (not callables):
    # Wrong (Symfony 5+ style)
    controller: App\Action\UserAction::class::__invoke
    
    # Correct
    controller: App\Action\UserAction
    
  • Tip: Use YAML anchors for DRY routing:
    routes:
        user:
            path: /users
            controller: App\Action\User\ListAction
        user_show:
            path: /users/{id}
            controller: App\Action\User\ShowAction
    

3. Dependency Injection

  • Pitfall: Circular dependencies between actions/services will cause autowiring failures. Solution: Refactor to use interfaces or lazy-loading proxies.
  • Tip: Use null for optional dependencies:
    public function __invoke(?LoggerInterface $logger = null): Response
    {
        if ($logger) {
            $logger->info('Action invoked');
        }
        return new Response('OK');
    }
    

4. Error Handling

  • Pitfall: Uncaught exceptions in actions won’t trigger Symfony’s error handler by default. Solution: Wrap logic in a try-catch or use a global exception listener:
    // src/EventListener/ActionExceptionListener.php
    namespace App\EventListener;
    
    use Symfony\Component\HttpKernel\Event\ExceptionEvent;
    use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
    use Symfony\Component\HttpKernel\KernelEvents;
    
    class ActionExceptionListener
    {
        public function onKernelException(ExceptionEvent $event): void
        {
            if (!$event->getThrowable() instanceof HttpExceptionInterface) {
                $event->setResponse(new Response('An error occurred', 500));
            }
        }
    }
    
    Register the listener in config/services.yaml:
    services:
        App\EventListener\ActionExceptionListener:
            tags:
                - { name: kernel.event_listener, event: kernel.exception }
    

5. Performance

  • Tip: Cache action instances if they’re stateless:
    # config/services.yaml
    services:
        App\Action\HelloAction:
            public: true
            tags: ['controller.service_arguments']
    
  • Pitfall: Overusing actions for simple logic. For trivial cases, traditional controllers may be cleaner.

6. Testing

  • Tip: Mock dependencies in PHPUnit:
    public function testUserAction()
    {
        $userService = $this->createMock(UserService::class);
        $userService->method('getName')->willReturn('John Doe');
    
        $action = new UserAction();
        $response = $action($userService);
    
        $this->assertEquals('John Doe', $response->getContent());
    }
    
  • Pitfall: Forgetting to mock the Request object when testing actions that use it.

7. Migration from Controllers

  • Tip: Use a find/replace to convert controllers:
    # Replace:
    # class UserController extends AbstractController
    # with:
    # class UserAction
    
  • Pitfall: Forgetting to update route references (e.g., controller: App\Controller\UserController::indexActioncontroller: App\Action\UserAction).

8. Archived Status

  • Note: The package is archived (last release in 2017). Consider alternatives like:
    • Symfony’s built-in autowiring (no bundle needed).
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