You’re reading the v0.1 development preview. APIs may change.
~ 3 min readUpdated 2026-10-29

Models, relationships, and pagination

Elang Data is a small model and repository layer over Doctrine DBAL. It makes common record operations explicit without claiming the full behavior of a mature ORM.

Define a model

php elang make:model Post --table=posts

Edit the generated class:

final class Post extends Elang\Data\Model
{
    protected const TABLE = 'posts';
    protected const FILLABLE = ['title', 'author_id', 'published'];
    protected const HIDDEN = ['internal_note'];
    protected const CASTS = ['id' => 'int', 'published' => 'bool'];
}

FILLABLE explicitly allows writes. Unexpected fields are rejected, rather than becoming assignments accidentally. HIDDEN excludes selected fields from JSON and array output; it does not replace authorization.

Read and write records

Inject Elang\Data\Models into your service:

$posts = $models->of(Post::class);
$post = $posts->create(['title' => 'Hello', 'author_id' => 1, 'published' => 0]);
$mine = $posts->where('author_id', 1);
$mine->update($post->id, ['title' => 'A clearer title']);

Repository filters are immutable: where and orderBy return a new scoped repository. Scoped update and delete include the ownership conditions in the statement. A create call must match every equality scope already applied to its repository.

Safe identifiers and values

Table and column identifiers must be simple application-controlled names. Operators and sort directions use allowlists. User values are bound as parameters. Do not offer arbitrary SQL fragments as a user-controlled search or sort option.

Pagination

$page = $posts->where('published', 1)->orderBy('id', 'DESC')->paginate(15, 1);

Paginator exposes items, total, perPage, currentPage, and lastPage. Its JSON representation contains data and meta. PageRequest validates an incoming page number, and per-page limits are bounded.

Relationships

Relationships are explicit queries, not invisible property loads:

$comments = $models->hasMany($post, Comment::class, 'post_id')->get();
$author = $models->belongsTo($post, UserProfile::class, 'author_id');

Plan eager loading and query counts for large collections. Studio helps inspect the queries actually executed. This layer does not currently provide a unit of work, identity map, or automatic graph persistence.

Factories and seeders

Factory accepts a repository and a definition callback. It supports bounded batch creation and overrides. Seeder classes implement Elang\Data\Seeder and run through php elang db:seed 'App\Database\DemoSeeder'. Production seeding requires review and the --force flag.

Need a hand?

Check the troubleshooting guide, look up a term in the glossary, or work through the complete task board project.

Search Elang documentation

Start typing to find your next step.