Few tips for building frameworks

Illustration de l'article

Quite often I read threads on /r/php and sometimes I see people that share their custom PHP framework, a bit curious, I read the thread and analyze the linked repository, most of the time the framework has some small (or big) flaws.

If you have in mind to create a framework (PHP or another language, we'll talk PHP here) or if you already have a framework and you wish to make it better and to maintain it, I'd like to give you some advices.

PHP Standards Recommendations

The PSR (PHP Standards Recommendations) are conventions adopted and followed by "most" of the developers. They are designed to standardize code and to make the interoperability between system better.

The PSR system allows to suggest new convention and then the members of the community agree or not to it.

For example, by following the PSR-2 "Coding Style Guide" your code must follow a certain style, here are some rules ;

  • Use 4 spaces for indenting instead of tabulation
  • Put a space after the declaration of namespace
  • Method's brackets must be on their own line (so not on the end of current line)

Here is a piece of code (took from the official website) which is PSR-2 compliant :

<?php
namespace Vendor\Package;

use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class Foo extends Bar implements FooInterface
{
    public function sampleMethod($a, $b = null)
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }
    }

    final public static function bar()
    {
        // method body
    }
}

If you make an open-source framework and so designed for a large public, it is strongly recommended to follow this convention because it will make your code more familiar to other developers but also making you feel more familiarised with other's code.
You don't have to follow strictly every PSR but if you do, there is more chances that other developers won't be lost. (and if you don't follow it, please stay constant in your codebase)

Namespaces

Namespaces allow you to better organize your code. Instead of giving you an abstract definition, here are some code to explain how they are useful.

<?php
namespace App\Controller;

use Framework\Controller\AbstractController;
use Framework\Http\Response;

final class HomeController extends AbstractController
{
    public function index(): Response
    {
        return new Response('Hello World!');
    }
}

With the following code, we can see :

  • The class HomeController has been declared in the namespace App\Controller (on the second line)
  • We use AbstractController from the namespace Framework\Controller
  • We use Response from the namespace Framework\Http

What if we reproduce the code without namespaces ?

<?php
require('./../../Framework/Controller/AbstractController.php');
require('./../../Framework/Http/Response.php');

final class HomeController extends AbstractController
{
    public function index(): Response
    {
        return new Response('Hello World!');
    }
}

It would work, isn't it ?

Now, what happens if in the file AbstractController.php the class HomeController has already been declared ? The code breaks simply
Well, we can prefix every class of our project with App or MyFrameworkName but we'll never be sure that a class hasn't been declared already...

Now what happens if the framework user change a file in another directory to refactor ? He has to change every path of his requires...

What happens if in AbstractController.php and Response.php the same dependency is declared ? We would use require_once() everywhere and lose some performances ? We wrap every class in class_exists() ?

Namespaces allow a lot of things, mostly to avoid the collision between classes name but also to organize your files by thematics / scopes so you can get a well structured codebase.

Finally, namespaces work well with autoloading, if you are PSR-4: Autoloader compliant.

Composer

Composer is a dependency manager, it allows you to easily get, maintain and manage your project dependencies in a few command lines.

It also has the serious benefit to manage your namespaces. It can manage them with it's autoloader and also manage your dependecies namespaces (e.g. : doctrine, monolog, swiftmailer, ...)

We could say that if you don't use Composer you'll have a hard time to handle your files inclusions and to maintain your codebase.

Performances

Even if PHP 7 gained a lot in performances, if you decide to make a framework, it has to be performant too to be appealing to potential users.

So, in your codebase things like this, should not exist :

for ( $i = 0; $i <= count($myArray); $i++ ) {
    // ...
}

This example is probably the most known

This loop is slower than if we assigned count($myArray) to a variable and then execute the for loop.
It is caused by every iteration of the loop, PHP ask which length is $myArray.
On small arrays, this shouldn't be felt but on bigger arrays it can cause some slowness in your application. That said, think about your code that is going to be used by numerous people, you don't know how exactly they'll use it so it's always good to save a few ms.

The "good" way of doing it would be :

$myArrayLength = count($myArray);
for ( $i = 0; $i <= $myArrayLength; $i++ ) {
    // ...
}

or even :

for ( $i = 0, $j = count($myArray); $i <= $j; $i++ ) {
    // ...
}

SOLID Principles

SOLID Principles define some basics for object oriented code. They allow to specify a directive line of development in your project to make it more robust and maintainable.

By following these principles, you more likely to avoid what we call "spaghetti code" by defining your classes with a specific goal. (Single Responsability Principle)

Your framework users (or yourself) to manipulate classes without breaking everything. (Open/Closed Principle)

In a more global perspective, it makes your code more maintainable, understable and easier to evolute.

You can also look at other principles like KISS, YAGNI and DRY which can also help you in your development.

Documentation

Documentation is a significant part of every project, without documentation it can be quite complicated for you to maintain your framework, but most importantly, for your users to understand your tool and so to use it.

<?php
namespace Framework\Controller;

use Framework\Controller\AbstractController;
use Framework\Http\Response;

final class HomeController extends AbstractController;
{
    public function index(): Response
    {
        return new Response('Hello World!', 200, []);
    }
}

Without documentation how to know how work the class AbstractController ? What arguments takes Response ?

Think about writing a clear and concise documentation, it also helps your users if you give concrete example of how your tool works.
Also, documentation should be written in parallel of development, don't let rot your documentation.

Unit tests

Unit tests are probably the most missed in every homemade framework I saw on reddit.
Still, it's probably the most important piece that your framework must have...

Unit tests gives you a lot of benefits like ; you can check your code quality to see if it isn't decreasing, without tests, how would you know if your new functionality hasn't broke another piece of code that you wouldn't even think about ?

Units tests allows you to make your development more agile, if tomorrow your community asks for a refactor on your way of dealing with database (which is quite crucial in an application), units tests makes you feel more comfortable about refactoring your code because you would detect any new bugs that your refactor introduced and so you could fix them directly.

Units tests makes you think harder about your design issues and so make you understand them better, if it's easy to test a piece of code or a part of your application so it probably means that the code is wellmade and it's responsability is under control. On the other hand, if it's difficult to test your code, maybe your solution could be better designed.

Units tests play along with your documentation, read clear and concise tests help other developers to better understand your codebase.

In short, if you make a framework, make units tests !

You can read the PHPUnit documentation if you wanna start to make some.

Use dependencies

Making a framework is a challenge, a challenge so time consuming that you can allow yourself to use external dependencies.
Wanna make a framework because Symfony/Laravel/Zend are not suited for you ? Ok, so you'll rewrite everything from scratch, but maybe you did like Doctrine, why would you rewrite an ORM ? Use your favorites dependencies to make your life easier.

To keep it a challenge, make your framework agnostic from how it handle database, how it handle routing and so on...
It is quite good for your framework if you leave to your users the possibility to use their tools inside your framework. Some would prefer Doctrine over Eloquent, other would rather use FastRoute than AltoRouter, ...

Well, use dependencies, let your users use some aswell.

Don't silence errors

I saw things like this in some codebase :

$myVariable = @function($param1, $param2, null);

Silencing a potential error in your framework would make debugging very difficult, don't do it.
If your code can result in an error, it would be a better idea to handle this with exceptions.

Handle exceptions

When developing a framework, it is quite crucial to know when things go wrong, to handle this, it is a good idea to warn your users or yourself about errors that happened during the framework execution.

It allow a better development workflow for you and your community.

<?php
namespace Framework\Http;

use Framework\Http\ResponseInterface;
use Framework\Http\Exception\BuildResponseException;

class Response implements ResponseInterface
{
    private $content;
    private $code;
    private $params;

    public function __construct(string $content, int $code = 200, array $params = null)
    {
        if ( strlen($content) <= 0 ) {
            throw new BuildResponseException('A Response cannot be empty.');
        }

        $this->content = $content;
        $this->code = $code;
        $this->params = $params;
    }

    public function buildResponse()
    {
        // ...
    }
}

The example is pretty simple, if the user tries to build an empty response, it would throw BuildResponseException that you can catch in your framework execution. It send a clear and understandable message for the user that he isn't using your class Response in a correct way.

Handle database

If in your framework you want to create a database layer to handle it, you can do it, but please don't use mysql functions, they are deprecated, not supported in PHP7 and have no object oriented interface.

You should look after PDO or mysqli (PDO by personal preferences)

Document yourself

Put every odds by your side, and update yourself about the languages that you're going to use, know the good practices, the existing tools, ...
Wanna make a framework ? Document yourself first, get a little bit of experience with other frameworks to see how they work, to get inspired.
Wanna make a tool/CLI ? Look at what is already existing that don't differ that much with what you wanna create, this would help to see what is missing from the other tools and what should be implemented in yours. Read the communities comments on what should current tools do better, etc...

Making a useful project is by far more gratifying than making something to leave it rot.

Don't copypasta existing frameworks aswell, there is already too many CodeIgniter-like on /r/php. (Code igniter being far from a reference in terms of good practices)
Inspire yourself from what's existing but don't brainlessly copypasta.

Conclusion

Making a framework from scratch is a challenge, I strongly encourage you to use one or more frameworks before making one.

It's still an excellent exercice to better understand the other frameworks, the language that you're using and overall to be a better developer.

If you wanna jump into this adventure, well first, good luck ! It's a very time consuming exercice which takes a lot of patience, but you'll get out of it accomplished ! :p

There, some additional links for you if you wanna know more about making a framework :

And some more themes and resources in which you should be interested :

  • PHP Stan is a analysis tool that warns you about errors before even running your code or before writing units test.
  • Dependency Injection Container It's in french (sorry) but examples talks for themselves, it introduce you to dependency injection container, which is an important topic when developing a sophisticated and maintainable framework.

Resources:


Leave a comment