Open-Source
Je partage certaines de mes créations et connaissances accumulées au fil des années sous la forme de bibliothèques de code, principalement destinées à PHP et Symfony.
Elles faciliteront le développement et le respect des bonnes pratiques au sein de vos applications... :)
Écosystème Symfony
Doctrine Specifications
Cette surcouche du QueryBuilder de Doctrine vous permettra de réduire les duplications de code et de garder vos repositories Doctrine proprement organisés.
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;
}
}
Combinez ensuite vos critères de recherche en fonction des besoins grâce à des appels de méthodes fluides :
// 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);
Hydratez des classes personnalisées (DTO) plutôt que des entités pour optimiser vos requêtes :
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
Améliorez la fiabilité et la clarté de vos templates Twig en déclarant explicitement les variables attendues, pour valider automatiquement leur existence et leur 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 %}
Déclarez des ressources globales dans vos pages pour y accéder à partir d'un autre 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
Construisez vos API rapidement à partir de simples controlleurs Symfony.
/**
* @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);
}
);
}
}
Formats de réponses normalisés et gestion automatique des erreurs :
{
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
Cette bibliothèque propose des collections fortement typées pour les types primitifs de PHP.
Des méthodes chaînables remplacent les fonctions PHP natives de manipulation des tableaux et étendent ses fonctionnalités.
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)
Créez également des collections fortement typées pour vos classes en quelques lignes :
/*
* @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
Fournit des Value-Objects génériques pour remplacer les types primitifs de PHP et typer fortement vos propriétés et 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 (pour PHP 7.4+)
Fournit des classes fortement typées permettant de simuler les enums de PHP 8.1.
/**
* @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()
Small UID
Un identifiant unique similaire à un ULID mais moitié moins grand (64 bits), utile lorsqu'on a besoin de concision au coût d'un peu d'unicité.
$uid = SmallUid::random();
$shortString = (string)$uid; // string(11) "LscmjzUyKLR"
$hexString = (string)$uid->getHex(); // string(16) "1234567890abcdef"