Skip to content

Instantly share code, notes, and snippets.

@butschster
Last active December 3, 2021 16:38
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save butschster/337051259807aa8433ebdd8cca8e0991 to your computer and use it in GitHub Desktop.
Save butschster/337051259807aa8433ebdd8cca8e0991 to your computer and use it in GitHub Desktop.
CycleORM news digest

Hello everybody!

We know, that you are looking forward to a CycleORM updates and this fact motivates us working faster and more efficiently.

Here they are!

1. Added support for Entity typecast handlers.

CycleORM is used to allow otping a column type among the primitive types such as int, string, float, datetime and callable, but now you can create typecast handler class, that implements Cycle\ORM\Parser\TypecastInterface and define it (or array of handlers) as an Entity columns typecast handler.

Example

use Cycle\ORM\Parser\TypecastInterface;

class UuidTypecast implements TypecastInterface
{
    private array $rules = [];

    public function setRules(array $rules): array
    {
        foreach ($rules as $key => $rule) {
            if ($rule === 'uuid') {
                unset($rules[$key]);
                $this->rules[$key] = $rule;
            }
        }

        return $rules;
    }

    public function cast(array $values): array
    {
        foreach ($this->rules as $key => $rule) {
            if (!isset($values[$key])) {
                continue;
            }

            $values[$key] = Uuid::fromString($values[$key]);
        }

        return $values;
    }
}
use Cycle\ORM\Parser\Typecast as PrimitiveTypecast;

#[Entity(
    typecast: [
        UuidTypecast::class,
        PrimitiveTypecast::class
    ]
)]
class User {
    #[Column]
    private UuidInterface $id;
}

By default all etities use \Cycle\ORM\Parser\Typecast::class typecast handler, but you can change default behavior globally in the next release of spiral/app or you can do it right now in your app via \Cycle\Schema\Compiler.

Example

use Cycle\Schema\Compiler;
use Cycle\ORM\SchemaInterface;
use Cycle\ORM\Parser\Typecast as PrimitiveTypecast;

(new Compiler)->compile($r, [/* Generators[] */], [
    SchemaInterface::TYPECAST_HANDLER = [
        UuidTypecast::class,
        PrimitiveTypecast::class
    ]
])

2. We improved cycle/annotated package. It's now more flexible and suppors all the new features.

2.1 Custom collections for HasMany, ManyToMany and, MorphedHasMany relations

Example

#[Entity]
class User
{
    // ...

    #[ManyToMany(
        target: Tag::class, 
        collection: \Doctrine\Common\Collections\Collection::class, 
        // or
        collection: 'doctrine'
    )]
    protected \Doctrine\Common\Collections\Collection $tags;
    

    #[HasMany(
        target: Post::class, 
        collection: \Illuminate\Support\Collection::class,
        // or
        collection: 'illuminate'
    )]
    protected \Illuminate\Support\Collection $tags;
    

    #[HasMany(
        target: Post::class, 
        collection: 'array'
    )]
    protected array $comments;
}

2.2 STI/JTI

CycleORM has support Single Table Inheritance and Joined Table Inheritance, but now you can configure JTI/STI via attributes (annotations) also.

STI

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Inheritance\DiscriminatorColumn;
use Cycle\Annotated\Annotation\Inheritance\SingleTable as InheritanceSingleTable;

#[Entity]
#[DiscriminatorColumn(name: 'type')] // Discriminator column (required)
class Person
{
    #[Column(type: 'primary', primary: true)]
    protected int $id;

    #[Column(type: 'string')]
    protected string $name;
}

#[Entity]
#[InheritanceSingleTable]
class Employee extends Person
{
    #[Column(type: 'int')]
    protected int $salary;
}

#[Entity]
#[InheritanceSingleTable(value: 'foo_customer')]
class Customer extends Person
{
    #[Column(type: 'json')]
    protected array $preferences;
}

JTI

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Inheritance\JoinedTable as InheritanceJoinedTable;

#[Entity]
class Person
{
    #[Column(primary: true)]
    protected int $id;
    
    #[Column()]
    protected int $fooId;

    #[Column(type: 'string')]
    protected string $name;
}

#[Entity]
#[InheritanceJoinedTable(outerKey: 'fooId')]
class Employee extends Person
{
    #[Column(type: 'int')]
    protected int $salary;
}

#[Entity]
#[InheritanceJoinedTable(outerKey: 'id')]
class Customer extends Person
{
    #[Column(type: 'json')]
    protected array $preferences;
}

You can combine STI and JTI

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Inheritance\DiscriminatorColumn;
use Cycle\Annotated\Annotation\Inheritance\SingleTable as InheritanceSingleTable;
use Cycle\Annotated\Annotation\Inheritance\JoinedTable as InheritanceJoinedTable;

#[Entity]
#[DiscriminatorColumn(name: 'type')]
class Person
{
    #[Column(type: 'primary', primary: true)]
    protected int $id;

    #[Column(type: 'string')]
    protected string $name;
}

#[Entity]
#[InheritanceSingleTable]
class Employee extends Person
{
    #[Column(type: 'int')]
    protected int $salary;
}

#[Entity]
#[InheritanceSingleTable(value: 'foo_customer')]
class Customer extends Person
{
    #[Column(type: 'json')]
    protected array $preferences;
}

#[Entity]
#[InheritanceJoinedTable(outerKey: 'foo_id')]
class Executive extends Employee
{
    #[Column(type: 'int')]
    protected int $bonus;
}

Custom TypecastHandler

As I mentioned above, you can configure Typecast handlers via attributes (annotatios).

use Cycle\ORM\Parser\Typecast as PrimitiveTypecast;

#[Entity(
    typecast: [
        UuidTypecast::class,
        PrimitiveTypecast::class
    ]
)]
class User {
    #[Column]
    private UuidInterface $id;
}

3. A new CycleORM package - cycle/schema-renderer

The package allows to render CycleORM schema in a terminal or to prepare schemas to save into a php file. The package will be a part of spiral/app in the next release and there can be used a console command php app.php cycle:render for rendering entity schemas in a terminal.

Example

use Cycle\Schema\Compiler;
use ycle\Schema\Renderer\OutputSchemaRenderer;
use ycle\Schema\Renderer\SchemaToArrayConverter;

// Create schema from generators
$schema = (new Compiler)->compile(...);

// Or getting schema form object \Cycle\ORM\SchemaInterface
$schema = (new SchemaToArrayConverter())->convert($schema);

$renderer = new OutputSchemaRenderer();

// Render to a terminal
$output->writeln(
    $renderer->render($schemaArray)
);

Output example

4. Another one new package for CycleORM - cycle/entity-macros

The package is in progress yet, but you can try it right now. The package allows you to use macros for changing default entity columns behavior.

Macros replace https://cycle-orm.dev/docs/advanced-timestamp

Example

use Cycle\ORM\Entity\Macros\Timestamped;

#[Entity]
#[Timestamped\CreatedAtMacro(field: 'createdAt')]
#[Timestamped\UpdatedAtMacro(field: 'updatedAt')]
#[Timestamped\DeletedAtMacro(field: 'deletedAt')]
class Post {

    #[Column(type: 'datetime')]
    private \DateTimeImmutable $createdAt;

    #[Column(type: 'datetime')]
    private \DateTimeImmutable $updatedAt;

    #[Column(type: 'datetime')]
    private \DateTimeImmutable $deletedAt;
}

In addition to use macros including in this package, there is an ability easily to create your own macros!

!!! Some features can be changed in the final release.

5. Database drivers via DTO

Enough! Now there are no more arrays and attempts to understand what settings a driver supports.

Before

use Cycle\Database\Driver;

'drivers'   => [
    'mysql'     => [
        'driver'     => Driver\MySQL\MySQLDriver::class,
        'options'    => [
            'connection' => 'mysql:host=127.0.0.1;dbname=' . env('DB_NAME'),
            'username'   => env('DB_USERNAME'),
            'password'   => env('DB_PASSWORD'),
        ]
    ],
    'postgres'  => [
        'driver'     => Driver\Postgres\PostgresDriver::class,
        'options'    => [
            'connection' => 'pgsql:host=127.0.0.1;dbname=' . env('DB_NAME'),
            'username'   => env('DB_USERNAME'),
            'password'   => env('DB_PASSWORD'),
        ]
    ],
    'runtime'   => [
        'driver'     => Driver\SQLite\SQLiteDriver::class,
        'options'    => [
            'connection' => 'sqlite:' . directory('runtime') . 'runtime.db',
            'username'   => 'sqlite',
            'password'   => '',
        ]
    ],
    'sqlServer' => [
        'driver'     => Driver\SQLServer\SQLServerDriver::class,
        'options'    => [
            'connection' => 'sqlsrv:Server=MY-PC;Database=' . env('DB_NAME'),
            'username'   => env('DB_USERNAME'),
            'password'   => env('DB_PASSWORD'),
        ]
    ]
]

After

use Cycle\Database\Config;

'drivers' => [
    'sqlite_memory' => new Config\SQLiteDriverConfig(
        connection: new SQLite\MemoryConnectionConfig(),
        queryCache: true,
    ),

    'sqlite_file' => new Config\SQLiteDriverConfig(
        connection: new Config\SQLite\FileConnectionConfig(
            database: env('DB_DATABASE', __DIR__.'./../../runtime/database.sqlite')
        ),
        queryCache: true,
    ),

    'mysql' => new Config\MySQLDriverConfig(
        connection: new Config\MySQL\TcpConnectionConfig(
            database: env('DB_DATABASE', 'forge'),
            host: env('DB_HOST', '127.0.0.1'),
            port: (int)env('DB_PORT', 3306),
            user: env('DB_USERNAME', 'forge'),
            password: env('DB_PASSWORD', ''),
        ),
        queryCache: true
    ),
    'postgres' => new \Config\PostgresDriverConfig(
        connection: new Config\Postgres\TcpConnectionConfig(
            database: env('DB_DATABASE', 'forge'),
            host: env('DB_HOST', '127.0.0.1'),
            port: (int)env('DB_PORT', 5432),
            user: env('DB_USERNAME', 'forge'),
            password: env('DB_PASSWORD', ''),
        ),
        schema: 'public',
        queryCache: true,
    ),
],

We will provide more information about driver DTOs after CycleORM v2.0 release.

6. Demo app spiral/app with CycleORM v2 support

We prepared a demo app of spiral/app where you can try CycleORM v2 with all new features


CycleORM is one of the most high performance by speed ORM, and soon by functionality!

Всем привет!

Знаем, что вы долго ждали очередной порции обновлений и именно этот факт мотивирует нас работать быстрее и эффективнее.

Итак, ловите обновления!

1. Добавлена поддержка обработчиков для типизации полей Entity.

Раньше CycleORM позволяла выбрать только среди стандартных типов int, string, float, datetime и callable, но теперь вы можете создать класс, который реализует интерфейс Cycle\ORM\Parser\TypecastInterface и указать его или несколько таких классов в качестве обработчиков полей сущности.

Пример:

use Cycle\ORM\Parser\TypecastInterface;

class UuidTypecast implements TypecastInterface
{
    private array $rules = [];

    public function setRules(array $rules): array
    {
        foreach ($rules as $key => $rule) {
            if ($rule === 'uuid') {
                unset($rules[$key]);
                $this->rules[$key] = $rule;
            }
        }

        return $rules;
    }

    public function cast(array $values): array
    {
        foreach ($this->rules as $key => $rule) {
            if (!isset($values[$key])) {
                continue;
            }

            $values[$key] = Uuid::fromString($values[$key]);
        }

        return $values;
    }
}
use Cycle\ORM\Parser\Typecast as PrimitiveTypecast;

#[Entity(
    typecast: [
        UuidTypecast::class,
        PrimitiveTypecast::class
    ]
)]
class User {
    #[Column]
    private UuidInterface $id;
}

По умолчанию все сущности используют \Cycle\ORM\Parser\Typecast::class, но это поведение может быть изменено глобально в следующей версии spiral\app либо через \Cycle\Schema\Compiler.

Пример:

use Cycle\Schema\Compiler;
use Cycle\ORM\SchemaInterface;
use Cycle\ORM\Parser\Typecast as PrimitiveTypecast;

(new Compiler)->compile($r, [/* Generators[] */], [
    SchemaInterface::TYPECAST_HANDLER = [
        UuidTypecast::class,
        PrimitiveTypecast::class
    ]
])

2. Прокачали пакет аннотаций/атрибутов. Теперь он стал более гибким

2.1 Пользовательские коллекции в HasMany, ManyToMany, MorphedHasMany связях

Пример

#[Entity]
class User
{
    // ...

    #[ManyToMany(
        target: Tag::class, 
        collection: \Doctrine\Common\Collections\Collection::class, 
        // or
        collection: 'doctrine'
    )]
    protected \Doctrine\Common\Collections\Collection $tags;
    

    #[HasMany(
        target: Post::class, 
        collection: \Illuminate\Support\Collection::class,
        // or
        collection: 'illuminate'
    )]
    protected \Illuminate\Support\Collection $tags;
    

    #[HasMany(
        target: Post::class, 
        collection: 'array'
    )]
    protected array $comments;
}

2.2 Наследование STI/JTI

CycleORM уже умеет работать с Single Table Inheritance и Joined Table Inheritance, а теперь и через атрибуты (аннотации тоже).

STI

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Inheritance\DiscriminatorColumn;
use Cycle\Annotated\Annotation\Inheritance\SingleTable as InheritanceSingleTable;

#[Entity]
#[DiscriminatorColumn(name: 'type')] // Discriminator column (required)
class Person
{
    #[Column(type: 'primary', primary: true)]
    protected int $id;

    #[Column(type: 'string')]
    protected string $name;
}

#[Entity]
#[InheritanceSingleTable]
class Employee extends Person
{
    #[Column(type: 'int')]
    protected int $salary;
}

#[Entity]
#[InheritanceSingleTable(value: 'foo_customer')]
class Customer extends Person
{
    #[Column(type: 'json')]
    protected array $preferences;
}

JTI

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Inheritance\JoinedTable as InheritanceJoinedTable;

#[Entity]
class Person
{
    #[Column(primary: true)]
    protected int $id;
    
    #[Column()]
    protected int $fooId;

    #[Column(type: 'string')]
    protected string $name;
}

#[Entity]
#[InheritanceJoinedTable(outerKey: 'fooId')]
class Employee extends Person
{
    #[Column(type: 'int')]
    protected int $salary;
}

#[Entity]
#[InheritanceJoinedTable(outerKey: 'id')]
class Customer extends Person
{
    #[Column(type: 'json')]
    protected array $preferences;
}

Их можно и кобинировать

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Inheritance\DiscriminatorColumn;
use Cycle\Annotated\Annotation\Inheritance\SingleTable as InheritanceSingleTable;
use Cycle\Annotated\Annotation\Inheritance\JoinedTable as InheritanceJoinedTable;

#[Entity]
#[DiscriminatorColumn(name: 'type')]
class Person
{
    #[Column(type: 'primary', primary: true)]
    protected int $id;

    #[Column(type: 'string')]
    protected string $name;
}

#[Entity]
#[InheritanceSingleTable]
class Employee extends Person
{
    #[Column(type: 'int')]
    protected int $salary;
}

#[Entity]
#[InheritanceSingleTable(value: 'foo_customer')]
class Customer extends Person
{
    #[Column(type: 'json')]
    protected array $preferences;
}

#[Entity]
#[InheritanceJoinedTable(outerKey: 'foo_id')]
class Executive extends Employee
{
    #[Column(type: 'int')]
    protected int $bonus;
}

Пользовательский TypecastHandler

Как упоминалось выше, через атрибуты (аннотации) для сущностей можно указаывать обработчики полей.

use Cycle\ORM\Parser\Typecast as PrimitiveTypecast;

#[Entity(
    typecast: [
        UuidTypecast::class,
        PrimitiveTypecast::class
    ]
)]
class User {
    #[Column]
    private UuidInterface $id;
}

3. Новый пакет для CycleORM - cycle/schema-renderer

Данный пакет будет полезен для вывода схемы в консоль или сохранения в файл. Данный пакет будет включен в spiral/app в виде консольной команды cycle:render для дебага схем сущностей приложения.

Пример

use Cycle\Schema\Compiler;
use ycle\Schema\Renderer\OutputSchemaRenderer;
use ycle\Schema\Renderer\SchemaToArrayConverter;

// Create schema from generators
$schema = (new Compiler)->compile(...);

// Or getting schema form object \Cycle\ORM\SchemaInterface
$schema = (new SchemaToArrayConverter())->convert($schema);

$renderer = new OutputSchemaRenderer();

// Render to a terminal
$output->writeln(
    $renderer->render($schemaArray)
);

Пример вывода

4. Новый пакет для CycleORM - cycle/entity-macros

Добавили пакет находится сейчас в горячей разработке и позволит изпользовать "макросы" для изменения поведения полей сущности.

Решает задачу https://cycle-orm.dev/docs/advanced-timestamp

Пример

use Cycle\ORM\Entity\Macros\Timestamped;

#[Entity]
#[Timestamped\CreatedAtMacro(field: 'createdAt')]
#[Timestamped\UpdatedAtMacro(field: 'updatedAt')]
#[Timestamped\DeletedAtMacro(field: 'deletedAt')]
class Post {

    #[Column(type: 'datetime')]
    private \DateTimeImmutable $createdAt;

    #[Column(type: 'datetime')]
    private \DateTimeImmutable $updatedAt;

    #[Column(type: 'datetime')]
    private \DateTimeImmutable $deletedAt;
}

Помимо возможности использования существующих макросов вы сможете создавать свои собственные макросы!

!!! Некоторые вещи в окончательном релизе все еще могут быть изменены

5. Подключение к БД конфигурируется с помощью DTO

Все! Теперь больше никаких массивов и попыток понять, а какие настройки поддерживает тот или иной драйвер.

Было

use Cycle\Database\Driver;

'drivers'   => [
    'mysql'     => [
        'driver'     => Driver\MySQL\MySQLDriver::class,
        'options'    => [
            'connection' => 'mysql:host=127.0.0.1;dbname=' . env('DB_NAME'),
            'username'   => env('DB_USERNAME'),
            'password'   => env('DB_PASSWORD'),
        ]
    ],
    'postgres'  => [
        'driver'     => Driver\Postgres\PostgresDriver::class,
        'options'    => [
            'connection' => 'pgsql:host=127.0.0.1;dbname=' . env('DB_NAME'),
            'username'   => env('DB_USERNAME'),
            'password'   => env('DB_PASSWORD'),
        ]
    ],
    'runtime'   => [
        'driver'     => Driver\SQLite\SQLiteDriver::class,
        'options'    => [
            'connection' => 'sqlite:' . directory('runtime') . 'runtime.db',
            'username'   => 'sqlite',
            'password'   => '',
        ]
    ],
    'sqlServer' => [
        'driver'     => Driver\SQLServer\SQLServerDriver::class,
        'options'    => [
            'connection' => 'sqlsrv:Server=MY-PC;Database=' . env('DB_NAME'),
            'username'   => env('DB_USERNAME'),
            'password'   => env('DB_PASSWORD'),
        ]
    ]
]

Стало

use Cycle\Database\Config;

'drivers' => [
    'sqlite_memory' => new Config\SQLiteDriverConfig(
        connection: new SQLite\MemoryConnectionConfig(),
        queryCache: true,
    ),

    'sqlite_file' => new Config\SQLiteDriverConfig(
        connection: new Config\SQLite\FileConnectionConfig(
            database: env('DB_DATABASE', __DIR__.'./../../runtime/database.sqlite')
        ),
        queryCache: true,
    ),

    'mysql' => new Config\MySQLDriverConfig(
        connection: new Config\MySQL\TcpConnectionConfig(
            database: env('DB_DATABASE', 'forge'),
            host: env('DB_HOST', '127.0.0.1'),
            port: (int)env('DB_PORT', 3306),
            user: env('DB_USERNAME', 'forge'),
            password: env('DB_PASSWORD', ''),
        ),
        queryCache: true
    ),
    'postgres' => new \Config\PostgresDriverConfig(
        connection: new Config\Postgres\TcpConnectionConfig(
            database: env('DB_DATABASE', 'forge'),
            host: env('DB_HOST', '127.0.0.1'),
            port: (int)env('DB_PORT', 5432),
            user: env('DB_USERNAME', 'forge'),
            password: env('DB_PASSWORD', ''),
        ),
        schema: 'public',
        queryCache: true,
    ),
],

Подробную информацию по драйверам мы предоставим в момент релиза CycleORM v2.0

6. Тестовая сборка spiral/app с CycleORM v2

Для всех желающих попробовать beta версию со всеми последними нововведениями CycleORM v2 мы подготовили тестовую сборку spiral/app


CycleORM один из самых производительных и оптимизированных по скорости ORM, и совсем скоро не уступающий по возможностям!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment