Estendendo migrações do Laravel com Postgres

Todos que já começaram a liderar um projeto Enterprise mais ou menos normal no Laravel, se deparam com o fato de que as soluções padrão que o Laravel oferece fora da caixa não são mais suficientes.





E se você, como eu, usa Postgres em seus projetos, então mais cedo ou mais tarde você vai precisar das vantagens deste maravilhoso DBMS, tais como: vários tipos de índices e restrições, extensões, novos tipos, etc.





Hoje, como você já notou, falaremos sobre Postgres, sobre as migrações do Laravel, como fazer tudo junto, em geral, sobre tudo que falta nas migrações padrão do Lara.



Bem, para aqueles que não querem mergulhar nos meandros da estrutura interna do Laravel, eles podem simplesmente baixar um pacote que estende as capacidades de migração do Laravel e Postgres deste link e usá-lo em seus projetos.





Mas eu ainda recomendo não rolar, mas ler tudo até o fim.





Migrações

Esta é a aparência da migração padrão no Laravel:





Exemplo típico de migração
<?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);
    }
}
      
      







Mas, aqui você leu um artigo sobre Habré sobre novos tipos no Postgres, por exemplo, tsrange e queria adicionar algo assim à migração ...





$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