Blog

A piece of PHP awesomeness.

by Nikola Nedic

When Composer came out in March 2012, everyone was immediately aware of its awesomeness, and all popular PHP frameworks started adding support for it. By the end of the year, it created a boom in the PHP community.

After all these years it only became even more popular, and there are more and more people using it and contributing every day, which is great for all PHP developers. Today we can be almost sure that there is a library on Packagist that can solve our problem, or make things easier for us. I present you a list of well-tested libraries written with modern coding standards in mind, that helped me tackle different challenges.

State machine

Ever worked on an ecommerce system? Then it's a highly possible that you needed a state machine to track and handle transitions of the state of an order - even if you weren't aware at the time that a state machine is what you want.

This is a great package that helps with that.

First off, states, possible transitions, and transition callbacks are defined as graphs. Multiple graphs can be configured, and events attached to the same object.

Here's an example of the object to which we will attach a graph:

<?php

namespace DomainSpace;

class DomainObject
{
    private $state = 'checkout';

    public function getState()
    {
        return $this->state;
    }

    public function setState($state)
    {
        $this->state = $state;
    }

    public function setConfirmedNow()
    {
        var_dump('I (the object) have been confirmed at ' . date('Y-m-d') . '.');
    }
}

We can define a graph like this:

$config = [
    'graph' => 'myGraphA', // Name of the current graph - there can be many of them attached to the same object.
    'property_path' => 'stateA', // Property path of the object actually holding the state.
    'states' => [
        'checkout',
        'pending',
        'confirmed',
        'cancelled',
    ],
    'transitions' => [
        'create' => [
            'from' => ['checkout'],
            'to'   => 'pending',
        ],
        'confirm' => [
            'from' => ['checkout', 'pending'],
            'to'   => 'confirmed',
        ],
        'cancel' => [
            'from' => ['confirmed'],
            'to'   => 'cancelled',
        ],
    ],
    'callbacks' => [
        'guard' => [
            'guard-cancel' => [
                'to' => ['cancelled'], // Will be called only for transitions going to this state
                'do' => function () {
                    var_dump('Prevent cancelling orders.');
                    return false;
                },
            ],
        ],
        'before' => [
            'from-checkout' => [
                'from' => ['checkout'], // Will be called only for transitions coming from this state
                'do'   => function () {
                    var_dump('Called when transitioning from the `checkout` state.');
                },
            ],
        ],
        'after' => [
            'confirm-date' => [
                'on' => ['confirm'], // Will be called only on this transition
                'do' => ['object', 'setConfirmedNow'], // `setConfirmedNow` will be called on the object undergoing the transition
            ],
        ],
    ],
];

And finally we would set up and use the state machine like this (assuming that the $config variable is the graph config array as defined previously):

<?php

use DomainSpace\DomainObject;
use SM\StateMachine\StateMachine;

$object = new DomainObject();

// Configure a state machine with an object and a configuration.
$stateMachine = new StateMachine($object, $config);

// The current state is `checkout`.
var_dump($stateMachine->getState());

// Returns `true`, because we can apply this transition to the current state.
var_dump($stateMachine->can('create'));

// Applies the transition and returns `true`. In addition, the `from-checkout`
// callback is called.
var_dump($stateMachine->apply('create'));

// The current state is `pending`.
var_dump($stateMachine->getState());

// Only the `confirm` transition is possible from the `pending` state.
var_dump($stateMachine->getPossibleTransitions());

// Returns `false`, because this transition cannot be applied. The second
// argunemt enables soft mode: the call will returns `false` instead of
// throwing an exception.
var_dump($stateMachine->apply('cancel', true));

// The current state is still `pending`.
var_dump($stateMachine->getState());

// Returns `true`, after the transition is applied. In addition, the
// `confirm-date` callback calls `setConfirmedNow()` on the object itself.
var_dump($stateMachine->apply('confirm'));

// The current state is `confirmed`.
var_dump($stateMachine->getState());

// Returns `false`, as it is guarded.
var_dump($stateMachine->can('cancel'));

// The current state is still `confirmed`.
var_dump($stateMachine->getState());

Forget about messy if-else statements and handle state transitions with style. Your code will be more readable, better structured, and easier to maintain. Enjoy.

Tactician

Tactician is a command bus library that makes it easy to implement the command pattern in your application. The term "Command bus" is mostly used when we combine the Command pattern with a service layer domain logic pattern. It takes a Command object (which describes the user's intention) and matches it to a Handler (which executes it). This pattern helps you structure your code.

Here's an example:

A command

<?php

namespace App\Commerce;

class PurchaseProductCommand
{
    private $productId;

    private $userId;

    // Also a constructor to assign those properties, as well as getter methods
    // for accessing them.
}

A command handler

<?php

namespace App\Commerce;

use App\Commerce\PurchaseProductCommand;

class PurchaseProductHandler
{
    public function handle(PurchaseProductCommand $command)
    {
        // Use the data from the command to update your models or whatever.
    }
}

Issuing commands from a controller

<?php

namespace App\Commerce;

use App\Commerce\PurchaseProductCommand;

class CartController
{
    public function checkout()
    {
        // In your controllers, you can populate the command using your favorite
        // form or serializer library, then drop it in a CommandBus and you're
        // done!
        $command = new PurchaseProductCommand(42, 29);

        // This property appears out of nowhere! Wow, magic! Just kidding,
        // you'll probably want to inject this, or follow our advice below.
        $this->commandBus->handle($command);
    }
}

We can refactor this even more by creating a helper function where we would pass a command instance, and inside a function, we would call the handle function on a $commandBus instance. Here's an example helper function implementation for the Laravel framework:

<?php

use League\Tactician\CommandBus;

if (!function_exists('command')) {
    /**
     * Runs the passed command through the Tactician command bus.
     *
     * @param mixed $command The command to run.
     *
     * @return mixed
     */
    function command($command)
    {
        // Fetches a `CommandBus` (which is set up as a singleton by some
        // service provider) from the IoC container.
        return app(CommandBus::class)->handle($command);
    }
}

And then you can use it in the controller like this:

<?php

namespace App\Commerce;

use App\Commerce\PurchaseProductCommand;

class CartController
{
    public function checkout()
    {
        command(new PurchaseProductCommand(42, 29));
    }
}

Tactician can come in handy if you have a service layer (you can also watch awesome talks by Ross Tuck, about using a service layer, here and here). The command bus can be easily decorated with extra behavior, like locking, database transactions, logging etc.

KnpMenu

Do you hard-code you navigation into views? You don't have to; here's a package that can clean your views and elegantly generate navigation markup. This library was originally made for Symfony2, but it can also be used standalone. It's easy to use and well documented.

Basic usage would be:

use Knp\Menu\Matcher\Matcher;
use Knp\Menu\MenuFactory;
use Knp\Menu\Renderer\ListRenderer;

$factory = new MenuFactory();

$menu = $factory->createItem('My menu');

$menu->addChild('Home', ['uri' => '/']);
$menu->addChild('Comments');

$renderer = new ListRenderer(new Matcher());

echo $renderer->render($menu);

But this library allows you to do a lot of fun stuff with navigation menus, like:

Creating a tree menu

A menu tree works and acts like a multi-dimensional array, it implements ArrayAccess, Countable and Iterator interfaces.

use Knp\Menu\MenuFactory;

$factory = new MenuFactory();

$menu = $factory->createItem('My menu');

$menu->addChild('Home', ['uri' => '/']);
$menu->addChild('Comments');

$menu['Comments']->setUri('#comments');
$menu['Comments']->addChild('My comments', ['uri' => '/my_comments']);

Customizing menu items

Each menu items can be additionally customized after it is created. The following methods are at your disposal:

  • setUri() - sets the URI for a menu item
  • setLabel() - sets the label for a menu item
  • addChild() - adds a child to a menu item
  • setAttribute() or setAttributes() - adds any attribute(s) to the (later) rendered <li> tag for that menu item

Rendering only part of a menu

Only a part of the menu can be rendered, and there are many ways to specify what to render.

// Render only 2 levels deep (root, parents, children).
$renderer->render($menu, ['depth' => 2]);

// Render everything except for the children of the Home branch.
$menu['Home']->setDisplayChildren(false);
$renderer->render($menu);

// Render everything except for Home *and* its children.
$menu['Home']->setDisplay(false);
$renderer->render($menu);

There is even more you can do with this library, like tracking the active menu item, creating a menu from a tree structure, integrating with templating engines, and other stuff - check out the official documentation for more info.

Faker

This one's a treat!

Do you ever spend your time filling your database with random data just so you can test some functionality of your project? Not anymore, Faker will do this for you, it just needs a little bit of your guidance.

The basic concepts of this library are:

  • Generators - used for generating fake data,
  • Providers - providers serves as data sources for generators, and
  • Formatters - the properties used for getting specific data from generators (username, email...)

Faker supports localization, and the locale is defined when a Faker instance is created.

$faker = Faker\Factory::create('en_US');

Note that not all providers are available in all locales, so only generators that are defined for a specific locale will be locale-specific, while others will use the default providers that come with Faker.

You can even create you own providers.

<?php

namespace Faker\Provider;

use Faker\Provider\Base;

class Fruit extends Base
{
    protected static $fruit = [
        'Banana',
        'Blackberry',
        'Blackcurrant',
        'Boysenberry',
        'Cherimoya',
        'Cloudberry',
        'Dragonfruit',
        'Durian',
        'Elderberry',
        'Feijoa',
        'Fig',
        'Goji berry',
        'Guava',
        'Jabuticaba',
        'Kiwifruit',
        'Lemon',
    ];

    public function fruit()
    {
        return static::randomElement(static::$fruit);
    }
}

And use it like this:

$faker->addProvider(new Faker\Provider\Fruit($faker));
echo $faker->fruit;

Here is example usage of Faker and some common generators:

use Faker\Factory;

// use the factory to create a Faker\Generator instance
$faker = Faker\Factory::create();

// generate data by accessing properties

echo $faker->name;
// 'Lucy Cechtelar';

echo $faker->address;
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"

echo $faker->text;
// Dolores sit sint laboriosam dolorem culpa et autem. Beatae nam sunt fugit
// et sit et mollitia sed.
// Fuga deserunt tempora facere magni omnis. Omnis quia temporibus laudantium
// sit minima sint.

A common way to use Faker in a project is in database seeder classes. Most modern PHP frameworks come with database migration and seeding functionality, in Laravel we have seeders, in Symphony fixtures, and if you don't use any framework or if framework you use doesn't have this functionality you can use Phinx or another solution.

You could then seed data for various automated testing processes, whether it's some kind of integration testing, or testing database performance under load and optimizing queries, or just adding dummy data as some kind of a placeholder.

TrulyRandom

For situations when rand() and mt_rand() are just not good enough for you. This package uses random.org's API to generate truly random lists of integers, sequences of integers, and random alpha-numeric strings.

<?php

use Pixeloution\Random\Randomizer;

// Takes a partial User Agent as an argument; random.org requests you use your
// email address in case of issues.
$generator = new Randomizer('name@example.com');

$minimumValue = 1;
$maximumValue = 100;
$quantity = 4;

$integers = $generator->integers($minimumValue, $maximumValue, $quantity);

$start = 1;
$end = 10;

$sequence = $generator->sequence($start, $end);

It's really straightforward, and you can see more info on the project's GitHub page.

Snappy

This is a package that you'll want to use if you generate any PDFs from your code. It's simple to use and it's much faster and resource-efficient than Dompdf.

It uses wkhtmltopdf and it requires the wkhtmltopdf and wkhtmltoimage binaries installed on the server, but they can also be installed as Composer dependencies:

$ composer require h4cc/wkhtmltopdf-i386 0.12.x
$ composer require h4cc/wkhtmltoimage-i386 0.12.x

Then it can be used like this:

<?php

use Knp\Snappy\Pdf;

$myProjectDirectory = '/path/to/my/project';

$snappy = new Pdf($myProjectDirectory . '/vendor/h4cc/wkhtmltopdf-i386/bin/wkhtmltopdf-i386');

$snappy->generateFromHtml('<p>Some content</p>', 'test.pdf');

wkhtmltopdf settings can be applied using the setOption() method, and the generated file can be returned as a response, instead of writing it to the file system.

<?php

use Knp\Snappy\Pdf;

$myProjectDirectory = '/path/to/my/project';

$snappy = new Pdf($myProjectDirectory . '/vendor/h4cc/wkhtmltopdf-i386/bin/wkhtmltopdf-i386');

$snappy->setOption('disable-javascript', true);
$snappy->setOption('cookie', ['key' => 'value', 'key2' => 'value2']);
$snappy->setOption('cover', 'pathToCover.html');

header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="file.pdf"');

echo $snappy->getOutput('<p>Some content</p>');

Nice!

Laravel Charting Package

This one is only for Laravel users but it's awesome. It's used for generating charts and it uses familiar Laravel syntax. It supports all different types of charts and even real-time charts, and different styles derived from popular charting libraries (chartjs, highcharts, google...)

Installation

Add service provider and an optional facade to config/app.php:

// Service provider:
ConsoleTVs\Charts\ChartsServiceProvider::class,

// Facade:
'Charts' => ConsoleTVs\Charts\Facades\Charts::class,

And publish assets

php artisan vendor:publish --tag=charts_config

This will generate a config/charts.php file which will contain the default settings for the package.

Example usage

There are multiple ways to create a chart based on your needs. A basic example is the create method:

<?php

namespace App\Http\Controllers;

use ConsoleTVs\Charts\Builder as ChartsBuilder;

class TestController extends Controller
{
    // We rely on Laravel's method injection here.
    public function index(ChartsBuilder $chartsBuilder)
    {
        $chart = $chartsBuilder->create('bar', 'material')
            ->title('My nice chart')
            ->labels(['First', 'Second', 'Third'])
            ->values([5,10,20])
            ->dimensions(0,500);

        return view('test', ['chart' => $chart]);
    }
}

The first argument of the create method is the type of the chart, while second is the library whose styles are used to display the chart.

The test view used in the example would look something like this:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>My Charts</title>

        {{-- We don't actually recommend using Laravel's "facades" at all,
        ever, but for the purposes of a simplified code example in a blog
        post, it's okay. --}}
        {!! Charts::assets() !!}
    </head>
    <body>
        {{-- Haha, center! We don't recommend this either, we just like to
        frustrate front-end developers who might be reading this. --}}
        <center>
            {!! $chart->render() !!}
        </center>
    </body>
</html>

In addition to the create method, there are also multi, database, realtime and math methods available.

Multi

Multi-dataset charts can be created using the multi method, which is mostly used the same way as te create method, but additionally, multiple datasets can be added via the dataset method.

<?php

// Assume this is an instance of `ConsoleTVs\Charts\Builder`.
$chartsBuilder->multi('line', 'highcharts')
    ->colors(['#ff0000', '#00ff00', '#0000ff'])
    ->labels(['One', 'Two', 'Three'])
    ->dataset('Test 1', [1, 2, 3])
    ->dataset('Test 2', [0, 6, 0])
    ->dataset('Test 3', [3, 4, 1]);

Database

Database charts are created by passing an Illuminate\Database\Eloquent\Collection instance as a the first argument of the database method, while the second and third arguments are the chart type and style.

<?php

// Assume this is an instance of `ConsoleTVs\Charts\Builder`.
$chartsBuilder->database(User::all(), 'bar', 'highcharts')
    ->elementLabel('Total')
    ->dimensions(1000, 500)
    ->responsive(false)
    ->groupBy('game');

Note: You can even create multi-database charts using the multiDatabase function, the same way as the multi function is used, the only difference is that you would pass an Illuminate\Database\Eloquent\Collection instance as the second argument of the dataset method, instead of the array.

Realtime

Real-time charts just couldn't get any simpler.

<?php

// Assume this is an instance of `ConsoleTVs\Charts\Builder`.
$chartsBuilder->realtime(url('/path/to/json'), 2000, 'gauge', 'google')
    ->values([65, 0, 100])
    ->labels(['First', 'Second', 'Third'])
    ->responsive(false)
    ->height(300)
    ->width(0)
    ->title('Permissions Chart')
    ->valueName('value'); // Determines the JSON property which will be used.

Example JSON that a /path/to/json API endpoint would have to return would be:

{"value":31}

Math

You can create math function charts with the math method. The first argument is a mathematical function, the second is the interval, defined as an array, the third is the amplitude, and the fourth and fifth are the chart type and style.

// Assume this is an instance of `ConsoleTVs\Charts\Builder`.
$chartsBuilder->math('sin(x)', [0, 10], 0.2, 'line', 'highcharts');

Each method has additional chainable methods for customizing each type of chart - there is a lot more to explore on the documentation page of this awesome package, so take your time.

Breadcrumbs

Last but not least, it's our child. This is the first package that we developed and maintain (we intend to create a lot more!), and it's used for generating breadcrumbs with ease.

<?php

$breadcrumbs = new Creitive\Breadcrumbs\Breadcrumbs();

$breadcrumbs->addCrumb('Home', '/');

echo $breadcrumbs->render();

It generates Twitter Bootstrap-compatible HTML.

It enables you to add crumbs, set CSS classes on generated output, and even change the default divider or list element used to wrap breadcrumbs.

<?php

$breadcrumbs->addCrumb('Home', '/')
    ->addCrumb('Pages', 'pages')
    ->addCrumb('Subpage', 'subpage')
    ->addCrumb('Subsubpage', '/subsubpage')
    ->addCrumb('Other website', 'http://otherwebsite.com/some-page');

$breadcrumbs->setCssClasses(['breadcrumbs', 'header-breadcrumbs']);
$breadcrumbs->setDivider('ยป');
$breadcrumbs->setListElement('ol');

PHP Package Checklist

If you write your own Composer packages or are considering to give it a try, this is a must read. The PHP Package Checklist contains useful rules that you can follow in order to create a good PHP package, which can, as they say, be "taken seriously by the rest of the PHP community".

Back