Open-Source

I share some of my creations and knowledge gathered over the years through code libraries, primarily aimed at PHP and Symfony.

They'll facilitate development and adherence to good practices within your applications... :)

Symfony Ecosystem

Doctrine Specifications

This layer on top of Doctrine's QueryBuilder will help you reduce code duplication and cleanly organize your Doctrine repositories.


final class ManyArticles extends SpecificationCompound
{

    public static function asEntity() : self
    {
        return new self(
            SpecificationRepositoryResult::MANY_OBJECTS,
            SelectEntity::specification(Article::class, 'article'),
        );
    }

    public function postedByUser(UserId $userId) : self
    {
        $this->whereFieldEqual('article.author', 'userId', $userId);

        return $this;
    }

    public function orderedByDateDesc() : self
    {
        $this->orderResultsByDesc('article.publishedAt');
        // Equivalent to:
        //  $doctrineQuerybuilder->addOrderBy('article.publishedAt', 'DESC');

        return $this;
    }

    public function paginate(int $page, int $itemPerPage) : self
    {
        $this->limitResultsPaginate($page, $itemPerPage);
        // Equivalent to:
        //  $query->setMaxResults($itemPerPage);
        //  $query->setFirstResult(($page - 1) * $itemsPerPage);

        return $this;
    }

}

Then combine your search criteria as needed using fluent method calls:


// We want all published articles in a given category
$publishedCategoryArticles = ManyArticles::asEntity()
    ->postedByUser($userId)
    ->orderedByDateDesc();

// Conditionnal pagination
if ($pageNumber) {
    $publishedCategoryArticles->paginate($pageNumber, 10);
}

// Retrieve articles from the db
$articles = $repository->find($publishedCategoryArticles);

Hydrate custom classes (DTO) instead of entities to optimize your queries:


final class ArticleModel implements SpecificationReadModel
{
    public function __construct(
        public readonly int $id,
        public readonly string $title,
        public readonly string $content,
        public readonly int $categoryId,
        public readonly string $categoryName,
    ) {}
    
    public static function getDqlConstructorArguments(): array
    {
        return [
            'article.id',
            'article.title',
            'article.content',
            'category.id',
            'category.name',
        ];
    }
}

final class ManyArticles extends SpecificationCompound
{

    public static function asModel() : self
    {
        return new self(
            SpecificationRepositoryResult::MANY_OBJECTS,
            SelectReadModel::specification(Article::class, 'article', ArticleModel::class),
            JoinLeft::specification('article.category', 'category') // Join declaration
        );
    }

    // ...

}

Twig Powerpack

Improve the reliability and clarity of your Twig templates by explicitly declaring the expected variables, to automatically validate their existence and type:

{% extends 'Layout.twig' %}

{% expect 'string' as TITLE %}
{% expect 'bool' as ENABLED %}
{% expect 'float' as AMOUNT %}
{% expect 'int' as COUNT %}
{% expect array of 'int' as IDS %}

{% expect 'App\\Classes\\Foo' as FOO %}
{% expect nullable 'App\\Classes\\Foo' as FOO_OR_NULL %}

Declare global resources in your pages to access them from another template:


{% extends 'Layout.twig' %}

{% register 'has-menu' in 'bodyClasses' %}
{% register 'responsive' in 'bodyClasses' %}

{% register '/css/few-styles.css' in 'styles' %}
{% register once '/css/some-styles.css' in 'styles' %}

{% register once '/js/custom-scripts.js' in 'scripts' %}

<html>
    <head>
        ...
        
        {% for css in registry('styles') %}
        <link rel="stylesheet" href="{{ css }}" />
        {% endfor %}
        <!-- <link rel="stylesheet" href="/css/few-styles.css" /> -->
        <!-- <link rel="stylesheet" href="/css/some-styles.css" /> -->
    </head>
    <body class="{{ registry('bodyClasses')|join(' ') }}">
    <!-- <body class="has-menu responsive"> -->
        ...
        
        {% for js in registry('scripts') %}
        <script src="{{ js }}"></script>
        {% endfor %}
        <!-- <script src="/js/custom-scripts.js"></script> -->
    </body>
</html>

Symfony Easy API

Quickly build your APIs using plain Symfony controllers.

/**
 * @Route("/api/things", name="api_things", methods={"GET"})
 */
final class ApiEndpointController
{
    public function __invoke(EasyApi $easyApi, ThingRepository $thingRepository) : Response
    {
        return $easyApi->response(
            function() use ($thingRepository)
            {
                $things = $thingRepository->findAll();
                return ApiPayload200Success::createWithArrayResult($things);
            }
        );
    }
}

Normalized response formats and automatic error handling:


{
    success: true,
    status: "ok",
    statusCode: 200,
    payload: {
        results: [
            { id: 1, name: "First thing" },
            { id: 2, name: "Second thing" },
            { id: 3, name: "Third thing" }
        ],
        resultsCount: 3,
        resultsCountTotal: 3,
        page: 1,
        pageCount: 1
    }
}

{
    success: false,
    status: "not_found",
    statusCode: 404,
    error: "not_found",
    errorDescription: "Thing not found (id: 1)",
    errorCode: 0
}

{
    success: false,
    status: "server_error",
    statusCode: 500,
    error: "server_error",
    errorDescription: "Unexpected server error: Invalid `$thingId` value (-1)",
    errorCode: 0
}

Types & Value Objects

Types collections

This library provides strongly typed collections for PHP's primitive types.
Chainable methods replace native PHP array manipulation functions and extend their capabilities.

IntCollection::fromArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    ->where(fn($n) => $n > 4)
    ->append(10)
    ->select(static fn($n) => $n * 10)
    ->forEach(static fn(int $n) => var_dump($n));

// Outputs:
//   int(50)
//   int(60)
//   int(70)
//   int(80)
//   int(90)
//   int(100)

Also create strongly typed collections for your own classes in just a few lines:

/*
 * @extends ClassCollection<Foo>
 */
class FooCollection extends ClassCollection
{
    protected static function classFqcn() : string
    {
        return Foo::class
    }
}


// Examples of usage
FooCollection::new();
FooCollection::fromArray([new Foo(), new Foo()]);

// Invalid
FooCollection::fromArray([
    new Foo(),
    new Bar(), // throws InvalidCollectionItemException
])

Types common

Provides generic Value Objects to enrich PHP's primitive types and strongly type your properties and arguments.

Name::fromString('Bruce');
Slug::slugify('Héllo world !'); // hello-world
Url::fromString('https://mediagone.fr/');
Color::fromRgb(255, 128, 76); // #ff804c
...
function sendMail(EmailAdress $to, Title $title, Text $content): void {
    //...
}

Types enums (for PHP 7.4+)

Provides strongly typed classes to simulate PHP 8.1 enums.

/**
* @method static ArticleStatus DRAFT
* @method static ArticleStatus PUBLISHED
*/
final class ArticleStatus extends EnumInt
{
    private const DRAFT = 0
    private const PUBLISHED = 1
}

// Enum values are accessible using static methods which return an instance of ArticleStatus
ArticleStatus::DRAFT()
ArticleStatus::PUBLISHED()

// Returned enum instances are singletons — strict identity comparison works
ArticleStatus::DRAFT() === ArticleStatus::DRAFT() // true
ArticleStatus::PUBLISHED() === ArticleStatus::DRAFT() // false

Small UID

A unique identifier similar to a ULID but half the size (64 bits), useful when you need conciseness at the cost of a little uniqueness.

$uid = SmallUid::random();

$shortString = (string)$uid; // string(11) "LscmjzUyKLR"
$hexString = (string)$uid->getHex(); // string(16) "1234567890abcdef"

CQRS Bus

Replace direct service calls with Commands and Queries and benefit from middleware features: automatic database transactions, logging, security...

Lighter and easier to use than symfony/messenger, it is particularly well-suited for messages strictly internal to an application (eg. DDD architecture).

Both systems are complementary: CQRS Bus handles internal exchanges (Commands, Queries, Events...) while Messenger takes care of communication with external systems (RabbitMQ, etc.).

Find all my libraries on GitHub or Packagist

Loading…
Loading the web debug toolbar…
Attempt #