Symfony 4: сохранить соединение SQLite PDO в тесте + контроллер + расширения twig

Моя ситуация

У меня есть проект Symfony 4.2 со следующей структурой:

  • источник
    • Контроллер
    • Услуга
    • Расширения ветки
  • Шаблоны
  • Тесты

Я использую класс базы данных, который внутри создает соединение PDO. Если я запускаю свои тесты с PHPUnit, мой класс базы данных должен переключиться с mysql на sqlite. Здесь все работает нормально.

Моя проблема

Я не могу сохранить однажды созданный экземпляр базы данных при запуске только теста один. Symfony, кажется, воссоздает его во время теста: внутри шаблона Twig, который использует расширение Twig. Поскольку класс базы данных использует

new \PDO('sqlite::memory:');

он теряет созданные таблицы, и поэтому тест не проходит. Я знаю, что экземпляр базы данных (со ссылкой на PDO) сбрасывается после каждого теста, но в моей ситуации у меня есть только один тест. Как я могу убедиться, что он повторно использует экземпляр базы данных?

Здесь соответствующий код

Класс InstanceExtension предоставляет заголовок функции, которая используется в шаблоне Twig и должна обращаться к базе данных.

<?php

namespace App\TwigExtension;

use App\Service\Database;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;

class InstanceExtension extends AbstractExtension
{
    protected $db;

    /**
     * @param Database $db
     */
    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    /**
     * Tries to return the title/name for a given ID.
     *
     * @param string $subject
     * @param string|array $tables
     * @param string $lang         Default is 'de'
     *
     * @return string label or id, if no title/name was found
     */
    public function title(string $subject, $tables, string $lang = 'de'): string
    {    
        return $this->db->get_instance_title($subject, $tables, $lang);
    }
}

В моем services.yaml класс Database установлен как общедоступный (что должно разрешить его повторное использование, не так ли?):

App\Service\Database:
    public: true

Класс базы данных

Вот часть класса базы данных, которая инициализирует соединение PDO. Рабочий код, который вместо этого использует MySQL, удален:

<?php

declare(strict_types=1);

namespace App\Service;

class Database
{
    /**
     * @param Config $app_config
     *
     * @throws \Exception
     */
    public function __construct(Config $app_config)
    {
        global $sqlite_pdo;

        try {
            // non test
            // ..

            // test environment
            } else {
                $this->db_engine = 'sqlite';

                // workaround to ensure we have the same PDO instance everytime we use the Database instance
                // if we dont use it, in Twig extensions a new Database instance is created with a new SQLite
                // database, which is empty.
                if (null == $sqlite_pdo) {
                    $pdo = new \PDO('sqlite::memory:');
                    $sqlite_pdo = $pdo;
                } else {
                    $pdo = $sqlite_pdo;
                }
            }

        } catch (\PDOException $e) {
            if (\strpos((string) $e->getMessage(), 'could not find driver') !== false) {
                throw new \Exception(
                    'Could not create a PDO connection. Is the driver installed/enabled?'
                );
            }

            if (\strpos((string) $e->getMessage(), 'unknown database') !== false) {
                throw new \Exception(
                    'Could not create a PDO connection. Check that your database exists.'
                );
            }

            // Don't leak credentials directly if we can.
            throw new \Exception(
                'Could not create a PDO connection. Please check your username and password.'
            );
        }

        if ('mysql' == $this->db_engine) {
            // ...
        }

        // default fetch mode is associative
        $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);

        // everything odd will be handled as exception
        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

        $this->db = $pdo;
    }

    // ...
}

Один тест выглядит примерно так:

<?php 

class SalesChannelControllerTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();

        // init Database class, using SQLite
        $this->init_db();

        // further setup function calls

        // SalesChannelController is a child of
        // Symfony\Bundle\FrameworkBundle\Controller\AbstractController
        $this->fixture = new SalesChannelController(
            $this->db
            // ...
        );
    }

    /**
     * Returns a ready-to-use instance of the database. Default adapter is SQLite.
     *
     * @return Database
     */
    protected function init_db(): Database
    {
        // config parameter just contains DB credentials
        $this->db = new Database($this->config);
    }

    public function test_introduction_action()
    {
         // preparations

         /*
          * run action
          *
          * which renders a Twig template, creates a Response and returns it.
          */
         $response = $this->fixture->introduction_action($this->request, $this->session, 'sales_channel1');

         /*
          * check response
          */
         $this->assertEquals(200, $response->getStatusCode());
    } 
}

Мой текущий обходной путь заключается в том, чтобы сохранить экземпляр PDO в глобальной переменной и повторно использовать его, если это необходимо.

<?php 

global $sqlite_pdo;

// ...

// inside Database class, when initializing the PDO connection
if (null == $sqlite_pdo) {
    $pdo = new \PDO('sqlite::memory:');
    $sqlite_pdo = $pdo;
} else {
    $pdo = $sqlite_pdo;
}

Если вам нужна дополнительная информация, пожалуйста, сообщите мне. Спасибо за ваше время и помощь заранее!

Не могли бы вы написать, как выглядит ваш тест? Похоже, это предполагаемое поведение, поскольку контекст в памяти должен очищаться между каждым тестом, оставляя вас с пустой базой данных. Чтобы быть уверенным и предложить хороший подход, было бы полезно увидеть используемую тестовую установку.

dbrumann 05.02.2019 17:36

Привет @dbrumann, я обновлю свой первоначальный вопрос.

k00ni 05.02.2019 17:50
Потому что я не мог обновить свой предыдущий комментарий: Я обновил вопрос и разъяснил проблему. Также предоставлен запрошенный тестовый пример. Спасибо за ваше время.
k00ni 05.02.2019 18:03

Это уже может помочь сохранить фикстуру внутри статической переменной, а затем использовать public static function setupBeforeClass() вместо простой настройки. Это сохранит прибор между каждым тестом. Дайте мне знать, если это поможет или вам нужен полный пример. Это все еще может зависеть от того, что делает init_db. Также было бы полезно увидеть содержимое этой функции.

dbrumann 05.02.2019 18:22

Привет @dbrumann, установкаBeforeClass возможна. Но мне было интересно, могу ли я настроить его в самой Symfony. Любая идея?

k00ni 06.02.2019 10:29

Насколько я могу судить, ничего не связано с вашим приложением Symfony, например. загрузка ядра и получение соединения из контейнера в вашем тесте. Если вам нужна тестовая база данных, которую можно использовать во всем наборе тестов, я обычно делаю сценарий начальной загрузки, который создает sqlite-файл в моей папке var/, использует команды Doctrine для построения схемы, а затем указывает DATABASE_URL, используемый Symfony в этот файл. Для примера см. этот демонстрационный проект: github.com/dbrumann/todo-basic/blob/master/tests/bootstrap.p‌​hp

dbrumann 06.02.2019 11:53

Как я уже писал в своем первоначальном вопросе, он сбрасывает соединение PDO при рендеринге шаблона Twig. Это делается в действии контроллера. Symfony участвует большую часть времени. Части, не относящиеся к Symfony, работают нормально. Поскольку база данных — это служба, мне было интересно, как настроить ее для повторного использования без повторного создания экземпляра.

k00ni 07.02.2019 09:51

Если бы этот материал базы данных был службой, он повторно использовался бы без повторного создания. Почему бы вам не использовать эту услугу в тестовом примере?

Nico Haase 07.02.2019 09:56

Я думал, что сделал. Пожалуйста, посмотрите в моем вопросе InstanceExtension, у которого Database является первым параметром. Это класс, который обрабатывает вещи PDO. InstanceExtension предоставляет функцию title, которую я использую в шаблоне ветки. Этот шаблон отображается в моем тестовом примере с помощью действия контроллера, называемого introduction_action (от контроллера SalesChannelController, дочернего элемента от Symfony\Bundle\FrameworkBundle\Controller\AbstractController‌​). Но экземпляр базы данных повторно создается Symfony, по какой-то причине я не знаю.

k00ni 07.02.2019 12:10

Установка службы на public не должна здесь меняться, но что делает init_db?

Nico Haase 07.02.2019 12:19

Привет @NicoHaase, я обновлю свой первоначальный вопрос и добавлю эту информацию.

k00ni 07.02.2019 12:38

Что ж, теперь мы подходим к этому: почему вы создаете новый экземпляр этого класса базы данных при каждой установке? Почему бы тебе не использовать для этого свой контейнер?

Nico Haase 07.02.2019 12:46

Спасибо за быстрый ответ. Я не уверен, правильно ли я вас понял. Что вы подразумеваете под «использовать свой контейнер»? Есть ли здесь лучшая практика, которой я могу следовать?

k00ni 07.02.2019 13:28

Конечно, есть: когда вы используете контейнер (как вы это делаете, когда используете сервисы Symfony), вы не должны создавать экземпляры сервисов вручную, а извлекать их из контейнера.

Nico Haase 07.02.2019 15:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
14
554
0

Другие вопросы по теме