PHP code example of astral-php / astral

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]);

<?= $viewEngine->partial('partials/flash') 

<?= $viewEngine->partial('partials/pagination', ['current' => $current, 'pages' => $pages, 'baseUrl' => '/users', 'mode' => 'elastic']) 

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),
));

$router->group('/admin', function (Router $r) {
    $r->get('/dashboard', AdminController::class, 'index');
    $r->get('/users',     AdminController::class, 'users');
}, [AuthMiddleware::class, CsrfMiddleware::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());

// config/dependencies.php
return [
    FrameworkServiceProvider::class,   // Session, Logger, Cache, Request, View, CSRF
    DatabaseServiceProvider::class,    // PDO
    AppServiceProvider::class,         // DAOs, Contrôleurs
];

// 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,
        );
    }
}

use Core\Auth\Role;

Role::ADMIN  // 'admin'
Role::USER   // 'user'
Role::GUEST  // 'guest' (non connecté)

// 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', '…');
    }
}

$dispatcher->listen(OrderPlaced::class, SendOrderConfirmation::class);

$this->dispatcher->dispatch(new OrderPlaced($order, $user));

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
composer create-project astral-php/astral:1.2.7.1 mon-projet
bash
composer create-project astral-php/astral-shop ma_boutique
bash
rm app/Models/Article.php
rm app/Models/Category.php
rm app/Dao/ArticleDao.php
rm app/Dao/CategoryDao.php
bash
rm app/Controllers/Api/ArticleApiController.php
rm app/Controllers/Api/CategoryApiController.php
rmdir app/Controllers/Api   # si le dossier est vide
bash
rm database/migrations/2026_03_11_000002_create_categories_table.php
rm database/migrations/2026_03_11_000003_create_articles_table.php

public/index.php
    └── Application::run()
            ├── loadDotEnv()        — charge .env via vlucas/phpdotenv
            ├── Logger              — disponible dès le début
            ├── bootEnvironment()   — timezone, error_reporting
            ├── ensureDatabase()    — crée le dossier SQLite si absent
            ├── loadDependencies()  ← config/dependencies.php
            │       └── ErrorHandler::register()  — via FrameworkServiceProvider
            ├── Session::start()    — avant tout rendu
            ├── View::share()       — $session, $csrf, $auth dans toutes les vues
            ├── loadRoutes()        ← config/routes.php
            └── dispatch()          — pipeline middleware → contrôleur
                                       → HTML : ErrorHandler | API : JSON

[2026-03-09 14:32:01] ERROR: Connexion BDD échouée {"driver":"mysql"}

YYYY_MM_DD_HHMMSS_nom_en_snake_case.php  →  classe StudlyCase du suffixe
2026_03_11_100000_create_articles_table.php  →  CreateArticlesTable
bash
# 1. Générer un fichier de migration
php bin/console make:migration create_articles_table
# → database/migrations/2026_03_11_100000_create_articles_table.php

# 2. Voir l'état de toutes les migrations
php bin/console migrate:status

# 3. Appliquer les migrations en attente (nouveau batch)
php bin/console migrate

# 4. Annuler le dernier batch
php bin/console migrate:rollback

+---------------------------------------------------+---------+-------+---------------------+
| Migration                                         | Statut  | Batch | Exécutée le         |
+---------------------------------------------------+---------+-------+---------------------+
| 2026_03_11_000001_create_users_table.php          | applied | 1     | 2026-03-11 10:00:00 |
| 2026_03_11_100000_create_articles_table.php       | pending | -     | -                   |
+---------------------------------------------------+---------+-------+---------------------+
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]);