Search by

kusikusicms / models

kusikusicms

Core Eloquent models for KusikusiCMS.

Package info

github.com/kusikusicms/models

pkg:composer/kusikusicms/models

Statistics

Installs: 5

Dependents: 2

Suggesters: 0

Stars: 0

v13.1.0 2026-11-01 14:02 UTC

This package is auto-updated.

Last update: 2026-11-01 14:12:26 UTC


README

Read-only split. This repository is automatically split from packages/models in the kusikusicms/monorepo. Please open issues and pull requests in the monorepo; pull requests sent here are closed automatically. Install it with composer require kusikusicms/models.

This package provides the core Eloquent models and helpers for KusikusiCMS on Laravel, including:

  • Entity with short string IDs, lifecycle events, scopes, and content helpers
  • EntityContent and EntityRelation
  • Collections helpers for transforming related contents
  • Factory states for realistic testing/seed data

Version: 13.x

Installation

  1. Require the package (when published):
composer require kusikusicms/models
  1. Configuration is auto-merged. To publish the config file:
php artisan vendor:publish --tag=kusikusicms-config

This will create config/kusikusicms/models.php.

Configuration

All keys live under the kusikusicms.models.* namespace:

  • short_id_length (int, default 10) — length of generated IDs
  • short_id_max_attempts (int, default 5) — retries before throwing on collision
  • default_language (string, default en) — used by content helpers/scopes

See docs/configuration.md for details.

Migrations

The service provider loads the package migrations automatically. They use timezone-aware timestamps (timestampsTz, softDeletesTz) and FK helpers.

Run migrations as usual:

php artisan migrate

Artisan commands

kusikusicms:make:model

Creates a new entity model in the host app. It asks for the model name, converts it to ProperCase (blog post becomes BlogPost), and writes app/Models/{Name}.php extending KusikusiCMS\Models\Entity:

php artisan kusikusicms:make:model
<?php

namespace App\Models;

use KusikusiCMS\Models\Entity;

class BlogPost extends Entity
{

}
  • The command is also available under the alias k:make:model.
  • It refuses to overwrite an existing model and creates app/Models/ if it does not exist yet.
  • It is only registered in console contexts, so it is available to artisan once the package is installed.

Quick start

Create an entity

use KusikusiCMS\Models\Entity;

$entity = Entity::create(['model' => 'Article']); // short ID assigned automatically
$entity = Entity::create(['id' => 'home', 'model' => 'Home']); // short ID given
$entity = Entity::create(['model' => 'Box', 'props' => ['width' => 23]]); // with props

Attach contents records

$entity->createContents([
  'title' => 'Hello',
  'body'  => 'World',
]); // defaults to config('kusikusicms.models.default_language')

Query with content-aware scopes

  • Load related contents with optional filters:
Entity::query()->withContents(['lang' => 'es', 'fields' => ['title']])->get();
  • Order by a content field (uses configured default language if omitted):
Entity::query()->orderByContent('title', 'desc')->get();
  • Filter by a content field:
// Current behavior is characterized in tests; future version will compare `field =` and apply operator to `text` only.
Entity::query()->whereContent('title', 'like', 'Hello%')->get();

Relations graph scopes

Entity::query()->childrenOf($parentId)->get();
Entity::query()->parentOf($childId)->first();
Entity::query()->ancestorsOf($entityId)->get();
Entity::query()->descendantsOf($entityId)->get();
Entity::query()->siblingsOf($entityId)->get();

Query builder

All the filters above are real, typed methods of KusikusiCMS\Models\Builders\EntityBuilder, which Entity registers with #[UseEloquentBuilder]. Entity::query() and static calls such as Entity::childrenOf($id) return it.

Methods: currentlyLive, ofModel, childrenOf, parentOf, ancestorsOf, descendantsOf, siblingsOf, rootOf, relatedBy, relating, withContents, orderByContent, whereContent.

The language used by withContents() is kept on the query, so orderByContent() and whereContent() reuse it for that query only.

Add a scope

A subclass can add its own scopes as usual; they chain after the builder methods and no custom builder is needed:

class Page extends Entity
{
    public function scopePublishedBy(Builder $query, string $author): Builder
    {
        return $query->where('props->author', $author);
    }
}

Page::query()->childrenOf($id)->publishedBy('ana')->get();

Override a built-in filter

Extend EntityBuilder and register it on your subclass:

class PageBuilder extends EntityBuilder
{
    public function childrenOf(string $entityId, ?string $tag = null): static
    {
        return parent::childrenOf($entityId, $tag)->where('model', 'page');
    }
}

#[UseEloquentBuilder(PageBuilder::class)]
class Page extends Entity {}

Page::childrenOf($id)->get(); // uses PageBuilder::childrenOf

You can also return it from newEloquentBuilder($query) instead of using the attribute.

Rules:

  • A model-level scopeChildrenOf() on a subclass is ignored, because the builder method always wins.
  • A subclass with its own builder must extend EntityBuilder.

Migration notes

The filters used to be scopeXxx() methods on Entity. Fluent calls are unchanged, but:

  • Calling scopeChildrenOf() and the like directly no longer works; call the builder method instead.
  • hasNamedScope('childrenOf') returns false and ->scopes(['childrenOf' => [...]]) is not supported; call the method directly.
  • Custom builders returned by Entity subclasses must extend EntityBuilder.
  • descendantsOf() is now typed (array|int as the second argument).
  • If you use barryvdh/laravel-ide-helper, regenerate the helper file.

Factory states

Entity::factory()->draft()->create();
Entity::factory()->scheduled()->create();
Entity::factory()->live()->create();
Entity::factory()->expired()->create();

// With contents helper
Entity::factory()->live()->withContents(['title' => 'Hello'])->create();

Events

Entity dispatches custom events via $dispatchesEvents so host apps can listen to them. Example:

use Illuminate\Support\Facades\Event;
use KusikusiCMS\Models\Events\EntityCreated;

Event::listen(EntityCreated::class, fn ($event) => logger('Entity created', ['id' => $event->entity->id]));

See docs/events.md for the full list.

Collections helpers

When loading contents, use collection helpers to transform related rows into convenient arrays:

$entities = Entity::withContents(['lang' => 'en'])->get();
$entities->flattenContentsByField();      // ['title' => 'Hello', 'body' => 'World']
$entities->groupContentsByField();        // ['title' => ['en' => 'Hello'], ...]
$entities->groupContentsByLang();         // ['en' => ['title' => 'Hello'], ...]

Testing

  • Package tests (Orchestra Testbench):
cd packages/models
../../vendor/bin/phpunit

Extensibility

Replace the ID generator

Bind your own generator to KusikusiCMS\Models\Support\IdGenerator in the host app container to control short ID generation.

See docs/id-generator.md for an example.

Documentation

  • docs/installation.md
  • docs/configuration.md
  • docs/models-and-scopes.md
  • docs/events.md
  • docs/factories-and-seeding.md
  • docs/testing.md
  • docs/id-generator.md
  • docs/design-decisions.md

License

MIT