bboezio / neophp
NeoPHP - PHP8 Framework
Requires
- php: >=8.1
- ext-curl: *
- ext-dom: *
- ext-fileinfo: *
- ext-ftp: *
- ext-iconv: *
- ext-libxml: *
- ext-pdo: *
- ext-simplexml: *
- ext-zip: *
- matthiasmullie/minify: ^1.3
- phpmailer/phpmailer: ^7.1
- predis/predis: ^2.0
- psr/container: ^2.0
- twig/intl-extra: ^3.23
- twig/twig: ^3.0
- wikimedia/less.php: ^5.4
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
- dev-main
- v2.9.0
- v2.8.0
- v2.7.0
- v2.6.0
- v2.5.0
- v2.4.0
- v2.3.0
- v2.2.0
- v2.1.1
- v2.1.0
- v2.0.1
- v2.0.0
- v1.17.0
- v1.16.0
- v1.15.0
- v1.14.0
- v1.13.0
- v1.12.0
- v1.11.0
- v1.10.1
- v1.10.0
- v1.9.0
- v1.8.0
- v1.7.0
- v1.6.1
- v1.6.0
- v1.5.0
- v1.4.0
- v1.3.1
- v1.3.0
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- dev-doc/readme
- dev-BenjiLeLoustik-patch-1
This package is auto-updated.
Last update: 2026-06-04 11:04:10 UTC
README
Framework PHP 8.1+ centré sur :
- un noyau applicatif dans
neo/ - une CLI interne dans
bin/neo - des projets applicatifs isolés dans
src/<Projet>/
Ce dépôt contient le moteur du framework et un projet d'exemple dans src/Test/.
NeoPHP vise un autre point d'équilibre que Symfony ou Laravel. L'objectif n'est pas d'empiler des couches, des bundles ou un ecosysteme très large, mais de fournir un noyau PHP lisible, compact et directement exploitable pour construire une application complete sans sortir du depot. Le framework mise sur une structure simple, une CLI integrée, des modules coeur autodétéctes et un workflow multi-projets qui reste explicite.
En pratique, NeoPHP s'adresse surtout aux projets qui veulent aller vite sans adopter toute la complexite organisationnelle des gros frameworks généralistes. Par rapport a Symfony, il réduit fortement la cérémonie de configuration et la fragmentation entre composants. Par rapport a Laravel, il se montre plus minimal, plus direct dans son architecture, et moins dépendant d'une couche "magique" ou d'un ensemble d'outils externes. Si le besoin est un framework plus petit, plus prévisible et plus facile a suivre de bout en bout dans le code source, c'est précisement le terrain de NeoPHP.
Sommaire
- Vue d'ensemble
- Architecture du depot
- Cartographie du coeur
- Cycle d'execution
- Structure d'un projet
- Conteneur DI et configuration
- Couche HTTP
- Routing et controleurs
- Vues Twig, assets et traductions
- Base de donnees et QueryBuilder
- ORM et repositories
- Formulaires, upload et validation
- Securite: auth, mot de passe, middlewares, csrf
- Events
- Crons
- Cache, logs, mailer, profiler et erreurs
- CLI et generateurs
- Tests PHPUnit
- Deploiement
- Dependances et prerequis
Vue d'ensemble
NeoPHP repose sur deux points d'entrée :
public/index.phppour le runtime HTTPbin/neopour la CLI
Le coeur passe par Neo\App, qui :
- détecte le projet courant
- initialise le conteneur
- enregistre les chemins applicatifs du projet courant
- découvre automatiquement les modules
*Module.phpdansneo/Core/ - ordonne ces modules selon leurs dépendances puis éxécute
register()/boot() - active Twig, la BDD, les assets, la traduction, l'auth, le cache, les crons, le mailer et le profiler
- scanne les contrôleurs, routes, listeners et crons applicatifs
- éxécute la requête HTTP ou la commande CLI
- centralise la gestion des erreurs
Architecture du dépôt
.
|-- bin/
| `-- neo
|-- neo/
| |-- App.php
| `-- Core/
| |-- Asset/
| |-- Console/
| |-- Controller/
| |-- Cron/
| |-- Database/
| |-- DI/
| |-- Error/
| |-- Event/
| |-- Extension/
| |-- Http/
| |-- Module/
| |-- Profiler/
| |-- Routing/
| |-- Security/
| |-- Testing/
| |-- Translation/
| |-- Utils/
| |-- Validator/
| `-- View/
|-- public/
| |-- index.php
| `-- builds/
|-- src/
| `-- <Projet>/
| |-- App/
| |-- Assets/
| |-- Config/
| |-- Model/
| |-- Repository/
| |-- Storage/
| |-- Tests/
| `-- Translations/
|-- composer.json
`-- vendor/
Le projet d'éxemple présent dans le dépôt est src/Test/.
Cartographie du coeur
Le noyau neo/Core/ est structuré par sous-système :
Asset/gestion des assets, compilation CSS / JS / Less, manifest, helper Twigasset()Console/chargement automatique des commandes CLI et générateursController/AbstractControlleret les raccourcis HTTP / auth / events / uploadCron/attribut#[Cron], scan deApp/Crons, listing et éxécution planifiée avec timezone et lock optionnelDatabase/connexion PDO,DatabaseManager, introspection,QueryBuilder, formulaires, pagination, ORM, repositoriesDI/conteneur de dépendances et autowiringError/ErrorHandleretFrameworkExceptionEvent/event dispatcher, attributs listeners, subscribers et evenements coeurExtension/extensions utilitairesArray,Date,File,Html,Json,Number,Path,String,Urlexposées en PHP et dans TwigHttp/Request, responses, fichiers uploades, session, cookie, flashModule/système de modules, découverte des*Module.php, résolution des dépendances et cycleregister()/boot()Profiler/barre de debug en environnementdev, collecteurs request / router / SQL / events / logs / auth / traduction / mailRouting/route collection, scan des contrôleurs, génération d'URL, attributsRoute,MainRoute,RateLimit,MaintenanceSecurity/auth session / token, JWT, middlewares, mot de passe, CSRFTesting/base de tests, scaffold PHPUnit, generation auto via#[Test]Translation/résolution de locale, chargement / écriture des traductions, extension TwigUtils/config, cache, logs, mailer et commandes utilitairesValidator/contraintes et moteur de validationView/intégration Twig et enregistrement des fonctions / filtres
Sous-dossiers notables dans neo/Core/ :
Asset/ -> Commands/, Compiler/, Exception/
Console/ -> Attribute/, Commands/, Helper/, Interface/
Controller/ -> Commands/, Exception/, Interface/
Cron/ -> Attribute/, Commands/, Exception/
Database/ -> Builder/, Commands/, Exception/, Form/, ORM/
DI/ -> Exception/
Error/ -> Exception/
Event/ -> Attribute/, Commands/, Contract/, Event/, Exception/
Extension/ -> Array/, Date/, File/, Html/, Json/, Number/, Path/, String/, Url/
Http/ -> Client/, File/, Response/
Module/ -> Exception/, Interface/
Profiler/ -> Collector/, Toolbar/
Routing/ -> Attribute/, Commands/, Exception/
Security/ -> Auth/, Csrf/, Middleware/
Testing/ -> Attribute/, Commands/, Context/, Enum/, Exception/, Generator/, Scaffold/, Scanner/, Template/
Translation/-> Exception/, Helper/, Interface/
Utils/ -> Cache/, Config/, Logger/, Mailer/
Validator/ -> Assert/
View/ -> Exception/, Interface/
Cycle d'éxécution
En HTTP
Neo\App cherche un projet en lisant src/*/Config/app.config.php et compare la clé access a HTTP_HOST / SERVER_NAME.
Si un seul projet existe dans src/, il est sélectionné automatiquement.
En CLI
Les commandes qui opèrent sur un projet existant attendent en général --project=NomDuProjet.
Exceptions notables :
app:make:projectapp:sync:projectsapp:serve
Exemple :
php bin/neo cache:clear --project=Test
Structure d'un projet
Un projet generé par app:make:project contient d'abord :
src/Blog/
|-- .gitignore
|-- composer.json
|-- App/
| |-- Controllers/
| |-- Forms/
| |-- Middlewares/
| |-- Services/
| `-- Views/
|-- Assets/
|-- Config/
| |-- api.config.php
| |-- app.config.php
| |-- cache.config.php
| |-- database.config.php
| |-- deploy.config.php
| |-- logger.config.php
| |-- mailer.config.php
| |-- session.config.php
| `-- twig.config.php
|-- Model/
|-- Repository/
|-- Storage/
`-- Translations/
Sans l'option --skeleton, le générateur ajoute aussi :
src/Blog/
|-- Assets/
| |-- css/
| `-- js/
|-- App/Views/
| |-- errors/
| |-- layouts/
| |-- pages/default/
| `-- partials/
`-- Translations/
|-- en/
`-- fr/
Certains dossiers sont créés plus tard, quand la fonctionnalité est activée :
App/Crons/viamake:cronApp/Event/Listener/viamake:eventetmake:event:listenerTests/viamake:testoumake:test:auto
Les configs sensibles database.config.php, deploy.config.php, api.config.php et mailer.config.php sont prévues pour être ignorées par Git dans le .gitignore généré.
Le générateur ignore aussi Storage/.
Conteneur DI et configuration
Le conteneur Neo\Core\DI\Container fournit :
set()pour enregistrer un service ou une factoryget()pour résoudre un servicebind()pour mapper une abstraction vers une implémentationmake()pour instancier une classe avec des paramêtres runtime- autowiring via reflexion
- support des constructeurs de contrôleurs et de services
Exemple :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Services; use Neo\Core\Utils\Cache\Cache; use Neo\Core\Utils\Logger\Logger; final class ReportService { public function __construct( private Cache $cache, private Logger $logger ) { } public function build(): array { $this->logger->info('Génération du rapport'); return $this->cache->get('report.latest', []); } }
Configuration
Le service Config charge tous les fichiers *.config.php du projet et peut merger les fichiers *.config.test.php pendant les tests.
Exemple :
$appName = $this->getConfig()->from('app')->get('general.name'); $timezone = $this->getConfig()->from('app')->get('date.timezone', 'UTC'); $twigOptions = $this->getConfig()->from('twig')->all();
Exemple de app.config.php :
<?php declare(strict_types=1); return [ 'general' => [ 'name' => 'Blog', 'description' => 'Mon projet NeoPHP', ], 'environment' => 'dev', 'access' => 'localhost:8000', 'date' => [ 'timezone' => 'Europe/Paris', ], ];
Couche HTTP
La couche HTTP est composée principalement de :
RequestResponseJsonResponseRedirectResponseSessionCookieFlash
Request
Request expose notamment :
getMethod()getPath()query()body()header()file()getIp()getUserAgent()getPreviousUrl()
Exemple :
#[Route(path: '/search', name: 'search', methods: ['GET'])] public function search(): Response { $term = (string) $this->request->query('q', ''); return $this->render('pages/search/index.html.twig', [ 'term' => $term, 'ip' => $this->request->getIp(), ]); }
Response
Response sert a construire les réponses HTTP de base.
Exemple :
$response = new Response(); $response->setStatusCode(200); $response->setHeader('Content-Type', 'text/plain; charset=UTF-8'); $response->setContent('OK'); return $response;
Exemples de raccourcis via AbstractController :
return $this->jsonSuccess(['saved' => true], 201); return $this->jsonError('Not found', 404); return $this->redirectToRoute('posts.index'); return $this->redirectToPath('/maintenance', 302);
Session, cookie et flash
Le framework configure automatiquement la session depuis session.config.php.
Exemple dans un contrôleur :
$this->getSession()->set('wizard.step', 2); $step = $this->getSession()->get('wizard.step', 1); $this->getCookie()->set('theme', 'dark'); $theme = $this->getCookie()->get('theme', 'light'); $this->getFlash()->add('success', 'Operation terminee');
Twig expose les messages flash via flashes() :
{{ flashes() }}
Routing et contrôleurs
Le routing repose sur des attributs PHP scannes dans src/<Projet>/App/Controllers.
Fonctionnalités confirmées :
- prefix de route via
#[MainRoute(...)] - routes multi-méthodes via
methods: [...] - paramêtres dynamiques
{id} - paramêtres optionnels
{slug?} - contraintes regex via
requirements - cache des routes hors environnement
dev - injection des arguments types via le conteneur
Exemple simple :
#[MainRoute(path: '/posts', name: 'posts')] final class PostController extends AbstractController { #[Route(path: '/', name: 'index', methods: ['GET'])] public function index(): Response { return $this->render('pages/posts/index.html.twig'); } }
Exemple plus complet :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Controllers; use Neo\Core\Controller\AbstractController; use Neo\Core\Http\Response\Response; use Neo\Core\Routing\Attribute\MainRoute; use Neo\Core\Routing\Attribute\Route; use Neo\Src\Blog\Repository\PostRepository; #[MainRoute(path: '/posts', name: 'posts')] final class PostController extends AbstractController { public function __construct(private PostRepository $postRepository) { } #[Route(path: '/', name: 'index', methods: ['GET'])] public function index(): Response { return $this->render('pages/posts/index.html.twig', [ 'posts' => $this->postRepository->findAll()->getModels(), ]); } #[Route(path: '/{id}', name: 'show', methods: ['GET'], requirements: ['id' => '\d+'])] public function show(int $id): Response { return $this->render('pages/posts/show.html.twig', [ 'post' => $this->postRepository->with('author')->find($id), ]); } }
Helpers exposés par AbstractController :
render()template()redirectToRoute()redirectToPath()redirectBack()json()jsonSuccess()jsonError()auth()dispatch()upload()- accès a
Session,Cookie,Flash,Logger,Cache,Config
Twig expose aussi :
path()currentRoute()
Vues Twig, assets et traductions
Vues Twig
Les vues sont chargées depuis src/<Projet>/App/Views.
Twig est initialisé avec :
- cache optionnel
- debug optionnel
twig/intl-extra- global
app - fonctions ajoutées par le framework
Exemple :
{% extends 'layouts/base_layout.html.twig' %}
{% block title %}Liste des posts{% endblock %}
{% block content %}
<h1>Posts</h1>
<ul>
{% for post in posts %}
<li>
<a href="{{ path('posts.show', {id: post.id}) }}">
{{ post.title }}
</a>
</li>
{% endfor %}
</ul>
{% endblock %}
Assets
Les assets sources vivent dans src/<Projet>/Assets/.
Le composant AssetHandler :
- expose
asset() - compile
css,jsetless - minifie CSS et JS
- génère des noms avec hash
- écrit
public/builds/<Projet>/manifest.json - sert les fichiers compiles depuis
public/builds/<Projet>/assets/
Exemple Twig :
<link rel="stylesheet" href="{{ asset('css/app.css') }}"> <script src="{{ asset('js/app.js') }}"></script>
Arborescence source :
src/Blog/Assets/
|-- css/
| `-- app.css
`-- js/
`-- app.js
Traductions
Les traductions sont chargées depuis src/<Projet>/Translations/<locale>/.
Fonctions Twig disponibles :
translate()trans()getLocales()getLocale()isEnabled_translation()
Comportement notable :
- la locale est résolue depuis la config et les cookies
setLocale()persiste la langue dans un cookielang- en environnement
dev, une clé manquante peut être auto-enregistrée
Exemple de fichier src/Blog/Translations/fr/messages.php :
<?php return [ 'page' => [ 'title' => 'Bienvenue sur le blog', ], 'button' => [ 'save' => 'Enregistrer', ], ];
Exemple Twig :
<h1>{{ trans('messages.page.title') }}</h1> <button>{{ trans('messages.button.save') }}</button>
Exemple dans un contrôleur :
#[Route(path: '/change-locale/{locale}', name: 'change.locale', methods: ['GET'])] public function changeLocale(string $locale, TranslationManager $translator): Response { $translator->setLocale($locale); return $this->redirectBack('home.index'); }
Extensions utilitaires
Le dossier neo/Core/Extension/ expose des helpers réutilisables à deux niveaux :
- dans les contrôleurs via
getString(),getDate(),getFile(),getHtml(),getJson(),getNumber(),getPath(),getUrl()etgetArray() - dans Twig via des fonctions et filtres enregistrés automatiquement
Familles disponibles :
StringExtensionslugify(),camelCase(),snakeCase(),pascalCase(),truncate(),excerpt()DateExtensiondate_now(),date_format(),human_diff(),date_age(),is_past(),is_future(),is_today()NumberExtensioncurrency(),percent(),human_size(),ordinal(),to_roman()FileExtensionfile_extension(),file_size(),file_mime(),is_image()HtmlExtensionhtml_escape(),html_strip(),html_truncate(),html_tag()JsonExtensionjson_encode_ext(),json_decode_ext(),json_is_valid()UrlExtensionurl_is_valid(),url_host(),url_params(),url_add_params()PathExtensionpath_join(),path_normalize(),path_extension(),path_filename()
Exemples :
$slug = $this->getString()->slugify('Mon Titre Exemple'); $price = $this->getNumber()->currency(19.99, 'EUR');
{{ 'Mon Titre Exemple'|slugify }}
{{ currency(19.99, 'EUR') }}
{{ date_format(post.created_at, 'd/m/Y H:i') }}
{{ path_join('uploads', user.avatar) }}
Base de donnees et QueryBuilder
La connexion PDO est pilotée par Config/database.config.php via DatabaseConnection.
Exemple minimal :
return [ 'enabled' => true, 'use' => 'default', 'connections' => [ 'default' => [ 'driver' => 'mysql', 'host' => 'localhost', 'port' => 3306, 'dbname' => 'blog', 'user' => 'root', 'pass' => '', 'charset' => 'utf8mb4', ], ], ];
QueryBuilder
Le QueryBuilder couvre notamment :
table()select()where(),orWhere()whereLike(),whereIn(),whereNull(),whereNotNull()between()join(),leftJoin()orderBy(),groupBy()limit(),offset()get(),first(),count()insert(),insertGetId(),update(),delete()paginate()- transactions via
transaction()
Exemple :
<?php declare(strict_types=1); use Neo\Core\Database\Builder\QueryBuilder; $qb = (new QueryBuilder()) ->table('posts') ->select(['posts.id', 'posts.title']) ->where('posts.user_id', '=', 1) ->whereLike('posts.title', 'neo') ->orderBy('posts.id', 'DESC') ->limit(10); $rows = $qb->get();
Exemple avec transaction :
(new QueryBuilder()) ->table('posts') ->transaction(function (QueryBuilder $qb): void { $qb->table('posts')->insert([ 'user_id' => 1, 'title' => 'Post transactionnel', 'content' => 'Contenu', ]); });
ORM et repositories
ORM
AbstractModel couvre notamment :
- table et clé primaire configurables
- hydratation typée via reflexion
save()fill()toArray()toDatabase()- identity map
- chargement lazy et eager des relations
- support du soft delete si colonne
deleted_at
Relations disponibles :
#[HasOne(...)]#[HasMany(...)]#[BelongsTo(...)]#[BelongsToMany(...)]
Exemple de modèles :
<?php declare(strict_types=1); namespace Neo\Src\Blog\Model; use Neo\Core\Database\ORM\Attribute\BelongsTo; use Neo\Core\Database\ORM\Attribute\HasMany; use Neo\Core\Database\ORM\Model\AbstractModel; final class User extends AbstractModel { protected static ?string $table = 'users'; public ?int $id = null; public string $firstname; public string $email; #[HasMany(target: Post::class, foreignKey: 'user_id', localKey: 'id')] public array $posts = []; } final class Post extends AbstractModel { protected static ?string $table = 'posts'; public ?int $id = null; public int $user_id; public string $title; public string $content; #[BelongsTo(target: User::class, foreignKey: 'user_id', ownerKey: 'id')] public ?User $author = null; }
Exemple d'utilisation :
$post = new Post(); $post->user_id = 1; $post->title = 'Premier post'; $post->content = 'Contenu'; $post->save();
Repositories
AbstractRepository fournit :
find()findAll()findBy()create()update()delete()restore()forceDelete()with()withTrashed()onlyTrashed()paginate()- accès au
QueryBuilder
Exemple :
<?php declare(strict_types=1); namespace Neo\Src\Blog\Repository; use Neo\Core\Database\ORM\Repository\AbstractRepository; use Neo\Src\Blog\Model\Post; final class PostRepository extends AbstractRepository { protected string $modelClass = Post::class; }
Exemple d'utilisation :
$posts = $postRepository ->with('author') ->findAll() ->getModels(); $post = $postRepository->find(10);
Formulaires, upload et validation
Formulaires
NeoPHP embarque :
FormBuilderForm- plusieurs types de champs
- rendu Twig
- CSRF
- validation
Helpers Twig disponibles :
form_start()form_end()form_row()form_widget()form_label()form_error()form_errors()form_csrf()- helpers pour les collections
Exemple de classe de formulaire :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Forms; use Neo\Core\Database\Builder\FormBuilder; use Neo\Core\Database\Form\Form; use Neo\Core\Database\Form\Type\EmailType; use Neo\Core\Database\Form\Type\SubmitType; use Neo\Core\Database\Form\Type\TextType; use Neo\Core\DI\Container; use Neo\Core\Http\Request; use Neo\Src\Blog\Model\User; final class UserForm { private Request $request; public function __construct(Container $container) { $this->request = $container->get(Request::class); } public function build(?User $user = null): Form { $user ??= new User(); $form = (new FormBuilder($user)) ->add('firstname', TextType::class, ['label' => 'Prénom']) ->add('email', EmailType::class, ['label' => 'Email']) ->add('submit', SubmitType::class, ['label' => 'Enregistrer']) ->generate(); $form->addCsrfField(); $form->handleRequest($this->request); $form->setData($user); $form->populateData(); return $form; } }
Exemple Twig :
{{ form_start(form) }}
{{ form_row(form, 'firstname') }}
{{ form_row(form, 'email') }}
{{ form_end(form) }}
Upload dans un controleur
Le point d'entrée applicatif est AbstractController::upload().
Signature :
$filename = $this->upload( string $field, string $name, array $extensions, string $directory );
Ce helper :
- récupère le fichier via
Request::file() - verifie l'upload PHP
- lit l'extension d'origine
- refuse
php,phtml,exe,sh,js - vérifie la whitelist fournie
- crée le dossier cible dans
src/<Projet>/Assets/<directory> - déplace le fichier
- renvoie le nom final du fichier
Exemple :
#[Route(path: '/profile/avatar', name: 'avatar.upload', methods: ['POST'])] public function uploadAvatar(): Response { $filename = $this->upload( field: 'avatar', name: 'user_' . (string) $this->auth()->user()?->id, extensions: ['jpg', 'jpeg', 'png', 'webp'], directory: 'uploads/avatars' ); return $this->jsonSuccess([ 'filename' => $filename, 'path' => 'uploads/avatars/' . $filename, ]); }
Affichage ensuite :
<img src="{{ asset('uploads/avatars/' ~ user.avatar) }}" alt="Avatar">
Validation
Le validateur repose sur des attributs de contraintes posés sur les propriétés des modèles.
Contraintes présentes dans le framework :
NotBlankLengthEmailDateChoiceRangeRegexUrlUniqueEqualToField
Exemple :
<?php declare(strict_types=1); namespace Neo\Src\Blog\Model; use Neo\Core\Database\ORM\Model\AbstractModel; use Neo\Core\Validator\Assert\Email; use Neo\Core\Validator\Assert\EqualToField; use Neo\Core\Validator\Assert\Length; use Neo\Core\Validator\Assert\NotBlank; final class RegisterUser extends AbstractModel { #[NotBlank(message: 'Le prenom est obligatoire.')] public string $firstname = ''; #[NotBlank(message: 'L email est obligatoire.')] #[Email(message: 'L email est invalide.')] public string $email = ''; #[Length(min: 8, message: 'Le mot de passe doit faire au moins 8 caracteres.')] public string $password = ''; #[EqualToField(field: 'password', message: 'Les mots de passe doivent etre identiques.')] public string $password_confirm = ''; }
Securite: auth, mot de passe, middlewares, csrf
Authentification
L'auth est pilotée depuis app.config.php.
Le framework supporte deux guards :
sessiontoken
Le guard token s'appuie sur JwtManager.
Configuration type :
'auth' => [ 'enabled' => true, 'model' => User::class, 'identifier' => 'email', 'password' => 'password', 'guard' => 'session', 'role' => [ 'model' => Role::class, 'foreign_key' => 'role_id', 'field' => 'slug', ], 'options' => [ 'secret' => 'change-me', 'expiration' => 3600, 'algorithm' => 'HS256', ], ],
API de AuthManager :
attempt()login()logout()check()user()hasRole()generateToken()
Exemple de login session :
#[MainRoute(path: '/login', name: 'login')] final class LoginController extends AbstractController { #[Route(path: '/', name: 'index', methods: ['GET', 'POST'])] public function index(): Response { if ($this->request->getMethod() === 'GET') { return $this->render('pages/auth/login.html.twig'); } $ok = $this->auth()->attempt([ 'email' => (string) $this->request->body('email'), 'password' => (string) $this->request->body('password'), ]); if (!$ok) { return $this->jsonError('Identifiants invalides', 401); } return $this->redirectToRoute('dashboard.index'); } }
Exemple de login token :
#[MainRoute(path: '/api', name: 'api')] final class ApiAuthController extends AbstractController { public function __construct(private UserRepository $userRepository) { } #[Route(path: '/login', name: 'login', methods: ['POST'])] public function login(): Response { $email = (string) $this->request->body('email'); $password = (string) $this->request->body('password'); $ok = $this->auth()->attempt([ 'email' => $email, 'password' => $password, ]); if (!$ok) { return $this->jsonError('Identifiants invalides', 401); } $user = $this->userRepository->findBy('email', $email); if ($user === null) { return $this->jsonError('Utilisateur introuvable', 401); } return $this->jsonSuccess([ 'token' => $this->auth()->generateToken($user), ]); } }
Twig expose :
auth_check()auth_user()auth_has_role()csrf_token()
PasswordManager
Le service PasswordManager fournit :
hash()verify()needsRehash()generate()getInfo()
Exemple :
$hash = $this->getPasswordManager()->hash('secret123'); $ok = $this->getPasswordManager()->verify('secret123', $hash);
Middlewares
Attributs supportés :
#[Middleware(...)]#[RateLimit(...)]#[Maintenance(...)]
Middlewares coeur :
AuthMiddlewareGuestMiddlewareRoleMiddlewareRateLimitMiddlewareExampleMiddleware
Exemple de middleware applicatif :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Middlewares; use Neo\Core\DI\Container; use Neo\Core\Security\Auth\AuthManager; use Neo\Core\Security\Middleware\Interface\MiddlewareInterface; final class AdminAccessMiddleware implements MiddlewareInterface { private AuthManager $auth; public function __construct(Container $container) { $this->auth = $container->get(AuthManager::class); } public function handle(): bool { return $this->auth->check() && $this->auth->hasRole('admin'); } }
Exemple d'utilisation :
#[MainRoute(path: '/admin', name: 'admin')] #[Middleware(use: AuthMiddleware::class, redirect: 'login.index')] #[Middleware(use: RoleMiddleware::class, params: ['role' => 'admin'])] final class DashboardController extends AbstractController { #[Route(path: '/', name: 'index', methods: ['GET'])] #[RateLimit(maxAttempts: 20, decaySeconds: 60)] public function index(): Response { return $this->render('pages/admin/index.html.twig'); } }
CSRF
Le manager CSRF stocke les tokens en session sous _csrf_tokens.
Comportement :
- génération via
generateToken() - expiration par défaut a 3600 secondes
- validation via
validateToken() - intégration dans les formulaires via
form_csrf()etcsrf_token()
Events
NeoPHP embarque un event dispatcher et plusieurs évènements coeur :
RequestEventResponseEventExceptionEvent
Les listeners applicatifs sont attendus dans src/<Projet>/App/Event/Listener.
Ils peuvent être déclarés :
- via
#[AsListener(event: ..., priority: ...)] - via
EventSubscriberInterface
Exemple complet :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Event; use Neo\Core\Event\AbstractEvent; final class UserRegisteredEvent extends AbstractEvent { public function __construct(public readonly int $userId) { } }
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Event\Listener; use Neo\Core\Event\Attribute\AsListener; use Neo\Src\Blog\App\Event\UserRegisteredEvent; #[AsListener(event: UserRegisteredEvent::class, priority: 0)] final class SendWelcomeEmailListener { public function handle(UserRegisteredEvent $event): void { $userId = $event->userId; } }
Exemple dans un contrôleur :
#[Route(path: '/register', name: 'register', methods: ['POST'])] public function register(): Response { $user = new \Neo\Src\Blog\Model\User(); $user->firstname = (string) $this->request->body('firstname'); $user->email = (string) $this->request->body('email'); $user->password = $this->getPasswordManager()->hash( (string) $this->request->body('password') ); $user->save(); $this->dispatch(new \Neo\Src\Blog\App\Event\UserRegisteredEvent((int) $user->id)); return $this->jsonSuccess([ 'id' => $user->id, ], 201); }
Crons
NeoPHP embarque un système de tâches planifiées éxécutables via la CLI.
Les crons applicatifs sont attendus dans le projet courant et peuvent être lancés manuellement ou automatiquement via le système d'exploitation.
Créer un cron
Pour générer un nouveau cron :
php bin/neo make:cron <NomDuCron> --project=Blog
Exemple :
php bin/neo make:cron CleanupTempFiles --project=Blog
Le générateur crée automatiquement le fichier du cron dans le projet cible.
Lister les crons
Pour afficher tous les crons disponibles d'un projet :
php bin/neo cron:list --project=Blog
Cette commande affiche notamment :
- le nom du cron
- sa description
- sa fréquence
- son statut
Exécuter les crons
Pour éxécuter tous les crons du projet :
php bin/neo cron:run --project=Blog
Cette commande est celle qui doit être planifiée automatiquement par le systeme d'exploitation.
Exécution automatique des crons
Linux
Sous Linux, les crons sont généralement pilotés via crontab.
Ouvrir la configuration cron :
crontab -e
Exécuter les crons NeoPHP toutes les minutes :
* * * * * php /path/to/project/bin/neo cron:run --project=Blog
Exemple concret :
* * * * * php /var/www/neophp/bin/neo cron:run --project=Blog
Vérifier les logs cron :
grep CRON /var/log/syslog
macOS
macOS supporte également crontab.
Ouvrir la configuration :
crontab -e
Ajouter :
* * * * * php /path/to/project/bin/neo cron:run --project=Blog
Exemple :
* * * * * php /Users/benjamin/Sites/neophp/bin/neo cron:run --project=Blog
Vérifier les tâches :
crontab -l
Windows
Sous Windows, utiliser le Planificateur de tâches.
Commande a éxécuter :
php C:\path\to\project\bin\neo cron:run --project=Blog
Exemple :
php C:\Sites\NeoPHP\bin\neo cron:run --project=Blog
Configuration conseillée :
- déclencheur : toutes les minutes
- programme :
php.exe - arguments :
C:\Sites\NeoPHP\bin\neo cron:run --project=Blog
Le Planificateur de tâches peut être ouvert avec :
Win + R -> taskschd.msc
Docker
Exemple avec une boucle simple :
while true; do php bin/neo cron:run --project=Blog sleep 60 done
Exemple via docker-compose :
services: cron: command: sh -c "while true; do php bin/neo cron:run --project=Blog; sleep 60; done"
Conseils
En production, il est recommandé :
- d'éxécuter
cron:runtoutes les minutes - de journaliser les erreurs via le
Logger - d'eviter les traitements bloquants trop longs
- d'utiliser des files d'attente pour les traitements lourds
- de surveiller les éxécutions via les logs applicatifs ou système
Cache, logs, mailer, profiler et erreurs
Cache
Le service Cache est piloté par cache.config.php.
Drivers disponibles :
filesstockage danssrc/<Projet>/Storage/<path>redisviapredis/predisarraystockage mémoire pour usage court ou test
API :
set()get()delete()clear()has()remember()
Exemple :
$this->getCache()->set('homepage.posts', $posts, 600); $posts = $this->getCache()->get('homepage.posts', []); $stats = $this->getCache()->remember('stats.daily', 300, fn() => $service->buildStats());
Logger
Le service Logger lit logger.config.php et gère :
- niveaux de logs
- channels
- rotation
- archivage zip
Niveaux supportés :
debuginfonoticewarningerrorcriticalalertemergency
Exemple :
$this->getLogger()->channel('framework')->error( 'Erreur metier', ['post_id' => 12], 'PostController::show' );
Mailer
Le dossier neo/Core/Utils/Mailer/ enregistre un service Mailer basé sur PHPMailer.
Configuration :
src/<Projet>/Config/mailer.config.php- driver courant via
default - expéditeur via
from - SMTP via
drivers.smtp
API principale :
to()subject()body()template()cc()bcc()attach()send()getSentMails()
Dans un contrôleur, getMailer() est disponible via l'extension de contrôleur.
Exemple :
$sent = $this->getMailer() ->to('user@example.com', 'John Doe') ->subject('Bienvenue') ->template('emails/welcome.html.twig', [ 'user' => $user, ]) ->send();
Si le mailer est désactivé, l'envoi est ignoré et un warning est journalisé.
Profiler
Le dossier neo/Core/Profiler/ active une barre de debug uniquement en HTTP et uniquement quand app.config.php definit environment = dev.
Collecteurs exposes :
- requête HTTP
- route et paramètres resolvés
- requêtes SQL
- évènements dispatchés
- logs
- utilisateur authentifié
- traductions résolues et cléfs manquantes
- mails envoyés
Le toolbar est injecté dans les réponses HTML.
Il est ignoré pour les JsonResponse, RedirectResponse et les contenus non HTML.
Gestion des erreurs
ErrorHandler :
- intercepte exceptions et erreurs PHP
- loggue les erreurs
- dispatch un
ExceptionEvent - rend
errors/<code>.html.twigsi présent - fournit un fallback HTML sinon
- affiche plus de détails en
dev
Exemple de vues d'erreur :
src/Blog/App/Views/errors/404.html.twig
src/Blog/App/Views/errors/500.html.twig
Exemple 404.html.twig :
{% extends 'layouts/base_layout.html.twig' %}
{% block content %}
<h1>404</h1>
<p>{{ message }}</p>
{% endblock %}
CLI et generateurs
Afficher l'aide globale :
php bin/neo
Afficher l'aide d'une commande :
php bin/neo <commande> --help
Commandes disponibles :
app:make:projectapp:delete:projectapp:sync:projectsapp:serveapp:make:serviceapp:composer:requireapp:make:deploymentasset:reloadcache:clearcron:listcron:rundebug:routergenerate:default:configmake:configmake:controllermake:cronmake:middlewaremake:eventmake:event:listenermake:crudmake:testmake:test:autorun:testrun:test:all
Générateurs principaux
Exemples :
php bin/neo app:make:project Blog php bin/neo make:controller PostController --project=Blog php bin/neo make:controller ApiPostController --api --project=Blog php bin/neo app:make:service Mail --project=Blog php bin/neo make:middleware AdminAccess --project=Blog php bin/neo make:event UserRegistered --project=Blog php bin/neo make:event:listener SendWelcomeEmail --event=UserRegistered --project=Blog php bin/neo make:cron CleanupTempFiles --project=Blog php bin/neo make:crud Post --project=Blog php bin/neo make:config mail --project=Blog
Exemple de commande intéractive de config :
php bin/neo make:config mail --project=Blog
Tu peux ensuite saisir par exemple :
smtp.hostsmtp.portsmtp.usersmtp.pass
Le générateur écrira un tableau PHP imbrique.
Maintenance de projet
Exemples :
php bin/neo generate:default:config --project=Blog php bin/neo app:composer:require league/flysystem --project=Blog php bin/neo app:sync:projects php bin/neo app:serve Blog php bin/neo debug:router --project=Blog php bin/neo cache:clear --project=Blog php bin/neo asset:reload --project=Blog
Tests PHPUnit
Le framework embarque une couche de test par projet avec PHPUnit 11.
Commandes disponibles :
make:testmake:test:autorun:testrun:test:all
Au premier make:test ou make:test:auto, NeoPHP génère :
src/<Projet>/Tests/bootstrap.phpsrc/<Projet>/Tests/phpunit.xmlsrc/<Projet>/Tests/Config/database.config.test.php- les dossiers
Unit,Feature,Database,Middleware
Classes de base :
TestCaseFeatureTestCaseDatabaseTestCaseMiddlewareTestCase
Fonctionnalités confirmées :
- simulation de requêtes HTTP pour les tests feature
- transactions et rollback automatique pour les tests database
- surcharge de config via
*.config.test.php - synchronisation du schema dev vers la base de test
- rapports
junit.xmlet couverture HTML
Tests manuels
Exemples :
php bin/neo make:test UserServiceTest --type=unit --project=Blog php bin/neo make:test UserControllerTest --type=feature --project=Blog php bin/neo make:test UserRepositoryTest --type=database --project=Blog php bin/neo make:test AuthMiddlewareTest --type=middleware --project=Blog
Génération automatique avec #[Test]
Le système automatique repose sur l'attribut Neo\Core\Testing\Attribute\Test.
Il peut être posé :
- sur une classe
- sur une méthode publique
Signature actuelle :
#[Test(
type: 'auto',
cases: [],
route: null,
httpMethod: 'GET',
dataset: [],
skip: false,
extends: null
)]
Ce que fait make:test:auto :
- prépare le scaffold PHPUnit si besoin
- scanne tous les fichiers PHP du projet
- charge les classes qui contiennent
#[Test] - lit l'attribut au niveau classe et méthode
- déduit un type de test
- choisit un template
- génère le fichier dans
Tests/<Type>/
Inférence du type si type = auto :
Repository=>databaseController=>featureMiddleware=>middleware- sinon =>
unit
Exemple sur une classe de service :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Services; use Neo\Core\Testing\Attribute\Test; #[Test(type: 'unit', cases: ['it_works', 'returns_slug'])] final class SlugService { public function slugify(string $value): string { return strtolower(trim(str_replace(' ', '-', $value))); } }
Exemple sur un repository :
<?php declare(strict_types=1); namespace Neo\Src\Blog\Repository; use Neo\Core\Database\ORM\Repository\AbstractRepository; use Neo\Core\Testing\Attribute\Test; use Neo\Src\Blog\Model\User; #[Test( type: 'database', cases: ['find_by_email', 'save'], dataset: [ 'table' => 'users', 'data' => [ 'firstname' => 'John', 'email' => 'john@example.com', ], ], )] final class UserRepository extends AbstractRepository { protected string $modelClass = User::class; }
Exemple sur une méthode de contrôleur :
<?php declare(strict_types=1); namespace Neo\Src\Blog\App\Controllers; use Neo\Core\Controller\AbstractController; use Neo\Core\Http\Response\Response; use Neo\Core\Routing\Attribute\MainRoute; use Neo\Core\Routing\Attribute\Route; use Neo\Core\Testing\Attribute\Test; #[MainRoute(path: '/login', name: 'login')] final class AuthController extends AbstractController { #[Test( route: '/login', httpMethod: 'POST', cases: ['returns_success', 'rejects_invalid_credentials'] )] public function login(): Response { return $this->jsonSuccess(); } }
Options utiles :
php bin/neo make:test:auto --project=Blog php bin/neo make:test:auto --project=Blog --only=database php bin/neo make:test:auto --project=Blog --dry-run php bin/neo make:test:auto --project=Blog --force php bin/neo run:test:all --project=Blog --coverage
Deploiement
La commande app:make:deployment prépare un deploiement FTP a partir de src/<Projet>/Config/deploy.config.php.
Le flux implémente :
- patch temporaire de
app.config.phpenprod - patch temporaire de
public/index.php - fusion du
composer.jsonracine et ducomposer.jsonprojet - installation des dépendances en
--no-dev - compression de
vendor/ - upload FTP du framework, du projet et du dossier public
- upload de
vendor.zip - éxécution d'un script temporaire de dézippage côté serveur
Clés attendues dans deploy.config.php :
ftp.hostftp.userftp.passremote.domainremote.framework_dirremote.public_dir
Exemple :
<?php declare(strict_types=1); return [ 'ftp' => [ 'host' => 'ftp.example.com', 'user' => 'my-user', 'pass' => 'my-pass', ], 'remote' => [ 'domain' => 'example.com', 'framework_dir' => 'domains/example.com/neo', 'public_dir' => 'domains/example.com/public_html', ], ];
Dépendances et prérequis
PHP
- PHP
>= 8.1
Extensions PHP requises
ext-pdoext-zipext-libxmlext-domext-ftpext-iconvext-curlext-simplexmlext-fileinfo
Dépendances principales
twig/twigtwig/intl-extrapsr/containermatthiasmullie/minifywikimedia/less.phpphpmailer/phpmailerpredis/predis
Dépendances de développement
phpunit/phpunitphpstan/phpstan
Résume
NeoPHP couvre aujourd'hui :
- noyau applicatif multi-projets
- conteneur DI avec autowiring
- configuration par fichiers PHP
- couche HTTP, responses, sessions, cookies et flash
- routing par attributs
- contrôleurs et vues Twig
- pipeline d'assets CSS, JS et Less
- traduction et helpers Twig/PHP
- QueryBuilder, ORM et repositories
- formulaires, validation, upload et CSRF
- auth session / token, mot de passe et middlewares
- events et crons
- cache, logs, mailer, profiler et gestion des erreurs
- CLI de génération et d'administration
- testing manuel et génération automatique via
#[Test] - déploiement FTP intégré
Le point clé du dépôt reste le même :
neo/contient le moteursrc/contient les applicationsphp bin/neo ...pilote l'essentiel du workflow