PHP code example of astral-php / astral-extend-orm

1. Go to this page and download the library: Download astral-php/astral-extend-orm 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-extend-orm example snippets



// app/Models/Article.php

declare(strict_types=1);

namespace App\Models;

use AstralOrm\Model\Model;
use AstralOrm\Relations\BelongsTo;
use AstralOrm\Relations\HasMany;

final class Article extends Model  // ← était : class Article (anémique)
{
    // Propriétés inchangées — compatibilité PDO::FETCH_CLASS préservée
    public int    $id          = 0;
    public string $title       = '';
    public string $body        = '';
    public string $slug        = '';
    public int    $category_id = 0;
    public int    $is_published = 0;
    public string $created_at  = '';

    // --- Configuration des traits ---

    protected array $fillable = ['title', 'body', 'slug', 'category_id', 'is_published'];

    protected array $casts = [
        'is_published' => 'bool',
        'created_at'   => 'datetime',
    ];

    protected array $hidden = [];  // rien à cacher sur Article

    // --- Relations ---

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'category_id');
    }
}

protected array $fillable = ['title', 'body', 'slug', 'category_id'];

// Dans un contrôleur — store()
$article = new Article();
$article->fill($request->body);

// Ou via la méthode statique (équivalent)
$article = Article::make($request->body);

// Créer une collection depuis des résultats bruts PDO (FETCH_ASSOC)
$articles = Article::collection($pdo->query('SELECT * FROM articles')->fetchAll());

protected array $casts = [
    'created_at'   => 'datetime',
    'published_at' => 'date',
    'is_published' => 'bool',
    'view_count'   => 'int',
    'score'        => 'float',
    'meta'         => 'array',   // colonne JSON en base
];

$article = $articleDao->findById(42);

// Accès casté explicite
$date    = $article->cast('created_at');   // DateTimeImmutable
$active  = $article->cast('is_published'); // bool
$meta    = $article->cast('meta');         // array

// Dans une vue
<?= $article->cast('created_at')->format('d/m/Y') 

final class User extends Model
{
    public string $first_name = '';
    public string $last_name  = '';
    public string $password   = '';
    public string $slug       = '';

    // Accessor : champ calculé (pas de colonne en base)
    public function getFullNameAttribute(): string
    {
        return trim($this->first_name . ' ' . $this->last_name);
    }

    // Mutator : hashage automatique
    public function setPasswordAttribute(string $value): void
    {
        $this->password = password_hash($value, PASSWORD_BCRYPT);
    }

    // Mutator : normalisation du slug
    public function setSlugAttribute(string $value): void
    {
        $this->slug = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $value), '-'));
    }
}

$user = new User();
$user->first_name = 'Jean';
$user->last_name  = 'Dupont';

$user->get('full_name');           // 'Jean Dupont' — via getFullNameAttribute()
$user->get('first_name');          // 'Jean'        — propriété directe (pas d'accessor)

$user->set('password', 'secret'); // hash automatique via setPasswordAttribute()
$user->set('first_name', 'Paul'); // assignation directe (pas de mutator)

// Dans un contrôleur — store()
$user = new User();
$user->set('password', $request->body['password']); // hashé automatiquement

protected array $hidden = ['password', 'remember_token', 'api_token'];

// Prioritaire sur $hidden si les deux sont définis
protected array $visible = ['id', 'name', 'email', 'created_at'];

// Dans un contrôleur API
$user = $userDao->findById(1);

$this->json($user->toArray());        // sans password, remember_token
$this->json($user->toJson());         // idem en JSON string

// Exclure un champ supplémentaire ponctuellement
$user->toArray(['email']);            // sans password ET sans email

// Accéder à tous les champs (override ponctuel)
$user->withHidden();                  // avec password et remember_token

// Dans un contrôleur API — réponse liste
$users = $userDao->findAll();
$data  = array_map(fn(User $u) => $u->toArray(), $users);
$this->json(['data' => $data]);

// Sur le modèle
public function category(): BelongsTo
{
    return $this->belongsTo(
        relatedClass: Category::class,
        foreignKey:   'category_id',      // clé sur CE modèle
        // ownerTable: auto-déduite → 'categories'
        // ownerKey:   'id' (défaut)
    );
}

// Dans un contrôleur ou DAO — résolution explicite
$article  = $articleDao->findById(42);
$category = $article->category()->resolve($pdo);  // → Category|null

// Sur le modèle
public function articles(): HasMany
{
    return $this->hasMany(
        relatedClass: Article::class,
        foreignKey:   'category_id',     // clé sur le modèle lié
        // relatedTable: auto-déduite → 'articles'
        // localKey:    'id' (défaut)
        // orderBy:     'id' (défaut)
        // direction:   'ASC' (défaut)
    );
}

// Résolution explicite
$category = $categoryDao->findById(3);
$articles = $category->articles()->resolve($pdo);  // → Article[]

// Dans un DAO ou contrôleur
$categories = $categoryDao->findAll();   // 1 requête

// Charge tous les articles de toutes les catégories en 1 requête supplémentaire
// et injecte la propriété 'articles' sur chaque Category
$categories = HasMany::eagerLoad(
    parents:      $categories,
    relatedClass: Article::class,
    foreignKey:   'category_id',
    propertyName: 'articles',          // propriété dynamique injectée sur chaque parent
    pdo:          $pdo,
    // table:     'articles'           // auto-déduit si omis
    // orderBy:   'created_at'
    // direction: 'DESC'
);

// Dans la vue
foreach ($categories as $category) {
    foreach ($category->articles as $article) {  // propriété injectée
        echo $article->title;
    }
}


declare(strict_types=1);

namespace App\Models;

use AstralOrm\Model\Model;
use AstralOrm\Relations\BelongsTo;

final class Article extends Model
{
    public int    $id          = 0;
    public string $title       = '';
    public string $body        = '';
    public string $slug        = '';
    public int    $category_id = 0;
    public int    $user_id     = 0;
    public int    $is_published = 0;
    public string $created_at  = '';

    protected array $fillable = ['title', 'body', 'slug', 'category_id', 'is_published'];

    protected array $casts = [
        'is_published' => 'bool',
        'created_at'   => 'datetime',
    ];

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'category_id');
    }

    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id', 'users');
    }

    // Mutator : normalisation du slug
    public function setSlugAttribute(string $value): void
    {
        $this->slug = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $value), '-'));
    }

    // Accessor : extrait des 150 premiers caractères du body
    public function getExcerptAttribute(): string
    {
        return mb_strimwidth(strip_tags($this->body), 0, 150, '…');
    }
}


declare(strict_types=1);

namespace App\Models;

use AstralOrm\Model\Model;
use AstralOrm\Relations\HasMany;

final class User extends Model
{
    public int    $id             = 0;
    public string $first_name     = '';
    public string $last_name      = '';
    public string $email          = '';
    public string $password       = '';
    public string $remember_token = '';
    public string $role           = 'user';
    public string $created_at     = '';

    protected array $fillable = ['first_name', 'last_name', 'email', 'role'];

    protected array $casts = [
        'created_at' => 'datetime',
    ];

    protected array $hidden = ['password', 'remember_token'];

    public function articles(): HasMany
    {
        return $this->hasMany(Article::class, 'user_id');
    }

    // Accessor
    public function getFullNameAttribute(): string
    {
        return trim($this->first_name . ' ' . $this->last_name);
    }

    // Mutator
    public function setPasswordAttribute(string $value): void
    {
        $this->password = password_hash($value, PASSWORD_BCRYPT);
    }
}

public function store(Request $request): Response
{
    $v = $this->validate($request->body, [
        'title' => 'e', ['errors' => $v->errors()]);
    }

    $article = Article::make($request->body);  // fill() sécurisé
    $article->set('slug', $request->body['title']); // mutator normalise
    $article->created_at = date('Y-m-d H:i:s');

    $id = $this->articleDao->insert((array) $article);

    $this->session->flash('success', 'Article créé.');
    return $this->redirect('/articles/' . $id);
}

public function index(Request $request): JsonResponse
{
    $result   = $this->articleDao->paginate(page: 1, perPage: 15);
    $articles = HasMany::eagerLoad(
        parents:      $result['data'],
        relatedClass: Category::class,
        foreignKey:   'category_id',
        propertyName: 'category',
        pdo:          $this->pdo,
        table:        'categories',
    );

    $data = array_map(fn(Article $a) => array_merge(
        $a->toArray(),
        [
            'created_at' => $a->cast('created_at')?->format('d/m/Y H:i'),
            'excerpt'    => $a->get('excerpt'),  // accessor
            'category'   => $a->category ?? null,
        ]
    ), $articles);

    return $this->paginated($data, $result);
}

 /** @var Article $article */ 

use AstralOrm\Model\Traits\HasFillable;
use AstralOrm\Model\Traits\HasCasts;
use AstralOrm\Model\Traits\HasHidden;

final class Article
{
    use HasFillable;
    use HasCasts;
    use HasHidden;

    public int    $id    = 0;
    public string $title = '';
    // ...

    protected array $fillable = ['title', 'body'];
    protected array $casts    = ['created_at' => 'datetime'];
    protected array $hidden   = [];
}

astral-extend-orm/
├── src/
│   ├── Model/
│   │   ├── Model.php                  ← Classe de base (hérite de cette classe)
│   │   └── Traits/
│   │       ├── HasFillable.php        ← Hydratation de masse sécurisée
│   │       ├── HasCasts.php           ← Conversion de types déclarative
│   │       ├── HasAccessors.php       ← Accessors & Mutators par convention
│   │       ├── HasHidden.php          ← toArray() / toJson() avec $hidden / $visible
│   │       └── HasRelations.php       ← belongsTo() / hasMany() déclaratifs
│   ├── Relations/
│   │   ├── BelongsTo.php              ← Descripteur de relation N→1
│   │   └── HasMany.php                ← Descripteur de relation 1→N + eager loading
│   └── Exceptions/
│       └── CastException.php          ← Levée si cast inconnu ou impossible
├── tests/
│   ├── Model/
│   │   ├── HasFillableTest.php
│   │   ├── HasCastsTest.php
│   │   ├── HasAccessorsTest.php
│   │   └── HasHiddenTest.php
│   └── Relations/
│       └── RelationsTest.php          ← SQLite in-memory
├── composer.json
├── phpunit.xml
├── LICENSE
└── README.md