wakebit / cycle-bridge

循环ORM桥接器

v2.1.0 2023-04-24 05:37 UTC

This package is auto-updated.

Last update: 2024-09-24 08:57:59 UTC


README

此软件包提供了一套Symfony命令和用于集成任何框架的Cycle ORM的类。我们支持ORM的两种版本集成。如果您想将其集成到Laravel中,我们已经有一个单独的软件包,它使用此桥接器。

要求

  • PHP >= 8.0
  • Cycle ORM 1或2(分别对应于v1.xv2.x分支)

安装

  1. 使用composer安装此软件包
composer require wakebit/cycle-bridge

PHP-DI使用的示例

  1. 声明配置config/config.php并填写数据库凭证
<?php

declare(strict_types=1);

use function DI\env;

return [
    // Environment variables
    'debug'         => env('APP_DEBUG', 'true'),

    // Database
    'db.connection' => env('DB_CONNECTION', 'mysql'),
    'db.host'       => env('DB_HOST', 'localhost'),
    'db.database'   => env('DB_DATABASE', ''),
    'db.username'   => env('DB_USERNAME', ''),
    'db.password'   => env('DB_PASSWORD', ''),
];
  1. 声明ORM配置config/cycle.php。不要忘记设置正确的实体路径和迁移路径
<?php

use Cycle\Database\Config\DatabaseConfig;
use Cycle\Migrations\Config\MigrationConfig;
use Psr\Container\ContainerInterface;
use Spiral\Tokenizer\Config\TokenizerConfig;
use Wakebit\CycleBridge\Schema\Config\SchemaConfig;
use function DI\create;

/**
 * @psalm-suppress UndefinedClass
 * @psalm-suppress MixedArgument
 */
return [
    'database' => static function (ContainerInterface $container): DatabaseConfig {
        return new DatabaseConfig([
            'default' => 'default',

            'databases' => [
                'default' => [
                    'connection' => $container->get('config')['db.connection'],
                ],
            ],

            'connections' => [
                'sqlite' => new \Cycle\Database\Config\SQLiteDriverConfig(
                    connection: new \Cycle\Database\Config\SQLite\MemoryConnectionConfig(),
                    queryCache: true,
                ),

                'mysql' => new \Cycle\Database\Config\MySQLDriverConfig(
                    connection: new \Cycle\Database\Config\MySQL\TcpConnectionConfig(
                        database: $container->get('config')['db.database'],
                        host: $container->get('config')['db.host'],
                        port: 3306,
                        user: $container->get('config')['db.username'],
                        password: $container->get('config')['db.password'],
                    ),
                    queryCache: true,
                ),

                'postgres' => new \Cycle\Database\Config\PostgresDriverConfig(
                    connection: new \Cycle\Database\Config\Postgres\TcpConnectionConfig(
                        database: $container->get('config')['db.database'],
                        host: $container->get('config')['db.host'],
                        port: 5432,
                        user: $container->get('config')['db.username'],
                        password: $container->get('config')['db.password'],
                    ),
                    schema: 'public',
                    queryCache: true,
                ),

                'sqlServer' => new \Cycle\Database\Config\SQLServerDriverConfig(
                    connection: new \Cycle\Database\Config\SQLServer\TcpConnectionConfig(
                        database: $container->get('config')['db.database'],
                        host: $container->get('config')['db.host'],
                        port: 1433,
                        user: $container->get('config')['db.username'],
                        password: $container->get('config')['db.password'],
                    ),
                    queryCache: true,
                ),
            ],
        ]);
    },

    'orm' => [
        'schema' => static function (): SchemaConfig {
            return new SchemaConfig();
        },

        'tokenizer' => static function (): TokenizerConfig {
            return new TokenizerConfig([
                'directories' => [
                    __DIR__ . '/../src/Entity',
                ],

                'exclude' => [
                ],
            ]);
        },
    ],

    'migrations' => static function (ContainerInterface $container): MigrationConfig {
        return new MigrationConfig([
            'directory' => __DIR__ . '/../resources/migrations',
            'table'     => 'migrations',
            'safe'      => filter_var($container->get('config')['debug'], FILTER_VALIDATE_BOOLEAN),
        ]);
    },
];
  1. config/container.php中声明PHP-DI容器的依赖项
<?php

declare(strict_types=1);

use Cycle\Database\Config\DatabaseConfig;
use Cycle\Database\DatabaseInterface;
use Cycle\Database\DatabaseManager;
use Cycle\Database\DatabaseProviderInterface;
use Cycle\Migrations\Config\MigrationConfig;
use Cycle\Migrations\FileRepository;
use Cycle\Migrations\RepositoryInterface;
use Cycle\ORM\EntityManager;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\Factory;
use Cycle\ORM\FactoryInterface;
use Cycle\ORM\ORM;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\SchemaInterface;
use Psr\Container\ContainerInterface;
use Spiral\Tokenizer\ClassesInterface;
use Spiral\Tokenizer\ClassLocator;
use Spiral\Tokenizer\Config\TokenizerConfig;
use Spiral\Tokenizer\Tokenizer;
use Wakebit\CycleBridge\Contracts\Schema\CacheManagerInterface;
use Wakebit\CycleBridge\Contracts\Schema\CompilerInterface;
use Wakebit\CycleBridge\Contracts\Schema\GeneratorQueueInterface;
use Wakebit\CycleBridge\Schema\CacheManager;
use Wakebit\CycleBridge\Schema\Compiler;
use Wakebit\CycleBridge\Schema\Config\SchemaConfig;
use Wakebit\CycleBridge\Schema\GeneratorQueue;
use Wakebit\CycleBridge\Schema\SchemaFactory;
use function DI\autowire;
use function DI\factory;
use function DI\get;

return [
    'config'                          => require 'config.php',
    'cycle'                           => require 'cycle.php',

    DatabaseConfig::class             => static function (ContainerInterface $container): DatabaseConfig {
        return $container->get('cycle')['database'];
    },

    SchemaConfig::class               => static function (ContainerInterface $container): SchemaConfig {
        return $container->get('cycle')['orm']['schema'];
    },

    TokenizerConfig::class            => static function (ContainerInterface $container): TokenizerConfig {
        return $container->get('cycle')['orm']['tokenizer'];
    },

    MigrationConfig::class            => static function (ContainerInterface $container): MigrationConfig {
        return $container->get('cycle')['migrations'];
    },

    DatabaseProviderInterface::class => autowire(DatabaseManager::class),
    DatabaseInterface::class         => static function (ContainerInterface $container): DatabaseInterface {
        return $container->get(DatabaseProviderInterface::class)->database();
    },
    DatabaseManager::class           => get(DatabaseProviderInterface::class),

    ClassLocator::class             => get(ClassesInterface::class),
    ClassesInterface::class         => static function (ContainerInterface $container): ClassesInterface {
        return $container->get(Tokenizer::class)->classLocator();
    },

    FactoryInterface::class         => autowire(Factory::class),
    CacheManagerInterface::class    => static function (): CacheManagerInterface {
        // Here you need to pass PSR-16 compatible cache pool. See example with cache file below.
        // Packages: league/flysystem, cache/filesystem-adapter
        $filesystemAdapter = new \League\Flysystem\Adapter\Local(__DIR__ . '/../var/cache');
        $filesystem = new \League\Flysystem\Filesystem($filesystemAdapter);
        $pool = new \Cache\Adapter\Filesystem\FilesystemCachePool($filesystem);

        return new CacheManager($pool);
    },

    GeneratorQueueInterface::class  => autowire(GeneratorQueue::class),
    CompilerInterface::class        => autowire(Compiler::class),
    SchemaInterface::class          => factory([SchemaFactory::class, 'create']),
    ORMInterface::class => static function (ContainerInterface $container): ORMInterface {
        return new ORM($container->get(FactoryInterface::class), $container->get(SchemaInterface::class));
    },
    EntityManagerInterface::class   => autowire(EntityManager::class),
    RepositoryInterface::class      => autowire(FileRepository::class),
];
  1. 现在,您需要将上一步创建的依赖项数组加载到PHP-DI中。在您可以使用依赖项之后,编写您的代码。

让我们看一个快速示例。定义实体

<?php

declare(strict_types=1);

namespace App\Entity;

use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;

#[Entity]
class User
{
    #[Column(type: 'primary')]
    private int $id;

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

    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }
}

您可以从容器中获取DBAL、ORM和EntityManager。快速使用示例

<?php

declare(strict_types=1);

namespace App;

use Cycle\Database\DatabaseProviderInterface;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;

final class SomeClass
{
    public function __construct(
        private DatabaseProviderInterface $dbal,
        private EntityManagerInterface $em,
        private ORMInterface $orm,
    ) {
    }
    
    public function __invoke()
    {
        // DBAL
        $tables = $this->dbal->database()->getTables();
        $tableNames = array_map(fn (\Cycle\Database\TableInterface $table): string => $table->getName(), $tables);
        dump($tableNames);
        
        // Create, modify, delete entities using Transaction
        $user = new \App\Entity\User();
        $user->setName("Hello World");
        $this->em->persist($user);
        $this->em->run();
        dump($user);
        
        // ORM
        $repository = $this->orm->getRepository(\App\Entity\User::class);
        $users = $repository->findAll();
        dump($users);
        
        $user = $repository->findByPK(1);
        dump($user);
    }
}

更多内容请参考官方Cycle ORM文档

控制台命令

操作ORM模式

数据库迁移

数据库命令

编写功能测试

如果您使用的是内存数据库(SQLite),您可以直接在测试的setUp方法中运行迁移,调用控制台命令cycle:migrate。对于其他数据库,请按照此说明进行操作,并在tearDown方法中删除所有表。

高级

手动定义ORM模式

如果您想使用手动定义的ORM模式,您可以在cycle.phporm.schema.map配置键中定义它

use Wakebit\CycleBridge\Schema\Config\SchemaConfig;

return [
    // ...
    'orm' => [
        'schema' => static function (): SchemaConfig {
            return new SchemaConfig([
                'map' => require __DIR__ . '/../orm_schema.php',
            ]);
        },
    ]
    // ...
]

手动定义的模式应以数组形式表示。它将被传递到\Cycle\ORM\Schema构造函数中。更多内容请参考这里

自定义模式编译管道

您可以在cycle.phporm.schema.generators配置键中重新定义ORM模式编译生成器

use Wakebit\CycleBridge\Schema\Config\SchemaConfig;

return [
    // ...
    'orm' => [
        'schema' => static function (): SchemaConfig {
            return new SchemaConfig([
                'generators' => [
                    'index' => [],
                    'render' => [
                        \Cycle\Schema\Generator\ResetTables::class,         // re-declared table schemas (remove columns)
                        \Cycle\Schema\Generator\GenerateRelations::class,   // generate entity relations
                        \Cycle\Schema\Generator\ValidateEntities::class,    // make sure all entity schemas are correct
                        \Cycle\Schema\Generator\RenderTables::class,        // declare table schemas
                        \Cycle\Schema\Generator\RenderRelations::class,     // declare relation keys and indexes
                    ],
                    'postprocess' => [
                        \Cycle\Schema\Generator\GenerateTypecast::class,    // typecast non string columns
                    ],
                ]
            ]);
        },
    ]
    // ...
]

类将通过DI容器解析。默认管道您可以在这里查看。

默认集合工厂

您可以在容器定义中指定预定义的或您自己的集合工厂

FactoryInterface::class => static function (ContainerInterface $container): FactoryInterface {
    return new \Cycle\ORM\Factory(
        dbal: $container->get(DatabaseProviderInterface::class),
        defaultCollectionFactory: new \Cycle\ORM\Collection\DoctrineCollectionFactory(),
    );
},

致谢