1. Go to this page and download the library: Download astral-php/astral library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
astral-php / astral example snippets
// Imports à supprimer
use App\Controllers\Api\ArticleApiController;
use App\Controllers\Api\CategoryApiController;
use App\Dao\ArticleDao;
use App\Dao\CategoryDao;
// Bindings à supprimer (section DAOs)
$container->singleton(ArticleDao::class, ...);
$container->singleton(CategoryDao::class, ...);
// Bindings à supprimer (section Contrôleurs API)
$container->bind(ArticleApiController::class, ...);
$container->bind(CategoryApiController::class, ...);
// Imports à supprimer
use App\Controllers\Api\ArticleApiController;
use App\Controllers\Api\CategoryApiController;
// Groupe de routes à supprimer
$router->group('/api/v1', function (Router $r): void {
$r->get('/articles', ...);
// ...
}, [CorsMiddleware::class, BearerTokenMiddleware::class]);
// Inspecter une variable (HTML ou CLI)
dump($user, $request->body);
// Dump and die — affiche et stoppe le script
dd($payload);
// Injecter Session dans un contrôleur
public function __construct(View $view, Session $session) { … }
// Enregistrer un message flash (avant redirect)
$this->session->flash('success', 'Utilisateur créé avec succès.');
$this->redirect('/users');
// Dans la vue suivante (lu une seule fois)
if ($session->hasFlash('success')):
// Dans un contrôleur (méthode héritée de AbstractController)
$v = $this->validate($request->body, [
'name' => 'ormulaire avec les erreurs
$this->render('users/create', ['errors' => $v->errors()]);
return;
}
// Dans chaque formulaire POST/PUT/DELETE
<form method="POST" action="/users">
<?= $csrf->field()
// config/routes.php
$router->post('/users', UserController::class, 'store')
->middleware(CsrfMiddleware::class);
// ou sur un groupe entier
use Core\Middleware\CsrfMiddleware;
$router->group('', function (Router $r) {
$r->post('/users', UserController::class, 'store');
$r->post('/users/:id/delete', UserController::class, 'destroy');
}, [CsrfMiddleware::class]);
namespace App\Middleware;
use Core\Middleware\MiddlewareInterface;
use Core\Request;
use Core\Session;
final class AuthMiddleware implements MiddlewareInterface
{
public function __construct(private Session $session) {}
public function handle(Request $request, callable $next): void
{
if (!$this->session->has('user_id')) {
header('Location: /login');
exit;
}
$next();
}
}
$container->bind(AuthMiddleware::class, fn(Container $c) => new AuthMiddleware(
$c->make(Session::class),
));
// config/routes.php — tous les verbes HTTP
$router->get('/users', UserController::class, 'index');
$router->post('/users', UserController::class, 'store');
$router->put('/users/:id', UserController::class, 'update');
$router->patch('/users/:id', UserController::class, 'patch');
$router->delete('/users/:id', UserController::class, 'destroy');
// Spoofing HTML (forms ne supportent que GET/POST)
<form method="POST" action="/users/42">
<input type="hidden" name="_method" value="DELETE">
<?= $csrf->field()
// Injecter Logger dans un service ou contrôleur
public function __construct(private Logger $logger) {}
$this->logger->info('Utilisateur créé', ['id' => $userId]);
$this->logger->warning('Tentative suspecte', ['ip' => $_SERVER['REMOTE_ADDR']]);
$this->logger->error('Connexion BDD échouée', ['driver' => 'mysql']);
// Dans un contrôleur
$page = (int) $request->query('page', 1);
$result = $this->userDao->paginate(page: $page, perPage: 15);
// $result contient :
// [
// 'data' => [...], // enregistrements de la page
// 'total' => 150, // nombre total d'enregistrements
// 'pages' => 10, // nombre total de pages
// 'current' => 2, // page courante
// 'per_page' => 15,
// ]
$this->render('users/index', $result);
// Dans la vue — navigation simple
for ($i = 1; $i <= $pages; $i++):
// Tester une action sans HTTP
$response = $controller->index();
assert($response->getStatus() === 200);
assert(str_contains($response->getContent(), 'Liste des utilisateurs'));
// Dans un contrôleur — retourner la réponse (plus d'exit)
public function store(): Response
{
// …
return $this->render('user/create', ['errors' => $v->errors()]);
// ou
return $this->redirect('/users');
// ou
return $this->json(['id' => $id], 201);
}
// Injecter Cache dans un contrôleur ou service
public function __construct(private Cache $cache) {}
// Mettre en cache 1 heure
$users = $this->cache->remember('users.all', 3600, fn() => $this->userDao->findAll());
// Invalider après une modification
$this->cache->forget('users.all');
// Tout vider
$this->cache->flush();
// app/Console/SeedUsersCommand.php
namespace App\Console;
use Core\Console\CommandInterface;
use Core\Console\Console;
final class SeedUsersCommand implements CommandInterface
{
public function getName(): string { return 'db:seed'; }
public function getDescription(): string { return 'Insère des données de test'; }
public function execute(array $args, Console $console): int
{
// … insérer des données …
$console->success('Base de données peuplée.');
return 0;
}
}
// bin/console — enregistrer la commande
$console->register(new App\Console\SeedUsersCommand());
// app/Providers/MailServiceProvider.php
namespace App\Providers;
use Core\Container;
use Core\ServiceProviderInterface;
final class MailServiceProvider implements ServiceProviderInterface
{
public function register(Container $container, array $appConfig, array $dbConfig): void
{
$container->singleton(Mailer::class, fn() => new Mailer(
host: $_ENV['MAIL_HOST'] ?? 'localhost',
port: (int) ($_ENV['MAIL_PORT'] ?? 587),
));
}
}
// config/dependencies.php — ajouter à la liste
return [
FrameworkServiceProvider::class,
DatabaseServiceProvider::class,
AppServiceProvider::class,
App\Providers\MailServiceProvider::class, // ← nouveau
];
// Corps JSON automatiquement décodé (Content-Type: application/json)
$data = $request->body; // tableau associatif
// Répondre en JSON
$this->json(['id' => 1, 'name' => 'Alice'], 201);
// Vérifier le type de requête
$request->isJson(); // true si Content-Type: application/json
$request->isXhr(); // true si X-Requested-With: XMLHttpRequest
// Dans un DAO concret
/** @return list<Article> */
protected function articlesOf(int $categoryId): array
{
return $this->hasMany(
relatedClass: Article::class,
table: 'articles',
foreignKey: 'category_id',
localId: $categoryId,
orderBy: 'created_at',
direction: 'DESC',
);
}
// Dans un DAO concret
public function categoryOf(Article $article): ?Category
{
return $this->belongsTo(
relatedClass: Category::class,
table: 'categories',
foreignId: $article->category_id,
);
}
// Charger une catégorie ET ses articles
$result = $categoryDao->findWithArticles(categoryId: 3);
// $result['category'] → Category
// $result['articles'] → list<Article>
// Charger un article ET sa catégorie
$result = $articleDao->findWithCategory(articleId: 42);
// $result['article'] → Article
// $result['category'] → Category|null
// app/Dao/ArticleDao.php — relation belongs-to
final class ArticleDao extends AbstractDao
{
protected function getTable(): string { return 'articles'; }
protected function getModelClass(): string { return Article::class; }
/** Retourne les articles d'une catégorie */
public function findByCategory(int $categoryId): array
{
return $this->hasMany(
relatedClass: Article::class,
table: 'articles',
foreignKey: 'category_id',
localId: $categoryId,
orderBy: 'created_at',
direction: 'DESC',
);
}
/** Charge la catégorie parente d'un article */
public function categoryOf(Article $article): ?Category
{
return $this->belongsTo(
relatedClass: Category::class,
table: 'categories',
foreignId: $article->category_id,
);
}
}
// Contrôleur
$this->auth->login($user); // connecte et régénère la session
$this->auth->logout(); // déconnecte
$this->auth->check(); // bool : connecté ?
$this->auth->guest(); // bool : non connecté ?
$this->auth->is(Role::ADMIN); // bool : rôle exact
$this->auth->can(Role::ADMIN, Role::USER); // bool : l'un de ces rôles
$this->auth->id(); // int|null
$this->auth->name(); // string
$this->auth->email(); // string
$this->auth->role(); // string (Role::GUEST si non connecté)
// Vue (variable $auth partagée globalement)
if ($auth->check()):
use Core\Auth\Middleware\AdminMiddleware;
use Core\Auth\Middleware\AuthMiddleware;
use Core\Auth\Middleware\GuestMiddleware;
// Route individuelle
$router->get('/profile', UserController::class, 'profile')
->middleware(AuthMiddleware::class);
// Groupe protégé par authentification
$router->group('', function (Router $r): void {
$r->get('/dashboard', DashboardController::class, 'index');
$r->get('/settings', SettingsController::class, 'index');
}, [AuthMiddleware::class]);
// Zone admin
$router->group('/admin', function (Router $r): void {
$r->get('/dashboard', AdminController::class, 'index');
$r->get('/users', AdminController::class, 'users');
}, [AdminMiddleware::class]);
// Routes invités seulement
$router->get('/login', AuthController::class, 'loginForm')->middleware(GuestMiddleware::class);
$router->post('/login', AuthController::class, 'login')->middleware(GuestMiddleware::class);
$router->get('/register', AuthController::class, 'registerForm')->middleware(GuestMiddleware::class);
$router->post('/register', AuthController::class, 'register')->middleware(GuestMiddleware::class);
// Créer un admin
$userDao->createUser('Alice', '[email protected]', 'secret', Role::ADMIN);
// Promouvoir un utilisateur existant
$userDao->promote(userId: 5, role: Role::ADMIN);
// Lister tous les admins
$admins = $userDao->findByRole(Role::ADMIN);
// app/Events/RoleChanged.php
final class RoleChanged implements EventInterface
{
public function __construct(
public User $user,
public string $oldRole,
public string $newRole,
public int $changedBy,
) {}
}
$user->isAdmin(); // bool
$user->isUser(); // bool
$user->hasRole(Role::ADMIN, Role::USER); // bool : l'un de ces rôles
// app/Events/OrderPlaced.php
final class OrderPlaced implements EventInterface
{
public function __construct(
public Order $order,
public User $customer,
) {}
}
// app/Listeners/SendOrderConfirmation.php
final class SendOrderConfirmation implements ListenerInterface
{
public function __construct(private Mailer $mailer) {}
public function handle(EventInterface $event): void
{
assert($event instanceof OrderPlaced);
$this->mailer->send($event->customer->email, 'Commande reçue', '…');
}
}
final class ShopSubscriber implements SubscriberInterface
{
public function subscribe(EventDispatcher $dispatcher): void
{
$dispatcher->listen(OrderPlaced::class, SendOrderConfirmation::class);
$dispatcher->listen(OrderShipped::class, SendShippingNotification::class);
}
}
// Dans AppServiceProvider :
$dispatcher->subscribe(ShopSubscriber::class);
declare(strict_types=1);
use Database\Migration\Migration;
class CreateArticlesTable extends Migration
{
public function up(\PDO $pdo): void
{
$driver = $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
if ($driver === 'sqlite') {
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
user_id INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
SQL);
} else {
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `articles` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`content` TEXT NOT NULL,
`user_id` INT UNSIGNED NOT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
SQL);
}
}
public function down(\PDO $pdo): void
{
$pdo->exec('DROP TABLE IF EXISTS articles');
}
}
// app/Controllers/Api/PostApiController.php
final class PostApiController extends AbstractApiController
{
public function __construct(
private PostDao $postDao,
private Request $request,
) {}
public function index(): JsonResponse
{
$result = $this->postDao->paginate(page: 1, perPage: 15);
return $this->paginated(
items: array_map(fn($p) => $p->toArray(), $result['data']),
pagination: $result,
);
}
public function show(string $id): JsonResponse
{
$post = $this->postDao->findById((int) $id);
return $post !== null
? $this->success($post->toArray())
: $this->notFound("Post #{$id} introuvable.");
}
public function store(): JsonResponse
{
$v = $this->validate((array) $this->request->post(), [
'title' => '
bash
rm app/Controllers/Api/ArticleApiController.php
rm app/Controllers/Api/CategoryApiController.php
rmdir app/Controllers/Api # si le dossier est vide
ini
API_KEY=change-me-generate-a-secure-key
# Générer : php -r "echo bin2hex(random_bytes(32));"
✦ Astral MVC — Générateur de module
? Nom de l'entité (PascalCase) [Article] : Article
? Que souhaitez-vous générer ?
▶ [a] Tout (Model + DAO + Controller + Migration)
[m] Model seulement
[d] DAO seulement
[c] Controller seulement
[g] Migration seulement
Votre choix [a] :
? Type de contrôleur
▶ [resource] Web CRUD (HTML + vues)
[api] API REST (JSON)
[empty] Vide (à compléter)
Votre choix [resource] :
? Confirmer la génération ? [O/n] :
✓ app/Models/Article.php
✓ app/Dao/ArticleDao.php
✓ app/Controllers/ArticleController.php
✓ database/migrations/…_create_articles_table.php
bash
# 1. Appliquer la migration
php bin/console migrate
# 2. Enregistrer dans app/Providers/AppServiceProvider.php
$container->singleton(ArticleDao::class, fn($c) => new ArticleDao($c->make(PDO::class)));
$container->bind(ArticleController::class, fn($c) => new ArticleController(
view: $c->make(View::class),
request: $c->make(Request::class),
session: $c->make(Session::class),
articleDao: $c->make(ArticleDao::class),
));
# 3. Ajouter les routes dans config/routes.php
$router->group('', function (Router $r): void {
$r->get('/articles', ArticleController::class, 'index');
$r->get('/articles/create', ArticleController::class, 'create');
$r->post('/articles', ArticleController::class, 'store');
$r->get('/articles/:id', ArticleController::class, 'show');
$r->get('/articles/:id/edit', ArticleController::class, 'edit');
$r->put('/articles/:id', ArticleController::class, 'update');
$r->post('/articles/:id/delete', ArticleController::class, 'destroy');
}, [AuthMiddleware::class, CsrfMiddleware::class]);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.