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.
winzou/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.
league/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:
<?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.
}
<?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.
}
}
<?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.
knplabs/knp-menu
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:
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']);
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 itemsetLabel()
- sets the label for a menu itemaddChild()
- adds a child to a menu itemsetAttribute()
or setAttributes()
- adds any attribute(s) to the (later) rendered <li>
tag for that menu itemOnly 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.
fzaninotto/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:
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.
pixeloution/true-random
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.
knplabs/knp-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!
consoletvs/charts
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...)
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.
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-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 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 themulti
function is used, the only difference is that you would pass anIlluminate\Database\Eloquent\Collection
instance as the second argument of thedataset
method, instead of the array.
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}
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.
creitive/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');
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".
We are Creitive, digital product agency. If you want to learn more, check out our PHP Web Development Services