Laravelで多かれ少なかれ通常のエンタープライズプロジェクトを主導し始めたすべての人は、Laravelが箱から出して提供する標準的なソリューションではもはや十分ではないという事実に直面しました。
そして、私のようにプロジェクトでPostgresを使用する場合は、遅かれ早かれ、さまざまな種類のインデックスと制約、拡張機能、新しいタイプなど、このすばらしいDBMSの特典が必要になります。
今日は、すでにお気づきのように、Postgres、Laravelの移行、すべてをまとめる方法、一般的に、Laraの標準的な移行に欠けているすべてについて説明します。
Laravelの内部構造の複雑さに飛び込みたくない人は、このリンクからLaravelとPostgresの移行機能を拡張するパッケージをダウンロードして、プロジェクトで使用するだけです。
しかし、私はまだスクロールするのではなく、すべてを最後まで読むことをお勧めします。
移行
これは、Laravelでの標準的な移行の様子です。
典型的な移行の例
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDocuments extends Migration
{
private const TABLE = 'documents';
public function up()
{
Schema::create(static::TABLE, function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->softDeletes();
$table->string('number');
$table->date('issued_date')->nullable();
$table->date('expiry_date')->nullable();
$table->string('file');
$table->bigInteger('author_id');
$table->bigInteger('type_id');
$table->foreign('author_id')->references('id')->on('users');
$table->foreign('type_id')->references('id')->on('document_types');
});
}
public function down()
{
Schema::dropIfExists(static::TABLE);
}
}
しかし、ここでは、Postgresの新しいタイプに関するHabréの記事を読みました。たとえば、tsrangeで、このようなものを移行に追加したいと考えていました...
$table->addColumn('tsrange', 'period')->nullable();
, , , Laravel, , :
$table->tsRange('period')->nullable();
, , , :
Laravel
Blueprint
<?php
Blueprint::macro('tsRange', function (string $columnName) {
return $this->addColumn('tsrange', $columnName);
});
PostgresGrammar
<?php
PostgresGrammar::macro('typeTsrange', function () {
rerurn 'tsrange';
});
- , ExtendDatabaseProvider:
<?php
use Illuminate\Support\ServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
public function register()
{
Blueprint::macro('tsRange', function (string $columnName) {
return $this->addColumn('tsrange', $columnName);
});
PostgresGrammar::macro('typeTsrange', function () {
return 'tsrange';
});
}
}
, , ..
, Laravel MacroableTrait, .
( 1)
, , +100500 , . , CI, ...
, " " - :
Doctrine\DBAL\Driver\PDOException: SQLSTATE[08006] [7]
FATAL: sorry, too many clients already
, , .env CI GitLab, , , , , . CI - , , , - , vendor. .
, , , .
- ( ), , , , - , , .
sorry, too many clients already .
, , PostgresGrammar , , .... -, . :
Doctrine\DBAL\DBALException: Unknown database type tsrange requested,
Doctrine\DBAL\Platforms\PostgreSQL100Platform may not support it.
, , ... , . ?
Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType().
You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap().
If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type.
Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes().
If the type name is empty you might have a problem with the cache or forgot some mapping information.
, Doctrine\Dbal , Database Connection , ( , Postgres, getTypesMap Doctrine.
( 2)
, - vendor doctrine\dbal...
- , , Doctrine, , , .
, !
, , ..
..
, .
, , , , - Doctrine.
, , , , , , , , Laravel Doctrine, .
, , , . .
DatabaseProvider, Laravel, Extension- , , , Doctrine.
, , , :
Blueprint - , ,
Builder - Schema
PostgresGrammar - Blueprint- SQL-
Types -
, , Laravel , , , IDE , , .
,
<?php
namespace Umbrellio\Postgres\Extensions;
use Illuminate\Support\Traits\Macroable;
use Umbrellio\Postgres\Extensions\Exceptions\MacroableMissedException;
use Umbrellio\Postgres\Extensions\Exceptions\MixinInvalidException;
abstract class AbstractExtension extends AbstractComponent
{
abstract public static function getMixins(): array;
abstract public static function getName(): string;
public static function getTypes(): array
{
return [];
}
final public static function register(): void
{
collect(static::getMixins())->each(static function ($extension, $mixin) {
if (!is_subclass_of($mixin, AbstractComponent::class)) {
throw new MixinInvalidException(sprintf(
'Mixed class %s is not descendant of %s.',
$mixin,
AbstractComponent::class
));
}
if (!method_exists($extension, 'mixin')) {
throw new MacroableMissedException(sprintf('Class %s doesn’t use Macroable Trait.', $extension));
}
/** @var Macroable $extension */
$extension::mixin(new $mixin());
});
}
}
, , , Laravel , Doctrine.
PostgresConnection
<?php
namespace Umbrellio\Postgres;
use DateTimeInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Events;
use Illuminate\Database\PostgresConnection as BasePostgresConnection;
use Illuminate\Support\Traits\Macroable;
use PDO;
use Umbrellio\Postgres\Extensions\AbstractExtension;
use Umbrellio\Postgres\Extensions\Exceptions\ExtensionInvalidException;
use Umbrellio\Postgres\Schema\Builder;
use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar;
use Umbrellio\Postgres\Schema\Subscribers\SchemaAlterTableChangeColumnSubscriber;
class PostgresConnection extends BasePostgresConnection
{
use Macroable;
private static $extensions = [];
final public static function registerExtension(string $extension): void
{
if (!is_subclass_of($extension, AbstractExtension::class)) {
throw new ExtensionInvalidException(sprintf(
'Class %s must be implemented from %s',
$extension,
AbstractExtension::class
));
}
self::$extensions[$extension::getName()] = $extension;
}
public function getSchemaBuilder()
{
if ($this->schemaGrammar === null) {
$this->useDefaultSchemaGrammar();
}
return new Builder($this);
}
public function useDefaultPostProcessor(): void
{
parent::useDefaultPostProcessor();
$this->registerExtensions();
}
protected function getDefaultSchemaGrammar()
{
return $this->withTablePrefix(new PostgresGrammar());
}
private function registerExtensions(): void
{
collect(self::$extensions)->each(function ($extension) {
/** @var AbstractExtension $extension */
$extension::register();
foreach ($extension::getTypes() as $type => $typeClass) {
$this
->getSchemaBuilder()
->registerCustomDoctrineType($typeClass, $type, $type);
}
});
}
}
:
DatabaseProvider
<?php
namespace Umbrellio\Postgres;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\DatabaseServiceProvider;
use Umbrellio\Postgres\Connectors\ConnectionFactory;
class UmbrellioPostgresProvider extends DatabaseServiceProvider
{
protected function registerConnectionServices(): void
{
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
});
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
}
}
ConnectionFactory
<?php
namespace Umbrellio\Postgres\Connectors;
use Illuminate\Database\Connection;
use Illuminate\Database\Connectors\ConnectionFactory as ConnectionFactoryBase;
use Umbrellio\Postgres\PostgresConnection;
class ConnectionFactory extends ConnectionFactoryBase
{
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
}
if ($driver === 'pgsql') {
return new PostgresConnection($connection, $database, $prefix, $config);
}
return parent::createConnection($driver, $connection, $database, $prefix, $config);
}
}
tsrange . .
TsRangeExtension.php
<?php
namespace App\Extensions\TsRange;
use App\Extensions\TsRange\Schema\Grammars\TsRangeSchemaGrammar;
use App\Extensions\TsRange\Schema\TsRangeBlueprint;
use App\Extensions\TsRange\Types\TsRangeType;
use Umbrellio\Postgres\Extensions\AbstractExtension;
use Umbrellio\Postgres\Schema\Blueprint;
use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar;
class TsRangeExtension extends AbstractExtension
{
public const NAME = TsRangeType::TYPE_NAME;
public static function getMixins(): array
{
return [
TsRangeBlueprint::class => Blueprint::class,
TsRangeSchemaGrammar::class => PostgresGrammar::class,
// ... Laravel
];
}
public static function getName(): string
{
return static::NAME;
}
public static function getTypes(): array
{
return [
static::NAME => TsRangeType::class,
];
}
}
TsRangeBlueprint.php
<?php
namespace App\Extensions\TsRange\Schema;
use Illuminate\Support\Fluent;
use App\Extensions\TsRange\Types\TsRangeType;
use Umbrellio\Postgres\Extensions\Schema\AbstractBlueprint;
class TsRangeBlueprint extends AbstractBlueprint
{
public function tsrange()
{
return function (string $column): Fluent {
return $this->addColumn(TsRangeType::TYPE_NAME, $column);
};
}
}
TsRangeSchemaGrammar.php
<?php
namespace App\Extensions\TsRange\Schema\Grammars;
use App\Extensions\TsRange\Types\TsRangeType;
use Umbrellio\Postgres\Extensions\Schema\Grammar\AbstractGrammar;
class TsRangeSchemaGrammar extends AbstractGrammar
{
protected function typeTsrange()
{
return function (): string {
return TsRangeType::TYPE_NAME;
};
}
}
TsRangeType.php
<?php
namespace App\Extensions\TsRange\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
class TsRangeType extends Type
{
public const TYPE_NAME = 'tsrange';
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return static::TYPE_NAME;
}
public function convertToPHPValue($value, AbstractPlatform $platform): ?array
{
//...
return $value;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
//...
return $value;
}
public function getName(): string
{
return self::TYPE_NAME;
}
}
TsRangeExtension :
<?php
namespace App\TsRange\Providers;
use Illuminate\Support\ServiceProvider;
use App\Extensions\TsRange\TsRangeExtension;
use Umbrellio\Postgres\PostgresConnection;
class TsRangeExtensionProvider extends ServiceProvider
{
public function register(): void
{
PostgresConnection::registerExtension(TsRangeExtension::class);
}
}
Postgres AbstractExtension, , , Laravel Doctrine.
, - PHP , Laravel / Postgres, , .
, , Issues / Pull-, , .
GitHub: laravel-pg-extensions.
.