master

laravel/framework

Last updated at: 29/12/2023 09:22

DatabaseTruncation.php

TLDR

The DatabaseTruncation.php file in the Illuminate\Foundation\Testing namespace provides a trait called DatabaseTruncation. This trait is used to truncate the database tables for all configured connections. It contains several methods for truncating tables, retrieving the connections to truncate, and performing tasks before and after truncating the database.

Methods

truncateDatabaseTables

This method truncates the database tables for all configured connections. It first checks if the database has been migrated and seeded before. If not, it runs the necessary migrations and seeds. Then, it truncates the tables for all connections and performs additional seeding if necessary.

truncateTablesForAllConnections

This method truncates the database tables for all configured connections. It fetches the database instance from the application container and iterates over the connections to truncate. For each connection, it disables foreign key constraints and calls the truncateTablesForConnection method.

truncateTablesForConnection

This method truncates the database tables for the given database connection. It retrieves the list of tables for the connection and filters them based on the tablesToTruncate and exceptTables properties. It then iterates over the filtered tables and truncates them using the connection instance.

withoutTablePrefix

This method removes the table prefix from a table name, if it exists. It accepts a connection instance and a table name as parameters. It checks if the table name starts with the connection's table prefix and removes the prefix if it does.

connectionsToTruncate

This method returns an array of database connections that should have their tables truncated. If the connectionsToTruncate property is defined, it is returned; otherwise, an array with a null value is returned.

exceptTables

This method returns an array of tables that should not be truncated. It accepts a connection name as an optional parameter. It first retrieves the migrations table name from the configuration. Then, it checks if the exceptTables property is defined. If it is a list, it merges it with the migrations table name. If it is an associative array, it merges the connection-specific except tables with the migrations table name.

beforeTruncatingDatabase

This method is a hook that can be overridden by child classes or traits to perform any work that should take place before the database starts truncating.

afterTruncatingDatabase

This method is a hook that can be overridden by child classes or traits to perform any work that should take place once the database has finished truncating.

END

<?php

namespace Illuminate\Foundation\Testing;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Foundation\Testing\Traits\CanConfigureMigrationCommands;

trait DatabaseTruncation
{
    use CanConfigureMigrationCommands;

    /**
     * The cached names of the database tables for each connection.
     *
     * @var array
     */
    protected static array $allTables;

    /**
     * Truncate the database tables for all configured connections.
     *
     * @return void
     */
    protected function truncateDatabaseTables(): void
    {
        $this->beforeTruncatingDatabase();

        // Migrate and seed the database on first run...
        if (! RefreshDatabaseState::$migrated) {
            $this->artisan('migrate:fresh', $this->migrateFreshUsing());

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;

            return;
        }

        // Always clear any test data on subsequent runs...
        $this->truncateTablesForAllConnections();

        if ($seeder = $this->seeder()) {
            // Use a specific seeder class...
            $this->artisan('db:seed', ['--class' => $seeder]);
        } elseif ($this->shouldSeed()) {
            // Use the default seeder class...
            $this->artisan('db:seed');
        }

        $this->afterTruncatingDatabase();
    }

    /**
     * Truncate the database tables for all configured connections.
     *
     * @return void
     */
    protected function truncateTablesForAllConnections(): void
    {
        $database = $this->app->make('db');

        collect($this->connectionsToTruncate())
            ->each(function ($name) use ($database) {
                $connection = $database->connection($name);

                $connection->getSchemaBuilder()->withoutForeignKeyConstraints(
                    fn () => $this->truncateTablesForConnection($connection, $name)
                );
            });
    }

    /**
     * Truncate the database tables for the given database connection.
     *
     * @param  \Illuminate\Database\ConnectionInterface  $connection
     * @param  string|null  $name
     * @return void
     */
    protected function truncateTablesForConnection(ConnectionInterface $connection, ?string $name): void
    {
        $dispatcher = $connection->getEventDispatcher();

        $connection->unsetEventDispatcher();

        collect(static::$allTables[$name] ??= $connection->getDoctrineSchemaManager()->listTableNames())
            ->when(
                property_exists($this, 'tablesToTruncate'),
                fn ($tables) => $tables->intersect($this->tablesToTruncate),
                fn ($tables) => $tables->diff($this->exceptTables($name))
            )
            ->filter(fn ($table) => $connection->table($this->withoutTablePrefix($connection, $table))->exists())
            ->each(fn ($table) => $connection->table($this->withoutTablePrefix($connection, $table))->truncate());

        $connection->setEventDispatcher($dispatcher);
    }

    /**
     * Remove the table prefix from a table name, if it exists.
     *
     * @param  \Illuminate\Database\ConnectionInterface  $connection
     * @param  string  $table
     * @return string
     */
    protected function withoutTablePrefix(ConnectionInterface $connection, string $table)
    {
        $prefix = $connection->getTablePrefix();

        return strpos($table, $prefix) === 0
            ? substr($table, strlen($prefix))
            : $table;
    }

    /**
     * The database connections that should have their tables truncated.
     *
     * @return array
     */
    protected function connectionsToTruncate(): array
    {
        return property_exists($this, 'connectionsToTruncate')
                    ? $this->connectionsToTruncate : [null];
    }

    /**
     * Get the tables that should not be truncated.
     *
     * @param  string|null  $connectionName
     * @return array
     */
    protected function exceptTables(?string $connectionName): array
    {
        $migrations = $this->app['config']->get('database.migrations');

        $migrationsTable = is_array($migrations) ? ($migrations['table'] ?? null) : $migrations;

        if (property_exists($this, 'exceptTables')) {
            if (array_is_list($this->exceptTables ?? [])) {
                return array_merge(
                    $this->exceptTables ?? [],
                    [$migrationsTable],
                );
            }

            return array_merge(
                $this->exceptTables[$connectionName] ?? [],
                [$migrationsTable],
            );
        }

        return [$migrationsTable];
    }

    /**
     * Perform any work that should take place before the database has started truncating.
     *
     * @return void
     */
    protected function beforeTruncatingDatabase(): void
    {
        //
    }

    /**
     * Perform any work that should take place once the database has finished truncating.
     *
     * @return void
     */
    protected function afterTruncatingDatabase(): void
    {
        //
    }
}