Strona Główna Blog webmasterski

Jak zaimportować dwie bazy danych w Symfony 4

Przyjmijmy, że mamy dwie strony internetowe — jedna zrobiona w WordPressie, a druga w PrestaShop — naszym zadaniem jest zrobienie backendu, który pobierze dane z obu stron i coś z nimi zrobi.

No to zaczynamy!

Na początek utwórzmy nowy projekt i zainstalujmy jednego bundla.

composer create-project symfony/website-skeleton my_project
composer req mikoweb/db-first-helper-bundle

Po utworzeniu projektu przystępujemy do konfiguracji połączeń z bazami danych. Połączenie domyślnie (default) zostawiamy do "wewnętrznej" logiki aplikacji i dodajemy dwa nowe, które nazwę db1 (WordpPess) i db2 (PrestaShop) — a zatem łącznie będziemy mieli trzy połączenia z bazą danych.

Tak oto wygląda kompletny plik config/packages/doctrine.yaml:

parameters:
    env(DATABASE_URL): ''

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver: 'pdo_mysql'
                server_version: '5.7'
                charset: utf8mb4
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_unicode_ci
                url: '%env(resolve:DATABASE_URL)%'
            db1:
                driver: 'pdo_mysql'
                server_version: '5.7'
                charset: utf8mb4
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_unicode_ci
                url: mysql://rafal:rafal@127.0.0.1:3306/dev_wp
            db2:
                driver: 'pdo_mysql'
                server_version: '5.7'
                charset: utf8mb4
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_unicode_ci
                url: mysql://rafal:rafal@127.0.0.1:3306/dev_prestashop
                mapping_types:
                    enum: string
                schema_filter: '/^(((?!accessory|cart_product|customer_message_sync_imap|linksmenutop_lang|order_detail_tax|order_invoice_tax).))*$/'
    orm:
        default_entity_manager: default
        auto_generate_proxy_classes: '%kernel.debug%'
        entity_managers:
            default:
                connection: default
                naming_strategy: doctrine.orm.naming_strategy.underscore
                auto_mapping: true
                mappings:
                    Default:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Default'
                        prefix: 'App\Entity\Default'
                        alias: Default
            db1:
                connection: db1
                naming_strategy: doctrine.orm.naming_strategy.underscore
                mappings:
                    Db1:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Db1'
                        prefix: 'App\Entity\Db1'
                        alias: Db1
            db2:
                connection: db2
                naming_strategy: doctrine.orm.naming_strategy.underscore
                mappings:
                    Db2:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Db2'
                        prefix: 'App\Entity\Db2'
                        alias: Db2

Jeśli ktoś jest ciekaw, co dokładnie robi ta magia, to odsyłam do dokumentacji. Ze swojej strony mogę dodać, że przy imporcie tabel z bazy PrestaShop pojawiły się drobne problemy. Pierwszy to enum w definicji którejś z kolumn (Doctrine nie rozumie tego typu), stad w połączeniu db2 wzięła się opcja mapping_types, która naprawia ten błąd.
Drugi problem to popsute tabele, które nie posiadają klucza głównego. Nie udało mi się znaleźć dobrego rozwiązania, więc postanowiłem zignorować niektóre tabele, za co odpowiada opcja schema_filter.
Teraz można łączyć się z różnymi bazami.

Komendy

Utwórzmy dwie komendy, każda z nich będzie odpowiedzialna za import innej bazy danych. Do napisania komend posłużymy się klasą abstrakcyjną zaciągniętą z zewnętrznego bundla.

Komenda 1
php bin/console make:command app:db1:import
src/Command/Db1ImportCommand.php
<?php

namespace App\Command;

use Mikoweb\Bundle\DbFirstHelperBundle\Command\ImportDatabaseCommandAbstract;

class Db1ImportCommand extends ImportDatabaseCommandAbstract
{
    protected static $defaultName = 'app:db1:import';
}
Dla tej komendy trzeba zmienić dwie opcje w pliku konfiguracyjnym config/packages/mikoweb_db_first_helper.yml:
mikoweb_db_first_helper:
    entity_folder: Entity/Db1 # Katalog gdzie będą zapisywane klasy Entity
    connection: db1           # Nazwa połączenia bazy danych

Komenda 2
php bin/console make:command app:db2:import
src/Command/Db2ImportCommand.php
<?php

namespace App\Command;

use Mikoweb\Bundle\DbFirstHelperBundle\Command\ImportDatabaseCommandAbstract;

class Db2ImportCommand extends ImportDatabaseCommandAbstract
{
    protected static $defaultName = 'app:db2:import';

    protected function getConnection(): string
    {
        return 'db2';
    }

    protected function getEntityFolder(): string
    {
        return 'Entity/Db2';
    }
}

W tym przypadku, zamiast modyfikować config, nadpisujemy dwie metody w klasie.

Uruchamiamy to!

php bin/console app:db1:import
php bin/console app:db2:import

W tym momencie obie bazy danych zostały już zaimportowane i można zacząć operować danymi. Żeby sprawdzić, czy to rzeczywiście działa, dodamy repository do encji WpPosts.

src/Repository/Db1/PostsRepository.php
<?php

namespace App\Repository\Db1;

use App\Entity\Db1\WpPosts;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;

class PostsRepository extends ServiceEntityRepository
{
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, WpPosts::class);
    }
}

A w kontrolerze pobierzemy sobie wszystkie posty z WordPressa. Zaręczam, że działa :)

public function __construct(PostsRepository $repository)
{
    $this->repository = $repository;
}

public function index()
{
    return $this->render('default/index.html.twig', [
        'posts' => $this->repository->findAll(),
    ]);
}

Podsumowanie

We wstępie napisałem, że naszym zadaniem jest pobranie danych z obu baz i zrobienie czegoś z nimi, lecz w tym wpisie pobrałem dane tylko z jednej bazy i nie zrobiłem z nimi nic użytecznego — myślę, że jest to temat na kolejny wpis.

Jeśli spodobał Ci się bundle użyty do celów tego wpisu, daj gwiazdkę.