4

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

Alexis
  • 347
  • 5
  • 16

3 Answers3

2

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();
Mike Doe
  • 16,349
  • 11
  • 65
  • 88
  • Hi @emix, I don't know what's the better solution between the one I gave or doing TRUNCATE on each tables (currently 15 tables in database). I have a postgre database, so a bit difficult than mysql database to do TRUNCATE – Alexis Jun 06 '19 at 14:18
  • You can stick to schema drop & recreate then. – Mike Doe Jun 06 '19 at 14:19
  • I tried the second method with transactional but I always retrieve an HTTP 200. I commented the drop, so it tries to create the database but it's already created. It should send an HTTP 500 but it don't. Maybe transactional works when modifying entities and flushing them but not with a console command ? – Alexis Jun 06 '19 at 14:44
  • If you get 200 everything works well. Doesn’t it recreate tables? You can simulate the error by throwing an exception inside the transactional anonymous function. – Mike Doe Jun 06 '19 at 19:14
  • Finally, I used your first example but I did a check on output of $application->run to know if the operation has successfully worked because it didn't sent any error (it returns string). `$application->run( new StringInput('doctrine:schema:drop --force'), $output = new BufferedOutput() ); if (strpos($output->fetch(), 'successfully') !== false) { $application->run( new StringInput('doctrine:schema:create'), $output = new BufferedOutput() );` – Alexis Jun 10 '19 at 08:29
  • I don't believe it's required at any point. In case of an error you would get an exception coming from the executed command. – Mike Doe Jun 10 '19 at 08:43
  • I tried but no it doesn't get any exception back. What I tried : I commented the drop so it tries to create but it is already. When I dumped the output, I can see the exception in a string. That's why I have to do the check with strpos because we get back a string – Alexis Jun 10 '19 at 08:48
1

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
Arleigh Hix
  • 9,990
  • 1
  • 14
  • 31
1

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);
}
BentCoder
  • 12,257
  • 22
  • 93
  • 165