Code style for Laravel migrations

Hello everyone.





For the first five years of my career as a programmer, I worked on an in-house project, for the next seven years I worked in various startups, with a maximum of five developers.





Now I have been working for a couple of months on a project with 20+ developers, work is simultaneously carried out in about 30 branches, there are five environments for code development (draft, dev, testing, hotfix, prod), each environment has its own database (before rolling out the kamit to stand / environment, a test rollout takes place using a separate database, that is, for five environments we have 10 separate databases).





It's not new to me to develop in multiple branches, I've always done it. The discovery for me was that the version of the code and the version of the database schema are not synchronized in any way. In a small project, it is not a problem to drop the entire scheme and roll it in its entirety, it takes a few minutes; in this project, rolling a scheme from scratch with seeding takes from an hour.





There is a big problem with how to synchronize the code version and the database schema version.





Below I will tell you about the rules that I have accepted for myself and I will be glad if you share your techniques and techniques that help you cope with this disaster.





Disclaimer

The code presented below is an obfuscated combat code, I have not debugged it, it may need to be modified with a file. I only share ideas with you.





Description of the problem

, , , - -. .





, - , , , , , ? , ?





, , . , , , . , .





.





: " "

, . , migrations, , , .





, .





:





#   
php artisan migrate --path="services/best-team-servise/database/migrations/2021_02_04_240000_alter_data_model_table_add_unique_index.php" --pretend
#  --pretend    SQL      ,  

#    
php artisan migrate:rollback --step=1
#    ,   
      
      



,





php artisan ide-helper:models "Project\Models\DataModel"
      
      



:





php artisan db:seed --class=DataModelSeeder
      
      



? up() down() , , .





, , , .





Builder :





        $conn = (new DataModel())->connection;
        $builder = Schema::connection($conn);
      
      



( ):





        $isExists = $builder->hasColumn(
            'data_model',
            'deleted_at'
        );
      
      



, :





        if (!$isExists) {
            $builder->table(
                'data_model',
                function (Blueprint $table) {
                    $table->softDeletesTz();
                }
            );
        }
      
      



- , - , , , :





        $alias = (new DataModel())->connection;
        $builder = Schema
            ::connection($alias)
            ->getConnection()
            ->getDoctrineSchemaManager();

$existingIndexes = $builder->listTableIndexes('data_model');
      
      



Laravel , :





Blueprint::unique('index_name');
      
      



:





Blueprint::dropUnique('index_name');
      
      



Laravel , , , SQL, Laravel ? , !





SQL, :





DROP TRIGGER IF EXISTS trigger_name
    ON public.data_model;
CREATE TRIGGER trigger_name
    BEFORE INSERT
    ON public.data_model
    FOR EACH ROW
    EXECUTE PROCEDURE public.function_name();
      
      



, :





DROP TRIGGER IF EXISTS trigger_name
    ON public.data_model;
      
      



: " "

, 1000+ . 1000 , .





, 50+ , "" .









, create, alter, , drop.





.





alter_data_model_add_property_column
alter_data_model_alter_property_column_to_text
alter_data_model_alter_property_column_set_default_value
alter_data_model_create_index_on_code_type_columns
alter_data_model_create_unique_index_on_code_column
      
      



, , MVP.





:





#     
php artisan make:migration create_profile_table --create=profile

#     
php artisan make:migration add_confirmed_to_profile --table=profile
      
      



database/migrations , .





: , nullable()

, NOT NULL, , , , .





, .





nullable(), , .





, , - :





            $columns = Schema
            ::connection((new DataModel())->connection)
            ->getConnection()
            ->getDoctrineSchemaManager()
            ->listTableColumns($(new DataModel())->getTable());

            $data = [];
            foreach ($columns as $column) {
                $name = $column->getName();
/* @var array[] $record    */
                $exists = key_exists($name, $record);
                if ($exists) {
                    $data[$name] = $record[$name];
                }
            }

            $isSuccess = DataModel
                ::withTrashed()
                ->updateOrCreate(
                    ['uniqe_index_column' => $data['uniqe_index_column'],],
                    $data
                )->exists;
      
      



: , null

, , , , , .





Or you can use some default value in the code, but I don't like this method, because this is hard code, and this kills the flexibility of our application. Application operation should be configured either through environment variables, or config files, or database records.





Conclusion

This set of rules is certainly not absolute, first of all we turn on the head and use common sense.





Let's discuss in the comments. Please share your experience.








All Articles