• Stars
    star
    245
  • Rank 159,258 (Top 4 %)
  • Language
    PHP
  • License
    MIT License
  • Created about 9 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Bundle to integrate Tactician with Symfony projects

TacticianBundle

Build Status Scrutinizer Code Quality

Symfony2 Bundle for the Tactician library https://github.com/thephpleague/tactician/

Installation

Step 1: Download the Bundle

Open a command console, enter your project directory and execute the following command to download the latest stable release for this bundle:

$ composer require league/tactician-bundle

This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.

Step 2: Enable the Bundle

Then, enable the bundle by adding it to the list of registered bundles in the app/AppKernel.php file of your project:

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...

            new League\Tactician\Bundle\TacticianBundle(),
        );

        // ...
    }

    // ...
}

Using the Command Bus

Create a service and inject the command bus:

services:
    app.your_controller:
        class: AppBundle\Controller\YourNameController
        arguments:
            - '@tactician.commandbus'

Then pass a command to the command bus for execution:

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;
use AppBundle\Commands\DoSomethingCommand;

class YourNameController
{
    private $commandBus;

    public function __construct(CommandBus $commandBus)
    {
        $this->commandBus = $commandBus;
    }

    public function doSomething()
    {
        $command = new DoSomethingCommand();
        $this->commandBus->handle($command);
    }
}

Autowiring

If Symfony autowire feature is enabled (available from Symfony 2.8), instead of creating a service for each controller using the default commandbus you can inject and use it as follows:

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;
use AppBundle\Commands\DoSomethingCommand;

class YourNameController
{
    public function doSomething(CommandBus $commandBus)
    {
        $command = new DoSomethingCommand();
        $commandBus->handle($command);
    }
}

Note that this only works for the default commandbus, if you want to inject other than the default one you can override the config through an alias with:

services:
    League\Tactician\CommandBus: '@tactician.commandbus.your_commandbus'

If you have multiple buses, you can use named parameter aliases by using the bus name as part of the parameter name. For example, if you want to inject the command bus named default, name the parameter defaultBus. The syntax always follows {bus_name}Bus.

This feature is only available from Symfony 4.2

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;

class YourNameController
{
    public function doSomething(CommandBus $defaultBus)
    {
        //
    }
}

Configuring Command Handlers

When you pass a command to Tactician, the ultimate goal is to have it mapped to a Handler.

Since handlers often have extra dependencies and are best lazily-loaded, you'll want to register them in the service container.

There's a few different ways to map your Commands to a Handler, all of which can be combined. We'll walk through them below:

1. Manually Mapping

Let's say we have two classes, RegisterUserCommand and RegisterUserHandler. We'll register the Handler in the service container, along with a repository it needs.

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'

However, we still need to map the Command to the Handler. We can do this by adding a tag to the Handler's DI definition.

The tag should have two attributes: the tag name, which should always be tactician.handler, and the command, which should be the FQCN of the Command.

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'
    tags:
        - { name: tactician.handler, command: Foo\User\RegisterUserCommand }

2. Map Based On Typehints

Rather than repeating the command's full class name, we can also reflect on the Handler's method typehints.

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'
    tags:
        - { name: tactician.handler, typehints: true }

This detects what commands this handler receives by inspecting the class' methods. The rules for matching are:

  1. The method must be public.
  2. The method must accept only one parameter.
  3. The parameter must be typehinted with a class name.

In other words, the RegisterUserHandler class should look like this:

<?php
class RegisterUserHandler
{
    public function handle(RegisterUser $command)
    {
       // do stuff
    }
}

If you have multiple commands going into a single handler, they will all be detected, provided they follow the rules above. The actual name of the method is NOT important.

If you're using typehints AND FQCN mappings, then the FQCN mapping always wins out.

Registering by typehints can be very useful if you're using the autowiring features in the latest versions of Symfony.

3. Custom Mapping Rules

If you'd like to define your own rules for automatically mapping commands to handlers in the container, you can do that as well.

First, implement the HandlerMapping interface. During compile time, you'll receive a ContainerBuilder and a Tactician Router object you can use to map your commands to your handler services.

There's a good chance that your strategy will involve using container tags of some sort. If that's the case, look into extending the TagBasedMapping abstract class. This will save you some of the boiler plate associated with handling multiple buses.

Once your object is ready, pass it to the TacticianBundle instance when setting your AppKernel.php:

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new League\Tactician\Bundle\TacticianBundle(
                new My\Custom\HandlerMapping()
            ),
        );
    }
}

4. Combining Mapping Strategies

If you have multiple strategies you'd like to chain together, you can use the CompositeMapping object to chain them together.

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new League\Tactician\Bundle\TacticianBundle(
                new League\Tactician\Bundle\DependencyInjection\HandlerMapping\CompositeMapping(
                    new League\Tactician\Bundle\DependencyInjection\HandlerMapping\ClassNameMapping(), // standard command: "FQCN" mapping
                    new League\Tactician\Bundle\DependencyInjection\HandlerMapping\TypeHintMapping(), // standard typehints: true mapping
                    new My\Custom\HandlerMapping() // your custom routing
                )
            ),
        );
    }
}

If multiple HandlerMapping strategies detect the same Command, but different Handlers, then the last mentioned mapping strategy wins. Therefore, it's usually best to put your custom strategy last OR the ClassNameMapping last so you can make full overrides when necessary.

5. Write Your Own Middleware

Remember, Tactician is based purely on middleware. If you don't want to mess around with all this and you have a simple convention based way of mapping commands to handlers, just write your own middleware to execute Handlers.

Checking your wiring

You can run the debug:tactician command to get a list of which commands are mapped to which services.

Configuring Middleware

Everything inside Tactician is a middleware plugin. Without any middleware configured, nothing will happen when you pass a command to $commandBus->handle().

By default, the only Middleware enabled is the Command Handler support. You can override this and add your own middleware in the app/config.yml.

tactician:
    commandbus:
        default:
            middleware:
                # service ids for all your middlewares, top down. First in, last out.
                - tactician.middleware.locking
                - my.custom.middleware.plugin
                - tactician.middleware.command_handler

Important: Adding your own middleware is absolutely encouraged, just be sure to always add tactician.middleware.command_handler as the final middleware. Otherwise, your commands won't actually be executed.

Check the Tactician docs for more info and a complete list of middleware.

Configuring Multiple Command Buses

The bundle is pre-configured with a command bus called "default", with the service id tactician.commandbus. Some users want to configure more than one command bus though.

Let's say you're integrating a remote accounting system into your application and you'd like to use a separate command bus for just those commands. You can wire up two command buses like this:

You can do this via configuration, like so:

tactician:
    commandbus:
        default:    # the "regular" command bus in your application
            middleware:
                - tactician.middleware.validator
                - tactician.middleware.command_handler
        accounting: # the command bus for accounting specific commands
            middleware:
                - tactician.middleware.locking
                - some.middleware.service.to.call.the.remote.accounting.app
                - tactician.commandbus.accounting.middleware.command_handler # Because "tactician.middleware.command_handler" refers to the default bus

The configuration defines two buses: "default" and "accounting". These buses will be registered as the tactician.commandbus.default and tactician.commandbus.accounting services respectively.

Take note that each bus now has configured their own command handler middleware: tactician.middleware.command_handler for the default and tactician.commandbus.accounting.middleware.command_handler for the account one.

If you want, you can also change which command handler is registered to tactician.commandbus. You can do this by setting the default_bus value in the configuration, like so:

tactician:
    default_bus: accounting
    commandbus:
        default:
            middleware:
                # ...
        accounting:
            middleware:
                # ...

By default, all commands are available in each bus. If you want to make a command available only to a specific bus, you need to specify its id :

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'
    tags:
        - { name: tactician.handler, command: Foo\User\RegisterUserCommand, bus: accounting }

and you will be able to handle this command only on the accounting bus:

$bus = $container->get('tactician.commandbus.accounting');
$bus->handle(new Foo\User\RegisterUserCommand('my', 'arguments'));

Extra Bundled Middleware

This bundles ships with a few pre-configured middlewares. To enable them, add them to the middlewares list in your bus configuration (see Configuring Middleware)

Validator Middleware (tactician.middleware.validator)

This middleware uses Symfony's validator to check the command object before passing it along. In practice, this means you can add any Symfony validator annotations to your command to ensure it's fully correct before execution. This isn't a full replacement for writing your objects in an internally consistent style but it can be very helpful.

Constraints can be added via configuration or annotations like in default Symfony practices, please refer to their docs.

If the command fails, it will throw a League\Tactician\Bundle\Middleware\InvalidCommandException. This exception also contains the ConstraintViolationList produced by the validator so you can inspect or log the errors yourself.

Locking Middleware (tactician.middleware.locking)

This middleware is bundled in Tactician, please refer to the official documentation for details.

Logger Middleware (tactician.middleware.logger)

This middleware is bundled in Tactician, please refer to the official documentation for details.

Security Middleware (tactician.middleware.security)

The security middleware will perform authorization on handling all commands. By default an AccessDenied exception will be thrown if the user is not authorized.

tactician:
    security:
        My\User\Command:
            - 'ROLE_USER'
        My\Admin\Command:
            - 'ROLE_ADMIN'
        My\UserAndAdmin\Command:
            - 'ROLE_USER'
            - 'ROLE_ADMIN'

This middleware is based on Symfony's AccessDecisionManager and voter system. We recommend familiarizing yourself with it before trying to use this middleware. If you'd like to configure more complex scenarios, consider implementing a custom Symfony voter.

As a precaution, the middleware requires you to register the command in the security configuration before it will be evaluated by Symfony's AccessDecisionManager.

Furthermore, while the security middleware is based on trusted components, we always recommend a defense in depth strategy. Simply hooking your command bus up to a public web endpoint and relying fully on this middleware may not be sufficient coverage for your application.

The Security middleware is disabled by default.

Command Handler Middleware (tactician.middleware.command_handler)

Always ensure this is the last middleware listed

This is the plugin that actually matches your command to a handler and executes it. If you have complex matching logic, feel free to implement your own variant and leave this middleware off.

However, for 99% of users, this should be enabled and set as the last middleware in the list.

Customizing the MethodNameInflector used by the tactician.middleware.command_handler middleware

By default, the bundle uses HandleInflector from Tactician core. That is to say, it expects your Command Handlers to have a handle() method that receives the command to execute.

However, if you prefer a different inflector, you can pass the service name in config.yml.

tactician:
    method_inflector: my_inflector.service.id

Tactician core offers a list of custom Inflectors, all of which are supported in this bundle. Assuming a class called My\App\RegisterUserCommand(), the invoked methods on the handler would be:

  • tactician.handler.method_name_inflector.handle - handle()
  • tactician.handler.method_name_inflector.handle_class_name - handleRegisterUserCommand()
  • tactician.handler.method_name_inflector.handle_class_name_without_suffix - handleRegisterUser()
  • tactician.handler.method_name_inflector.invoke - __invoke()

While handle() is a reasonable default, using one of the class name methods allows you to handle multiple commands on a single class (possibly useful if they share common dependencies or fit together in some fashion). Likewise, __invoke can be useful if you're mapping to a list of closures.

When using multiple buses, you can also specify the method_inflector of particular bus :

tactician:
    commandbus:
        command:
            middleware:
                - tactician.middleware.command_handler
        query:
            middleware:
                - tactician.middleware.command_handler
            method_inflector: tactician.handler.method_name_inflector.handle_class_name_without_suffix

Testing

$ ./vendor/bin/phpunit

Security

Disclosure information can be found on the main Tactician repo.

License

The MIT License (MIT). Please see License File for more information.

More Repositories

1

flysystem

Abstraction for local and remote filesystems
PHP
13,202
star
2

oauth2-server

A spec compliant, secure by default PHP OAuth 2.0 Server
PHP
6,362
star
3

omnipay

A framework agnostic, multi-gateway payment processing library for PHP 5.6+
PHP
5,813
star
4

fractal

Output complex, flexible, AJAX/RESTful data structures.
PHP
3,511
star
5

oauth2-client

Easy integration with OAuth 2.0 service providers.
PHP
3,508
star
6

csv

CSV data manipulation made easy in PHP
PHP
3,282
star
7

commonmark

Highly-extensible PHP Markdown parser which fully supports the CommonMark and GFM specs.
PHP
2,655
star
8

glide

Wonderfully easy on-demand image manipulation library with an HTTP based API.
PHP
2,527
star
9

climate

PHP's best friend for the terminal.
PHP
1,864
star
10

html-to-markdown

Convert HTML to Markdown with PHP
PHP
1,619
star
11

flysystem-aws-s3-v3

[READYONLY SUB-SPLIT]Flysystem Adapter for AWS SDK V3
PHP
1,528
star
12

skeleton

A skeleton repository for League Packages
PHP
1,525
star
13

event

Event package for your app and domain
PHP
1,504
star
14

plates

Native PHP template system
PHP
1,468
star
15

geotools

Geo-related tools PHP 7.3+ library built atop Geocoder and React libraries
PHP
1,352
star
16

color-extractor

Extract colors from an image like a human would do.
PHP
1,283
star
17

mime-type-detection

League Mime Type Detection
PHP
1,218
star
18

uri

[READ-ONLY] URI manipulation Library
PHP
1,013
star
19

pipeline

League\Pipeline
PHP
939
star
20

oauth1-client

OAuth 1 Client
PHP
936
star
21

tactician

A small, flexible command bus
PHP
854
star
22

container

Small but powerful dependency injection container
PHP
829
star
23

period

PHP's time range API
PHP
714
star
24

route

Fast PSR-7 based routing and dispatch component including PSR-15 middleware, built on top of FastRoute.
PHP
638
star
25

iso3166

A PHP library providing ISO 3166-1 data.
PHP
629
star
26

factory-muffin

Enables the rapid creation of objects for testing
PHP
533
star
27

openapi-psr7-validator

It validates PSR-7 messages (HTTP request/response) against OpenAPI specifications
PHP
500
star
28

uri-interfaces

League URI Interfaces
PHP
439
star
29

shunt

[ABANDONED] PHP library for executing commands on multiple remote machines, via SSH
PHP
436
star
30

config

Simple yet expressive schema-based configuration library for PHP apps
PHP
436
star
31

uri-parser

RFC3986/RFC3987 compliant URI parser
PHP
392
star
32

oauth2-google

Google Provider for the OAuth 2.0 Client
PHP
383
star
33

flysystem-cached-adapter

Flysystem Adapter Cache Decorator.
PHP
356
star
34

statsd

A library for working with StatsD
PHP
351
star
35

flysystem-bundle

Symfony bundle integrating Flysystem into Symfony 4.2+ applications
PHP
350
star
36

url

A simple PHP library to parse and manipulate URLs
PHP
347
star
37

booboo

A modern error handler capable of logging and formatting errors in a variety of ways.
PHP
338
star
38

monga

Simple and swift MongoDB abstraction.
PHP
328
star
39

omnipay-common

Core components for the Omnipay PHP payment processing library
PHP
327
star
40

flysystem-sftp

[READ-ONLY SUBSPLIT] Flysystem Adapter for SFTP
PHP
310
star
41

uri-components

[READ-ONLY] League URI components objects
PHP
305
star
42

oauth2-facebook

Facebook Provider for the OAuth 2.0 Client
PHP
297
star
43

omnipay-paypal

PayPal driver for the Omnipay PHP payment processing library
PHP
291
star
44

uri-schemes

Collection of URI Immutable Value Objects
PHP
215
star
45

uri-manipulations

Functions and Middleware to manipulate URI Objects
PHP
198
star
46

uri-hostname-parser

A lightweight hostname parser according to public suffix list ICANN section
PHP
195
star
47

omnipay-stripe

Stripe driver for the Omnipay PHP payment processing library
PHP
179
star
48

json-guard

Validation of json-schema.org compliant schemas.
PHP
175
star
49

oauth2-server-bundle

Symfony bundle for the OAuth2 Server.
PHP
170
star
50

commonmark-ext-table

The table extension for CommonMark PHP implementation
PHP
127
star
51

flysystem-local

PHP
117
star
52

glide-laravel

Glide adapter for Laravel
PHP
111
star
53

oauth2-github

GitHub Provider for the OAuth 2.0 Client
PHP
103
star
54

flysystem-ziparchive

Flysystem Adapter for ZipArchive's
PHP
102
star
55

omnipay-example

Example application for Omnipay PHP payments library
PHP
97
star
56

glide-symfony

Glide adapter for Symfony
PHP
91
star
57

oauth2-linkedin

LinkedIn Provider for the OAuth 2.0 Client
PHP
81
star
58

tactician-container

Load Tactician handlers from any PSR-11/container-interop container
PHP
75
star
59

stack-attack

StackPHP Middleware based on Rack::Attack
PHP
74
star
60

flysystem-webdav

[READ ONLY] WebDAV adapter for Flysystem
PHP
70
star
61

flysystem-memory

Flysystem Memory Adapter
PHP
69
star
62

flysystem-dropbox

Flysystem Adapter for Dropbox [ABANDONED] replacement: https://packagist.org/packages/spatie/flysystem-dropbox
PHP
67
star
63

stack-robots

StackPHP middleware providing robots.txt disallow for non-production environments
PHP
67
star
64

oauth2-instagram

Instagram Provider for the OAuth 2.0 Client
PHP
65
star
65

tactician-logger

Adds PSR-3 logging support to the Tactician Command Bus
PHP
62
star
66

omnipay-mollie

Mollie driver for the Omnipay PHP payment processing library
PHP
61
star
67

di

An Ultra-Fast Dependency Injection Container. DEPRECATED
PHP
58
star
68

tactician-doctrine

Tactician plugins for the Doctrine ORM, primarily transactions
PHP
57
star
69

omnipay-authorizenet

Authorize.Net driver for the Omnipay payment processing library
PHP
57
star
70

flysystem-azure-blob-storage

PHP
54
star
71

omnipay-sagepay

Sage Pay driver for the Omnipay PHP payment processing library
PHP
53
star
72

flysystem-aws-s3-v2

Flysystem Adapter for AWS SDK V2
PHP
50
star
73

DEPRECATED-squery

PHP wrapper for osquery
PHP
49
star
74

phpunit-coverage-listener

Report code coverage statistics to third-party services
PHP
48
star
75

thephpleague.github.io

The League of Extraordinary Packages website
SCSS
45
star
76

construct-finder

PHP code construct finder
PHP
40
star
77

factory-muffin-faker

A wrapper around faker for factory muffin
PHP
39
star
78

flysystem-rackspace

Flysystem Adapter for Rackspace
PHP
38
star
79

uri-query-parser

a parser and a builder to work with URI query string the right way in PHP
PHP
37
star
80

flysystem-azure

Flysystem adapter for the Windows Azure.
PHP
35
star
81

omnipay-braintree

Braintree Driver for Omnipay Gateway
PHP
34
star
82

json-reference

A library for working with JSON References.
PHP
33
star
83

commonmark-extras

Useful extensions for the league/commonmark parser
PHP
28
star
84

flysystem-sftp-v3

PHP
28
star
85

uploads

Receive, validate, and distribute uploaded files.
PHP
27
star
86

flysystem-replicate-adapter

Flysystem Adapter Decorator for Replicating Filesystems.
PHP
25
star
87

omnipay-dummy

Dummy driver for the Omnipay PHP payment processing library
PHP
25
star
88

omnipay-worldpay

WorldPay driver for the Omnipay PHP payment processing library
PHP
24
star
89

omnipay-paymentexpress

PaymentExpress driver for the Omnipay PHP payment processing library
PHP
24
star
90

object-mapper

PHP
22
star
91

uri-src

URI manipulation Library
PHP
21
star
92

flysystem-google-cloud-storage

PHP
21
star
93

flysystem-async-aws-s3

PHP
21
star
94

omnipay-migs

MIGS driver for the Omnipay PHP payment processing library
PHP
21
star
95

flysystem-ftp

[SUB-SPLIT] Flysystem FTP Adapter
PHP
21
star
96

omnipay-firstdata

First Data driver for the Omnipay PHP payment processing library
PHP
21
star
97

omnipay-payfast

PayFast driver for the Omnipay PHP payment processing library
PHP
21
star
98

tactician-bernard

Tactician integration with the Bernard queueing library
PHP
20
star
99

omnipay-multisafepay

MultiSafepay driver for the Omnipay PHP payment processing library
PHP
19
star
100

flysystem-gridfs

GridFS Adapter for Flysystem
PHP
19
star