Database

Concept Core uses Illuminate Database (Capsule + Eloquent) — the same layer as Laravel Eloquent. Connection, models, migrations, and seeders are integrated through the core DatabaseServiceProvider.

HTTP and CLI share the same configuration (config/db.php, .env); schema updates use console db:* commands (see Console). Component migration and seeder paths are wired by ComponentsServiceProvider (see Creating a component).

Core classes

ClassRole
DatabaseServiceProvider Capsule, Eloquent, Migrator, migration/seeder registries
DatabaseInterface capsule() wrapper for query builder outside models
MigrationRegistry Paths to migration directories (app + components)
SeederRegistry / SeederManager Seeder list and execution via db:seed

Connection

Settings from config/db.php are overridden by environment variables (DB_HOSTdb.host, etc.). Full table — in Configuration → db.php and environment variables in the same section.

// config/db.php
return [
    'db' => [
        'driver'   => 'mysql',
        'host'     => '127.0.0.1',
        'port'     => '3306',
        'database' => 'concept_db',
        'username' => 'root',
        'password' => '',
        'charset'  => 'utf8mb4',
    ],
];

In DatabaseServiceProvider::boot() a global Illuminate\Database\Capsule\Manager is created, bootEloquent() is called, and a Dispatcher is attached for query events. SQL is logged when log.db_queries is enabled (QueryLoggerstorage/logs/query.log). Telemetry TelemetryEvent::DB_QUERY_EXECUTED is recorded separately when telemetry.enabled and telemetry.db_queries are on.

Low-level access — Concept\Core\Services\Database\Contracts\DatabaseInterface and the capsule() method (for example in unique / exists rules). The db.port key exists in config, but the core getConnectionOptions() currently passes only driver, host, database, username, password, and charset to Capsule.

Eloquent models

The application extends Concept\App\Models\BaseModel (which extends Illuminate\Database\Eloquent\Model). Components declare models in their own namespace, for example Concept\Components\AuthAdmin\Models\UserModel.

Recommended practices:

  • FIELD_* field constants instead of “magic” strings in queries and fillable;
  • BaseModel::getId(), FIELD_ID, SORT_ASC / SORT_DESC;
  • $fillable, $hidden, $casts, SoftDeletes — same as Laravel;
  • inject the model into controller or service constructor — autowiring (UserModel $userModel).
use Concept\App\Models\BaseModel;
use Illuminate\Database\Eloquent\SoftDeletes;

class UserModel extends BaseModel
{
    use SoftDeletes;

    public const string FIELD_EMAIL = 'email';
    public const string FIELD_PASSWORD = 'password';
    public const string FIELD_CREATED_AT = 'created_at';

    protected $table = 'users';

    protected $fillable = [self::FIELD_EMAIL, /* ... */];
    protected $hidden = [self::FIELD_PASSWORD];
}

Queries in code

Queries are built via $model->newQuery() (query builder / Eloquent), not via static calls on the model class. This aligns with injecting the model in the constructor.

public function __construct(
    private readonly UserModel $userModel,
    private readonly ConfigInterface $config,
) {}

public function index(): ResponseInterface
{
    $users = $this->userModel
        ->newQuery()
        ->orderBy(UserModel::FIELD_CREATED_AT, BaseModel::SORT_DESC)
        ->paginate($this->config->getInt('pagination.per_page', 10))
        ->withQueryString();

    return $this->viewResponse->create('@auth-admin/users/list', ['users' => $users]);
}

Creating records after validation — newQuery()->create([...]) or validated() from FormRequest (see Requests and validation). Official references: Eloquent, Query Builder, Pagination.

Migrations

Migration directories are registered in config/migrations.php (application) and via ComponentInterface::migrationPaths() on each component class. During ComponentsServiceProvider::boot() paths from all enabled components are added to MigrationRegistry.

// config/migrations.php
return [
    'migrations' => [
        'table' => 'migrations',
        'paths' => ['database/migrations'],
    ],
];

// AuthAdminComponent::migrationPaths()
public function migrationPaths(): array
{
    return [
        $this->pathManager->toRelative($this->componentDir . '/Database/Migrations'),
    ];
}

A migration file is an anonymous class extending Migration, with up() / down() methods. Example file naming: V2026_03_23_120000_CreatePagesTable.php.

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Capsule\Manager as CapsuleManager;

return new class extends Migration
{
    public function up(): void
    {
        CapsuleManager::schema()->create('pages', function (Blueprint $table) {
            $table->increments('id');
            $table->string('slug', 255)->unique();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        CapsuleManager::schema()->dropIfExists('pages');
    }
};

db:migrate creates the migrations table if it does not exist yet, and applies all pending files from all registered paths. Laravel docs: Migrations.

Seeders

Seeder classes are listed in config/seeders.php and via ComponentInterface::seeders() on each component class. SeederManager calls run() only for classes that implement Concept\Core\Services\Database\Contracts\SeederInterface. A seeder usually also extends Illuminate\Database\Seeder.

// config/seeders.php
use Database\Seeders\PageSeeder;

return [
    'seeders' => [
        'list' => [PageSeeder::class],
    ],
];

// database/seeders/PageSeeder.php — SeederInterface required
use Concept\Core\Services\Database\Contracts\SeederInterface;
use Illuminate\Database\Seeder;

class PageSeeder extends Seeder implements SeederInterface
{
    public function run(): void { /* ... */ }
}

db:seed with no options runs all registered seeders. To run a single seeder, pass the class full FQCN via -c (or --class) — short class names are not supported. Quote the value because of backslashes in the namespace:

php bin/console.php db:seed -c "Concept\Components\AuthAdmin\Database\Seeders\UserSeeder"

List available classes: php bin/console.php seeders:list.

Console

CommandPurpose
db:migrateApply new migrations
db:rollbackRollback the last batch
migration:listList migrations and status
db:seedAll seeders; -c "Fully\Qualified\Class" — one seeder (full FQCN)
seeders:listRegistered seeders
php bin/console.php db:migrate
php bin/console.php db:seed
php bin/console.php db:seed -c "Database\Seeders\PageSeeder"
php bin/console.php db:seed -c "Concept\Components\AuthAdmin\Database\Seeders\UserSeeder"

Before the first db:migrate, ensure the database exists and DB_* in .env match your environment.

File layout

config/db.php
config/migrations.php
config/seeders.php
database/migrations/          # application migrations
database/seeders/             # application seeders (namespace Database\Seeders)
src/App/Models/BaseModel.php
src/Components/*/Models/
src/Components/*/Database/Migrations/
src/Components/*/Database/Seeders/

Query API, relations, scopes, soft deletes, and enum casts — same as Laravel Eloquent; differences are only in bootstrap (Capsule instead of full Laravel) and migration/seeder path registration via config and components.