Alexis
Alexis

Reputation: 377

Symfony - Best practice to reset the database

I'm working on a Symfony 4.2 project and I'm looking for the best practice to achieve a reset of the database when the admin needs to do it via a button in backoffice.

Explanation :

The project is a temporary event website. This means that, people will visit the website only for a day / a week and then the website is off. Per example, a website for spectators into a stadium during a basketball tournament.

When the tournament is over, the administrator would like to reset all datas sent during it via a button.

Right now I did it like this but I don't know if it's the better way in production environment.

I created a service which get the KernelInterface in constructor :

public function resetDB() {

    $application = new Application($this->kernel);
    $application->setAutoExit(false);

    $input = new ArrayInput([
        'command'   => 'doctrine:schema:drop',
        '--force' => true
    ]);

    $output = new BufferedOutput();
    $application->run($input, $output);

    $responseDrop = $output->fetch();

    if (strpos($responseDrop, 'successfully') !== false) {
        $input = new ArrayInput([
            'command'   => 'doctrine:schema:create',
        ]);

        $application->run($input, $output);

        $responseCreate = $output->fetch();

        if (strpos($responseCreate, 'successfully') !== false)
            return new Response();
    }

    return new \ErrorException();
}

Firstly, is it good to do it like this in a production environment ? (Nobody else the administrator will use the website when doing this operation)

Secondly, I'm not really satisfied with the method I used to check if the operation has been successfully done (strpos($responseCreate, 'successfully') !== false). Does someone know a better way ?

Thanks a lot for your help

Upvotes: 4

Views: 5387

Answers (3)

BentCoder
BentCoder

Reputation: 12730

Firstly, is it good to do it like this in a production environment ?

I don't think so! For example the commands below would warn you with: [CAUTION] This operation should not be executed in a production environment!. However, everything is possible in wild programming world as shown below.

Try Symfony's The Process Component.

This is the basic example so it is up to you make it cleaner and duplication free. I tested and it works. You can stream the output as well.

# DROP IT
$process = new Process(
    ['/absolute/path/to/project/bin/console', 'doctrine:schema:drop', '--force', '--no-interaction']
);
$process->run();
if (!$process->isSuccessful()) {
    throw new ProcessFailedException($process);
}

# RECREATE IT    
$process = new Process(
    ['/absolute/path/to/project/bin/console', 'doctrine:schema:update', '--force', '--no-interaction']
);
$process->run();
if (!$process->isSuccessful()) {
    throw new ProcessFailedException($process);
}

Upvotes: 1

Arleigh Hix
Arleigh Hix

Reputation: 10877

I'm not sure about the way you executing the commands, but there is a single command alternative to consider, using DoctrineFixturesBundle. You need to install it for use in the production environment (technically not recommended, I think because of risk of deleting prod data, but that's what you want to do).

Install:

$ composer require doctrine/doctrine-fixtures-bundle

Config:

// config/bundles.php

return [
  ...
  Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['all' => true],
  ...
];

You do need to create a fixture and it must have a load() method that is compatible with Doctrine\Common\DataFixtures\FixtureInterface::load(Doctrine\Common\Persistence\ObjectManager $manager), but it can be empty exactly like below:

<?php // src/DataFixtures/AppFixtures.php

namespace App\DataFixtures;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;

class AppFixtures extends Fixture
{
  public function load(ObjectManager $manager){}
}

The command:

$ php bin/console doctrine:fixtures:load -n --purge-with-truncate  --env=prod

Help:

$ php bin/console doctrine:fixtures:load --help

Upvotes: 1

Mike Doe
Mike Doe

Reputation: 17566

If it works for you, its ok. About the "successful" check part. Simply surround your call in a try-catch block and check for exceptions. If no exception was thrown, assume it did execute successfully.

$application = new Application($this->kernel);
$application->setAutoExit(false);

try {
    $application->run(
        new StringInput('doctrine:schema:drop --force'),
        new DummyOutput()
    );

    $application->run(
        new StringInput('doctrine:schema:create'),
        new DummyOutput()
    );

    return new Response();
} catch (\Exception $exception) {
    // don't throw exceptions, use proper responses
    // or do whatever you want

    return new Response('', Response::HTTP_INTERNAL_SERVER_ERROR);
}

Is PostgreSQL good enough at DDL transactions? Force a transaction then:

$application = new Application($this->kernel);
$application->setAutoExit(false);

// in case of any SQL error
// an exception will be thrown
$this->entityManager->transactional(function () use ($application) {
    $application->run(
        new StringInput('doctrine:schema:drop --force'),
        new DummyOutput()
    );

    $application->run(
        new StringInput('doctrine:schema:create'),
        new DummyOutput()
    );
});

return new Response();

Upvotes: 2

Related Questions