Memperluas Migrasi Laravel dengan Postgres

Setiap orang yang pernah mulai memimpin proyek Enterprise yang kurang lebih normal di Laravel dihadapkan pada kenyataan bahwa solusi standar yang ditawarkan Laravel di luar kotak tidak lagi cukup.





Dan jika Anda, seperti saya, menggunakan Postgres dalam proyek Anda, cepat atau lambat Anda akan membutuhkan fasilitas DBMS yang luar biasa ini, seperti: berbagai jenis indeks dan batasan, ekstensi, tipe baru, dll.





Hari ini, seperti yang telah Anda perhatikan, kita akan berbicara tentang Postgres, tentang migrasi Laravel, bagaimana menyatukan semuanya, secara umum, tentang segala hal yang tidak kita miliki dalam migrasi standar Lara.



Nah, bagi mereka yang tidak ingin menyelami seluk-beluk struktur internal Laravel, mereka cukup mengunduh paket yang memperluas kemampuan migrasi Laravel dan Postgres dari tautan ini dan menggunakannya dalam proyek mereka.





Tapi saya tetap merekomendasikan untuk tidak menggulir, tetapi membaca semuanya sampai akhir.





Migrasi

Seperti inilah tampilan migrasi standar di Laravel:





Contoh migrasi tipikal
<?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);
    }
}
      
      







Tapi, di sini Anda membaca artikel di Habrรฉ tentang tipe baru di Postgres, misalnya, tsrange dan ingin menambahkan sesuatu seperti ini ke migrasi ...





$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.





.












All Articles