{"id":358,"date":"2022-10-16T12:29:29","date_gmt":"2022-10-16T10:29:29","guid":{"rendered":"https:\/\/carsten.familie-schumann.info\/blog\/?p=358"},"modified":"2023-02-16T12:31:10","modified_gmt":"2023-02-16T10:31:10","slug":"mehrere-datenbanken-unter-symfony","status":"publish","type":"post","link":"https:\/\/carsten.familie-schumann.info\/blog\/2022\/10\/mehrere-datenbanken-unter-symfony\/","title":{"rendered":"Mehrere Datenbanken unter Symfony"},"content":{"rendered":"<div data-post-id=\"350\" class=\"insert-page insert-page-350 post-entry-content\"><p>In einem Projekt bestand die Notwendigkeit, die applikationsspezifische Datenbank (MariaDB) und eine externen Datenbank (MS-SQL) gleichzeitig zu verwenden. An der Datenbankstruktur war nichts zu drehen, da die MS-SQL Datenbank in anderen Projekten ebenfalls im Produktivbetrieb verwendet wurde. Somit bestand das Problem, zwei Datenbankverbindungen parallel offen zu halten und je nach Entity-Klasse die richtige Datenbank zu verwenden.<\/p>\n<p>Wie man sich unter Linux mit einer MS-SQL Datenbank verbindet, habe ich bereits im Artikel <a href=\"https:\/\/carsten.familie-schumann.info\/blog\/ms-sql-mit-symfony-unter-linux\/\">MS-SQL mit Symfony unter Linux<\/a> im Detail beschrieben. In diesem Artikel geht es vornehmlich um den gleichzeitigen Betrieb zweier Datenbank und die automatische Auswahl der korrekten Datenbankverbindung.<\/p>\n<h2>Zwei Datenbanken parallel betreiben<\/h2>\n<p>Hierf\u00fcr habe ich zur einfacheren \u00dcbersicht in den Verzeichnissen <code>src\/Entity<\/code> und <code>src\/Repository<\/code> ein Unterverzeichnis <code>MsSql<\/code> angelegt, in welche die Klassen der externen Datenbank gelegt werden. Dieser Schritt erh\u00f6ht lediglich die \u00dcbersicht f\u00fcr menschliche Betrachter; Symfony\/Doctrine ist das egal.<\/p>\n<p>Somit ergeben sich die Verzeichnisse<\/p>\n<table>\n<tbody>\n<tr>\n<th>Verzeichnis<\/th>\n<th>Verwendung<\/th>\n<\/tr>\n<tr>\n<td><code>src\/Entity<\/code><\/td>\n<td>Entity-Klassen der MariaDB-Datenbank<\/td>\n<\/tr>\n<tr>\n<td><code>src\/Entity\/MsSql<\/code><\/td>\n<td>Entity-Klassen der MS-SQL-Datenbank<\/td>\n<\/tr>\n<tr>\n<td><code>src\/Repository<\/code><\/td>\n<td>Repository-Klassen der MariaDB-Datenbank<\/td>\n<\/tr>\n<tr>\n<td><code>src\/Repository\/MsSql<\/code><\/td>\n<td>Repository-Klassen der MS-SQL-Datenbank<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Zudem habe ich in der Datei <code>doctrine.yaml<\/code> zwei Verbindungen und zwei Entity-Manager definiert:<\/p>\n<pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\r\ndoctrine:\r\n    dbal:\r\n        default_connection:       default\r\n        connections:\r\n            default:\r\n                driver: pdo_mysql\r\n                dbname:           &quot;%env(resolve:DATABASE_DB)%&quot;\r\n                user:             &quot;%env(resolve:DATABASE_USER)%&quot;\r\n                password:         &quot;%env(resolve:DATABASE_PASS)%&quot;\r\n                host:             &quot;%env(resolve:DATABASE_HOST)%&quot;\r\n                port: 3306\r\n                schema_filter: ~^(?!tt&#x5B;a-z]{4}&#x5B;0-9]{6})~  # Ignore Mssql tables\r\n            mssql:\r\n                # IMPORTANT: this configuration requires FreeTDS installed!\r\n                # See doc\/OdbcMssql.md for details!\r\n                driver_class: App\\Lib\\OdbcMssql\\Driver\r\n                driver: pdo_mssql\r\n                dbname:           &quot;%env(resolve:MSSQL_DB)%&quot;\r\n                user:             &quot;%env(resolve:MSSQL_USER)%&quot;\r\n                password:         &quot;%env(resolve:MSSQL_PASS)%&quot;\r\n                host:             &quot;%env(resolve:MSSQL_HOST)%&quot;\r\n                port: 1433\r\n                # Eigentlich k\u00f6nnte FreeTDS hier schon eine automatische Konvertierung in UTF-8 vornehmen. Leider\r\n                # speichert unser MSSQL aber Strings als b&quot;Dies ist ein String&quot;, also als byte&#x5B;] und nicht als String.\r\n                # Daher schl\u00e4gt die Konvertierung fehl und muss in den Entities manuell per iconv erfolgen.\r\n                options: \r\n                  'client_charset': 'WINDOWS-1252'\r\n                server_version: '2008'\r\n        # IMPORTANT: You MUST configure your server version,\r\n        # either here or in the DATABASE_URL env var (see .env file)\r\n        #server_version: '13'\r\n    orm:\r\n        auto_generate_proxy_classes: true\r\n        default_entity_manager: default\r\n        entity_managers:\r\n            default:\r\n                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware\r\n                auto_mapping: true\r\n                mappings:\r\n                  App:\r\n                      is_bundle: false\r\n                      type: annotation\r\n                      dir: '%kernel.project_dir%\/src\/Entity'\r\n                      prefix: 'App\\Entity'\r\n                      alias: App\r\n            mssql:\r\n                connection: mssql\r\n                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware\r\n                mappings:\r\n                  Mssql:\r\n                      is_bundle: false\r\n                      type: annotation\r\n                      dir: '%kernel.project_dir%\/src\/Entity\/Mssql'\r\n                      prefix: 'App\\Entity\\Mssql'\r\n                      alias: Mssql\r\n<\/pre>\n<p>Die Verwendung von den Einzelnen Feldern Host, User, Pass und DB erfolgt hier absichtlich, da DATABASE_URL nicht sauber mit MS-SQL funktioniert.<\/p>\n<p>Da die <code>doctrine.yml<\/code> auf verschiedene <code>.env<\/code> Variablen verweist, werden diese noch erg\u00e4nzt (und mit sinnvollen Werten belegt).<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nDATABASE_HOST=www.example.com\r\nDATABASE_USER=user\r\nDATABASE_PASS=define-in-.env.local\r\nDATABASE_DB=database1\r\n\r\nMSSQL_HOST=www.anotherexample.com\r\nMSSQL_USER=anotheruser\r\nMSSQL_PASS=define-in-.env.local\r\nMSSQL_DB=database2\r\n<\/pre>\n<p>Die Klasse <code>App\\Lib\\OdbcMssql\\Driver<\/code> findet sich bereits in dem vorgehenden Tutorial <a href=\"https:\/\/carsten.familie-schumann.info\/blog\/ms-sql-mit-symfony-unter-linux\/\">MS-SQL mit Symfony unter Linux<\/a>.<\/p>\n<p>An diesem Punkt weiss Symfony, dass wir zwei Datenbanken haben und wir k\u00f6nnen die Datenbanken manuell ausw\u00e4hlen. Das ist aber umst\u00e4ndlich, fehleranf\u00e4llig und verhindert, dass wir Entit\u00e4ten aus der MS-SQL Instanz \u00fcber Injection holen.<\/p>\n<h2>Automatische Auswahl der korrekten Datenbank<\/h2>\n<p>Damit die Magie volle Fahrt aufnimmt, m\u00fcssen wir Symfony die M\u00f6glichkeit geben, automagisch die richtige Verbindung zu verwenden. Die Idee ist relativ einfach. Wir modifizieren die Repository-Klassen derart, dass der <code>EntityManagerDecorator<\/code> f\u00fcr die korrekte Datenbank verwendet wird. Daf\u00fcr <i>vergewaltigen<\/i> das Autowiring.<\/p>\n<p>Zuerst definieren wir uns eine Repository-Klasse, die sp\u00e4ter nicht mehr die Default-Verbindung verwenden soll:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nnamespace App\\Repository\\Infor;\r\n\r\nuse App\\Entity\\Mssql\\MssqlArticle;\r\nuse App\\Lib\\OdbcMssql\\OdbcEntityManager;\r\n\r\nclass MssqlArticleRepository extends EntityRepository\r\n{\r\n    public function __construct(OdbcEntityManager $em)\r\n    {\r\n        parent::__construct($em, $em-&gt;getClassMetadata(MssqlArticle::class));\r\n    }\r\n}\r\n<\/pre>\n<p>W\u00e4hrend ein normaler Konstruktor einer Repository-Klasse ein Objekt von Typ <code>ManagerRegistry<\/code> haben m\u00f6cht, will unser Repository ein <code>OdbcEntityManager<\/code> haben. Das Autowiring wird nun alles tun, um unserer Bitte nachzukommen. Die Klasse <code>src\/Lib\/OdbcMssql\/OdbcEntityManager.php<\/code> sieht dabei herzlich unspektakul\u00e4r aus; wir brauchen sie nur, damit das Autowiring das richtige f\u00fcr uns tut:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\r\nnamespace App\\Lib\\OdbcMssql;\r\n\r\nuse Doctrine\\ORM\\Decorator\\EntityManagerDecorator;\r\n\r\nclass OdbcEntityManager extends EntityManagerDecorator\r\n{\r\n}\r\n<\/pre>\n<p>Doch woher kommt nun die richtige Datenbankverbindung? Hierf\u00fcr m\u00fcssen wir noch die <code>config\/services.yml<code> anpassen und erg\u00e4nzen:<\/code><\/code><\/p>\n<pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\r\nservices:\r\n    App\\Lib\\OdbcMssql\\OdbcEntityManager:\r\n      arguments:\r\n          $wrapped: '@doctrine.orm.mssql_entity_manager'\r\n<\/pre>\n<p>Damit injezieren wir die richtige Datenbankverbindung in diese Klasse und Doctrine kommt damit klar.<\/p>\n<h2>Warnungen<\/h2>\n<p>Prinzipiell k\u00f6nnen wir nun mit den MS-SQL Klassen so arbeiten, als m\u00fcssten wir die Datenbankverbindung nicht wechseln. <b>Was in dieser Konstellation jedoch nicht geht sind Joins \u00fcber Datenbankgrenzen hinweg!<\/b>. Je nach Beziehungen zwischen verschiedensten Tabellen kann ein 1:n Verkn\u00fcpfung damit zur Datenbank-Katastrophe werden&#8230;.vor allem, wenn man 10.000 Datens\u00e4tze hat.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-358","post","type-post","status-publish","format-standard","hentry","category-programmierung"],"_links":{"self":[{"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/posts\/358","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/comments?post=358"}],"version-history":[{"count":1,"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/posts\/358\/revisions"}],"predecessor-version":[{"id":359,"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/posts\/358\/revisions\/359"}],"wp:attachment":[{"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/media?parent=358"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/categories?post=358"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/carsten.familie-schumann.info\/blog\/wp-json\/wp\/v2\/tags?post=358"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}