Potherca
9/20/2017 - 8:27 AM

Example of how a trait can be used to add secondary functionality to classes in Symfony.

Example of how a trait can be used to add secondary functionality to classes in Symfony.

<?php

namespace Potherca\Example;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class Website extends Controller
{
    /**
     * Comment out the line below (and restart the server) and the logger will
     * not be added to this class by the dependency injection container
     */
    use LoggerAwareTrait;

    /**
     * @Sensio\Bundle\FrameworkExtraBundle\Configuration\Route("/")
     *
     * @return Response
     *
     * @throws \InvalidArgumentException
     */
    final public function homepage(): Response
    {
        $loggerName = 'undefined';

        if (isset($this->logger)) {
            $loggerName = is_object($this->logger)
                ? get_class($this->logger)
                : gettype($this->logger);
        }

        return new Response(
            sprintf(
                '<p>Logger = <code>%s</code></p>',
                $loggerName
            )
        );
    }
}

/*EOF*/
services:
  Potherca\Example\Website:
    autowire: true
<?php

namespace Potherca\Example;

use Psr\Log\LoggerInterface;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @required
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}

/*EOF*/
<?php

/**
 * This file can be ignored, it is just here to provide a working example using Katwizy
 */

$loader = require __DIR__.'/vendor/autoload.php';

Potherca\Katwizy\Bootstrap::run(
    $loader,
    Symfony\Component\HttpFoundation\Request::createFromGlobals()
);

/*EOF*/
{
    "require": {
        "php": "^5.4|^7.0",
        "potherca/katwizy": "^0.5"
    },
    "autoload": {
        "files": [
            "logger-aware-trait.php",
            "website.php"
        ]
    }
}

Introduction

Example of how a trait can be used to add secondary functionality to classes in Symfony.

(The names of the class files have been changed to lower-case so the this file is shown first in the gist.)

Rational

The Symfony framework offers functionality to "autowire" objects. This means that the framework creates objects and automatically injects any dependencies the object might need.

The most common form is simply to add dependencies as a parameter to a class' constructor with a type-hint.

However, over time, the constructor can become rather large and the object becomes polluted with properties that do not really influence it's behaviour.

A common example of this is logging.

Instead of adding yet another parameter, a trait can be used. That way the class code and constructor parameters are kept clean.

Then, using Symfony's dependency-injection container, the logger can be added to the object when instantiated automagically.

How it works

The services.yml configuration tells Symfony to auto-wire the main "controller" style class in this example.

That class uses the LoggerAwareTrait.

The LoggerAwareTrait contains a setter. There are several ways auto-wiring for setter calls can be achieved. The easiest is to simply add a @required annotation to the setter.

Auto-wiring will automatically call any method with the @required annotation above it, auto-wiring each argument. That way it is not necessary to manually wire each class that uses the LoggerAwareTrait.

For full details see: https://symfony.com/doc/current/service_container/autowiring.html#autowiring-other-methods-e-g-setters

The LoggerAwareTrait is an ad-verbatim copy of Psr\Log\LoggerAwareTrait, except for the @required annotation on the setLogger method.

Running the example

In order to keep this gist both work and small and to the point, Katwizy is used.

To see the example in action:

  1. Clone this repository
  2. Step into the repository folder
  3. Install the dependencies
  4. Start a web-server

These steps can be achieved with the following commands:

git clone git@gist.github.com:7b1ffe7e18098bc89931fe59c30642ff.git symfony-trait-autowiring-example
cd $_
composer install
php -S localhost:8080 -t ./

Visiting http://localhost:8080/ in the browser will show a page stating

Logger = Symfony\Bridge\Monolog\Logger

To remove the logger from the main class:

  1. Stop the web-server

  2. Remove (or comment out) the line stating:

     use LoggerAwareTrait;
    
  3. Remove the class cache \rm -rdf ./var/

  4. Start the web-server again php -S localhost:8080 -t ./