/cycle-bridge

Cycle ORM v2 bridge to Spiral Framework

Primary LanguagePHPMIT LicenseMIT

Cycle ORM v2 bridge to Spiral Framework

Latest Stable Version Unit tests Static analysis Codecov


Requirements

Make sure that your server is configured with following PHP version and extensions:

  • PHP 8.0+
  • PDO Extension with desired database drivers

Installation

To install the package:

composer require spiral/cycle-bridge

After package install you need to add bootloader Spiral\Cycle\Bootloader\BridgeBootloader from the package in your application.

use Spiral\Cycle\Bootloader as CycleBridge;
protected const LOAD = [
    CycleBridge\BridgeBootloader::class,
];

You can exclude Spiral\Cycle\Bootloader\BridgeBootloader bootloader and select only needed bootloaders by writing them separately.

use Spiral\Cycle\Bootloader as CycleBridge;

protected const LOAD = [
    // ...

    // Database
    CycleBridge\DatabaseBootloader::class,
    CycleBridge\MigrationsBootloader::class,

    // Close the database connection after every request automatically (Optional)
    // CycleBridge\DisconnectsBootloader::class,

    // ORM
    CycleBridge\SchemaBootloader::class,
    CycleBridge\CycleOrmBootloader::class,
    CycleBridge\AnnotatedBootloader::class,
    CycleBridge\CommandBootloader::class,

    // Validation (Optional)
    CycleBridge\ValidationBootloader::class,

    // DataGrid (Optional)
    CycleBridge\DataGridBootloader::class,

    // Database Token Storage (Optional)
    CycleBridge\AuthTokensBootloader::class,
    
    // Migrations and Cycle Scaffolders (Optional)
    CycleBridge\ScaffolderBootloader::class,
];

Migration from Spiral Framework v2.8

If you are migrating from Spiral Framework 2.8 at first, you have to get rid of old packages in composer.json:

"require": {
    "spiral/database": "^2.3",
    "spiral/migrations": "^2.0",
    "cycle/orm": "^1.0",
    "cycle/proxy-factory": "^1.0",
    "cycle/annotated": "^2.0",
    "cycle/migrations": "^1.0",
    ...
},

Then you need to replace some of bootloaders with provided by the package.

use Spiral\Cycle\Bootloader as CycleBridge;

protected const LOAD = [
    // ...

    // Databases
    // OLD
    // Framework\Database\DatabaseBootloader::class,
    // Framework\Database\MigrationsBootloader::class,
    // Close the database connection after every request automatically (Optional)
    // Framework\Database\DisconnectsBootloader::class,

    // NEW
    CycleBridge\DatabaseBootloader::class,
    CycleBridge\MigrationsBootloader::class,
    CycleBridge\DisconnectsBootloader::class,

    // ORM
    // OLD
    // Framework\Cycle\CycleBootloader::class,
    // Framework\Cycle\ProxiesBootloader::class,
    // Framework\Cycle\AnnotatedBootloader::class,

    // NEW
    CycleBridge\SchemaBootloader::class,
    CycleBridge\CycleOrmBootloader::class,
    CycleBridge\AnnotatedBootloader::class,

    // ...

    // DataGrid (Optional)
    // OLD
    // \Spiral\DataGrid\Bootloader\GridBootloader::class,

    // NEW
    CycleBridge\DataGridBootloader::class,

    // Database Token Storage (Optional)
    // OLD
    // Framework\Auth\TokenStorage\CycleTokensBootloader::class,

    // NEW
    CycleBridge\AuthTokensBootloader::class,

    // Framework commands
    // ...
    CycleBridge\CommandBootloader::class,
];

Note: CycleBridge\CommandBootloader should be added after the \Spiral\Bootloader\CommandBootloader to replace Cycle ORM 1 commands with new ones

That's it!

Configuration

Database

You can create config file app/config/database.php if you want to configure Cycle Database:

use Cycle\Database\Config;

return [
    /**
     * Database logger configuration
     */
    'logger' => [
        'default' => null, // Default log channel for all drivers (The lowest priority)
        'drivers' => [
            // By driver name (The highest priority)
            // See https://spiral.dev/docs/extension-monolog
            'runtime' => 'sql_logs',

            // By driver class (Medium priority)
            \Cycle\Database\Driver\MySQL\MySQLDriver::class => 'console',
        ],
    ],

    /**
     * Default database connection
     */
    'default' => env('DB_DEFAULT', 'default'),

    /**
     * The Cycle/Database module provides support to manage multiple databases
     * in one application, use read/write connections and logically separate
     * multiple databases within one connection using prefixes.
     *
     * To register a new database simply add a new one into
     * "databases" section below.
     */
    'databases' => [
        'default' => [
            'driver' => 'runtime',
        ],
    ],

    /**
     * Each database instance must have an associated connection object.
     * Connections used to provide low-level functionality and wrap different
     * database drivers. To register a new connection you have to specify
     * the driver class and its connection options.
     */
    'drivers' => [
        'runtime' => new Config\SQLiteDriverConfig(
            connection: new Config\SQLite\FileConnectionConfig(
                database: env('DB_DATABASE', directory('root') . 'runtime/app.db')
            ),
            queryCache: true,
        ),
        // ...
    ],
];

Monolog channels definition example

You can create config file app/config/monolog.php:

<?php

declare(strict_types=1);

use Monolog\Logger;

return [
    'globalLevel' => Logger::DEBUG,
    'handlers' => [
        'console' => [
            [
                'class' => \Monolog\Handler\SyslogHandler::class,
                'options' => [
                    'ident' => 'spiral-app',
                ]
            ]
        ],
        'sql_logs' => [
            [
                'class' => \Monolog\Handler\RotatingFileHandler::class,
                'options' => [
                    'filename' => __DIR__ . '/../../runtime/logs/sql.log'
                ]
            ]
        ]
    ],
];

Cycle ORM

You can create config file app/config/cycle.php if you want to configure Cycle ORM:

use Cycle\ORM\SchemaInterface;

return [
    'schema' => [
        /**
         * true (Default) - Schema will be stored in a cache after compilation.
         * It won't be changed after entity modification. Use `php app.php cycle` to update schema.
         *
         * false - Schema won't be stored in a cache after compilation.
         * It will be automatically changed after entity modification. (Development mode)
         */
        'cache' => false,

        /**
         * The CycleORM provides the ability to manage default settings for
         * every schema with not defined segments
         */
        'defaults' => [
            SchemaInterface::MAPPER => \Cycle\ORM\Mapper\Mapper::class,
            SchemaInterface::REPOSITORY => \Cycle\ORM\Select\Repository::class,
            SchemaInterface::SCOPE => null,
            SchemaInterface::TYPECAST_HANDLER => [
                \Cycle\ORM\Parser\Typecast::class
            ],
        ],

        'collections' => [
            'default' => 'array',
            'factories' => [
                'array' => new \Cycle\ORM\Collection\ArrayCollectionFactory(),
                // 'doctrine' => new \Cycle\ORM\Collection\DoctrineCollectionFactory(),
                // 'illuminate' => new \Cycle\ORM\Collection\IlluminateCollectionFactory(),
            ],
        ],

        /**
         * Schema generators (Optional)
         * null (default) - Will be used schema generators defined in bootloaders
         */
        'generators' => null,

        // 'generators' => [
        //        \Cycle\Schema\Generator\ResetTables::class,
        //        \Cycle\Annotated\Embeddings::class,
        //        \Cycle\Annotated\Entities::class,
        //        \Cycle\Annotated\TableInheritance::class,
        //        \Cycle\Annotated\MergeColumns::class,
        //        \Cycle\Schema\Generator\GenerateRelations::class,
        //        \Cycle\Schema\Generator\GenerateModifiers::class,
        //        \Cycle\Schema\Generator\ValidateEntities::class,
        //        \Cycle\Schema\Generator\RenderTables::class,
        //        \Cycle\Schema\Generator\RenderRelations::class,
        //        \Cycle\Schema\Generator\RenderModifiers::class,
        //        \Cycle\Annotated\MergeIndexes::class,
        //        \Cycle\Schema\Generator\GenerateTypecast::class,
        // ],
    ],

    /**
     * Custom relation types for entities
     */
    'customRelations' => [
        // \Cycle\ORM\Relation::EMBEDDED => [
        //     \Cycle\ORM\Config\RelationConfig::LOADER => \Cycle\ORM\Select\Loader\EmbeddedLoader::class,
        //     \Cycle\ORM\Config\RelationConfig::RELATION => \Cycle\ORM\Relation\Embedded::class,
        // ]
    ],

    /**
     * Prepare all internal ORM services (mappers, repositories, typecasters...)
     */
    'warmup' => false,
];

If you want to use specific collection type in your relation, you can specify it via attributes

// array
#[HasMany(target: User::class, nullable: true, outerKey:  'userId', collection: 'array')]
private array $friends = [];

// doctrine
#[HasMany(target: User::class, nullable: true, outerKey:  'userId', collection: 'doctrine')]
private \Doctrine\Common\Collections\Collection $friends;

// illuminate
#[HasMany(target: User::class, nullable: true, outerKey:  'userId', collection: 'illuminate')]
private \Illuminate\Support\Collection $friends;

Migrations

You can create config file app/config/migration.php if you want to configure Cycle ORM Migrations:

return [
    /**
     * Directory to store migration files
     */
    'directory' => directory('application').'migrations/',

    /**
     * Table name to store information about migrations status (per database)
     */
    'table' => 'migrations',

    /**
     * When set to true no confirmation will be requested on migration run.
     */
    'safe' => env('SPIRAL_ENV') == 'develop',
];

Integration with cycle/entity-behavior package

The package is available via composer and can be installed using the following command:

composer require cycle/entity-behavior

At first, you need to bind Cycle\ORM\Transaction\CommandGeneratorInterface with \Cycle\ORM\Entity\Behavior\EventDrivenCommandGenerator.

You can do it via spiral bootloader.

<?php

declare(strict_types=1);

namespace App\Bootloader;

use Cycle\ORM\Transaction\CommandGeneratorInterface;
use Cycle\ORM\Entity\Behavior\EventDrivenCommandGenerator;
use Spiral\Boot\Bootloader\Bootloader;

final class EntityBehaviorBootloader extends Bootloader
{
    protected const BINDINGS = [
        CommandGeneratorInterface::class => \Cycle\ORM\Entity\Behavior\EventDrivenCommandGenerator::class,
    ];
}

And then you need to register a new bootloader in your application:

protected const LOAD = [
    \App\Bootloader\EntityBehaviorBootloader::class,
    ...
],

Console commands

Migrations

Command Description
migrate Perform one or all outstanding migrations.
--one Execute only one (first) migration.
migrate:replay Replay (down, up) one or multiple migrations.
--all Replay all migrations.
migrate:rollback Rollback one (default) or multiple migrations.
--all Rollback all executed migrations.
migrate:init Init migrations component (create migrations table).
migrate:status Get list of all available migrations and their statuses.

Database

Command Description
db:list [db] Get list of available databases, their tables and records count.
db database name.
db:table <table> Describe table schema of specific database.
table Table name (required).
--database Source database.

ORM and Schema

Command Description
cycle Update (init) cycle schema from database and annotated classes.
cycle:migrate Generate ORM schema migrations.
--run Automatically run generated migration.
cycle:render Render available CycleORM schemas.
--no-color Display output without colors.
cycle:sync Sync Cycle ORM schema with database without intermediate migration (risk operation).