Připojení k více databázím zároveň

DrakMC
Člen | 35
+
0
-

Ahoj, potřeboval bych pracovat s více databázemi zároveň. Jak toho docílím? Našel jsem již řešené téma, ale je již 3 roky staré. https://forum.nette.org/…azi-najednou

nightfish
Člen | 518
+
+2
-

@DrakMC Docílíš toho tak, jak je popsáno ve 3 roky starém vlákně. A nebo třeba dle https://forum.nette.org/…uhe-databazi#…

Zkus popsat, k čemu ta dvě databázová spojení vlastně potřebuješ, možná existuje nějaké jiné řešení tvého problému.

DrakMC
Člen | 35
+
0
-

nightfish napsal(a):

@DrakMC Docílíš toho tak, jak je popsáno ve 3 roky starém vlákně. A nebo třeba dle https://forum.nette.org/…uhe-databazi#…

Zkus popsat, k čemu ta dvě databázová spojení vlastně potřebuješ, možná existuje nějaké jiné řešení tvého problému.

Díky za radu. :)

Vyzkoušel jsem to, ale bohužel to nefunguje. Zobrazuje to chybovou hlášku: https://i.imgur.com/cO4Vpkt.png

DatabaseAccessor.php
Mám ho v App\Model – je to dobré umístění? Případně jaké je lepší?

<?php

namespace App\Model;

interface DatabaseAccessor {
    function get($name): \Nette\Database\Context;
}

TestPresenter.php

<?php

namespace App\Presenters;

use App\Model\DatabaseAccessor;
use Nette;

final class TestPresenter extends Nette\Application\UI\Presenter {
    /** @var DatabaseAccessor @inject */
    public $databaseAccessor;

    public function renderDefault(): void {
        $db = $this->databaseAccessor->get("db1");
    }
}

services.neon

services:
	- App\Router\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
	    db1: @database.db1.context
	)

local.neon

parameters:


database:
    db1:
        dsn: 'mysql:host=127.0.0.1;dbname=databaze'
        user: uzivatel
        password: heslo
Marek Bartoš
Nette Blogger | 1274
+
+1
-

On ten interface v Nette 3 vypadá jinak

namespace App\Model;

use Nette\Database\Context;

interface ContextLocator
{
	public function getDb1(): Context;

	public function getDb2(): Context;
}
services:
	- App\Model\ContextLocator(a: @database.db1.context, b: @database.db2.context)

https://github.com/…orMulti.phpt

Editoval Marek Bartoš (30. 12. 2021 1:56)

Marek Bartoš
Nette Blogger | 1274
+
+2
-

Případně můžeš vyžadovat přímo Context, autowiring mít zapnutý jen pro hlavní databázi a službu s připojením pro další databázi do služeb předávat manuálně

database:
	primary:
		dsn: 'mysql:host=127.0.0.1;dbname=databaze'
		user: uzivatel
		password: heslo

	backlog:
		dsn: 'mysql:host=127.0.0.1;dbname=databaze'
		user: uzivatel
		password: heslo
		autowired: false

services:
	- App\Example\Service(@database.backlog.context)
namespace App\Example;

use Nette\Database\Context;

class Service
{
	public function __construct(private Context $context)
	{
	}
}

Editoval Marek Bartoš (30. 12. 2021 1:54)

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

On ten interface v Nette 3 vypadá jinak

namespace App\Model;

use Nette\Database\Context;

interface ContextLocator
{
	public function getDb1(): Context;

	public function getDb2(): Context;
}
services:
	- App\Model\ContextLocator(a: @database.db1.context, b: @database.db2.context)

https://github.com/…orMulti.phpt

Díky za rychlou odpověď. Nyní mi to zobrazuje tuto chybovou hlášku: https://i.imgur.com/NI1TuZ9.png

Marek Bartoš
Nette Blogger | 1274
+
0
-

Můžeš sem dát celý kód tvého locatoru a jak jej registruješ? Podle testů bych to měl mít imho správně.

Tipnu si: Nette si myslí, že chceš zapsat accessor a ne locator. Accessor může mít jen jednu metodu nazvanou get(), ty tam máš i další. Pojmenuj všechny metody getSomething(), ne jen get()

Editoval Marek Bartoš (30. 12. 2021 2:25)

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

Můžeš sem dát celý kód tvého locatoru a jak jej registruješ? Podle testů bych to měl mít imho správně.

Tipnu si: Nette si myslí, že chceš zapsat accessor a ne locator. Accessor může mít jen jednu metodu nazvanou get(), ty tam máš i další. Pojmenuj všechny metody getSomething(), ne jen get()

ContextLocator.php

<?php

namespace App\Model;

use Nette\Database\Context;

interface ContextLocator {
    public function getDb1(): Context;
}

services.neon

services:
	- App\Router\RouterFactory::createRouter
	- App\Model\ContextLocator(db1: @database.db1.context)
DrakMC
Člen | 35
+
0
-

Ještě přidávám, jak vypadá můj TestPresenter.php:

<?php

namespace App\Presenters;

use App\Model\ContextLocator;
use Nette;

final class TestPresenter extends Nette\Application\UI\Presenter {
    /* @var \App\Model\ContextLocator @inject */
    public $contextLocator;

    public function renderDefault(): void {
        $db = $this->contextLocator->getDb1();
    }
}
Marek Bartoš
Nette Blogger | 1274
+
+1
-

Zkus přidat druhou metodu, možná to nepředpokládá, že by byla jen jedna. Jinak to máš imho správně

DrakMC
Člen | 35
+
0
-

Bylo to tím – již to funguje. Díky!

Nicméně se mi zobrazil další error: https://i.imgur.com/lXEjvH2.png

Editoval DrakMC (30. 12. 2021 15:57)

Marek Bartoš
Nette Blogger | 1274
+
0
-

Nepředává se ti služba. Rozepiš var a inject anotace na více řádků, inline může být jen jedna.

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

Nepředává se ti služba. Rozepiš var a inject anotace na více řádků, inline může být jen jedna.

Myslíš pro každou databázi zvlášť? Jak? Mohl bys mi prosím ukázat příklad?

Marek Bartoš
Nette Blogger | 1274
+
-1
-
/**
 * @var \App\Model\ContextLocator
 * @inject
 */
public $contextLocator;

Ideálně si ale službu předej v konstruktoru. Mimo base presentery se spoustou závislostí je inject imho zbytečný.

Editoval Marek Bartoš (30. 12. 2021 16:52)

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

/**
 * @var \App\Model\ContextLocator
 * @inject
 */
public $contextLocator;

Ideálně si ale službu předej v konstruktoru. Mimo base presentery se spoustou závislostí je inject imho zbytečný.

Zobrazil se mi tento error: https://i.imgur.com/81NvmdE.png

Je to tím, že ho nemám přidaný v konstruktoru?

Marek Bartoš
Nette Blogger | 1274
+
0
-

Těžko říct, když nevidím kód

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

Těžko říct, když nevidím kód

Promiň, zasílám ho. :)

TestPresenter.php

<?php

namespace App\Presenters;

use App\Model\ContextLocator;
use Nette;

final class TestPresenter extends Nette\Application\UI\Presenter {
    /**
     * @var ContextLocator
     * @inject
     */
    public $contextLocator;

    public function renderDefault(): void {
        $db = $this->contextLocator->getDb1();
    }
}

ContextLocator.php

<?php

namespace App\Model;

use Nette\Database\Context;

interface ContextLocator {
    public function getDb1(): Context;

    public function getDb2(): Context;
}

services.neon

services:
	- App\Router\RouterFactory::createRouter
	- App\Model\ContextLocator(db1: @database.db1.context, db2: @database.db2.context)
Marek Bartoš
Nette Blogger | 1274
+
0
-

A služby database.db1.context a database.db2.context existují?

DrakMC
Člen | 35
+
0
-

Ano, existují:

local.neon:

parameters:


database:
    db1:
        dsn: 'mysql:host=127.0.0.1;dbname=databaze'
        user: uzivatel
        password: heslo
    db2:
        dsn: 'mysql:host=127.0.0.1;dbname=databaze'
        user: uzivatel
        password: heslo
Marek Bartoš
Nette Blogger | 1274
+
0
-

Zkus sem do LocatorDefinition přidat následující a poslat co ti to vypíše

bdump($name);
bdump($this->references);

Editoval Marek Bartoš (30. 12. 2021 17:29)

DrakMC
Člen | 35
+
0
-

Otevřel jsem soubor LocatorDefinition.php ve /vendor/nette/di/src/DI/Definitions a přidal

bdump($name);
bdump($this->references);

Upravený LocatorDefinition.php: https://i.imgur.com/eYJWa7B.png

Domnívám se, že se nic nezměnilo a ukazuje to stejnou chybovou hlášku: https://i.imgur.com/xRTJyXY.png

Marek Bartoš
Nette Blogger | 1274
+
0
-

Smaž vygenerovaný kontejner, jinak se ten kód nespustí.

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

Smaž vygenerovaný kontejner, jinak se ten kód nespustí.

Kde ho naleznu? Zkoušel jsem promazávat /temp/cache/.

Marek Bartoš
Nette Blogger | 1274
+
0
-

To je správně. Cesta k vygenerovanému kontejneru je vidět v poslední výjimce co jsi posílal. Určitě edituješ správný soubor? Jinde se stejný text výjimky nepoužívá, jestliže se spouští tak by bdump() měl též.

DrakMC
Člen | 35
+
0
-

Marek Bartoš napsal(a):

To je správně. Cesta k vygenerovanému kontejneru je vidět v poslední výjimce co jsi posílal. Určitě edituješ správný soubor? Jinde se stejný text výjimky nepoužívá, jestliže se spouští tak by bdump() měl též.

Editoval jsem soubor LocatorDefinition.php v /vendor/nette/di/src/DI/Definitions

Editoval DrakMC (30. 12. 2021 21:01)

David Grudl
Nette Core | 8227
+
+1
-

@DrakMC měl jsi to od začátku dobře, jen místo /* @var \App\Model\ContextLocator @inject */ musí být /** @var \App\Model\ContextLocator @inject */, ta hvězdička je podstatná, jinak PHP takový komentář zahodí a Nette se nemá jak dozvědět, že tam byl.

whatever122
Člen | 9
+
-17
-

Nebo si to udělej hardcoded a je to za minutu, místo toho sepisovat 100 dalších řádků do 10 souborů…
Fakt jakože extrém toto.
V normálním PHP se napíše „SELECT * FROM databaze.tabulka“ a je to hotovo.
Ale tady ne…
Tady se musí definovat config a stejně to nejde, jak člověk potřebuje.

			if (!isset(self::$sharedConnection)) {
				$dsn = 'mysql:host=localhost;dbname=shared';
				$user = 'xxxxx';
				$password = 'xxxxaaa';
				$conn = new \Nette\Database\Connection($dsn, $user, $password);
				$cacheStorage = new \Nette\Caching\Storages\MemoryStorage();
				$structure = new \Nette\Database\Structure($conn, $cacheStorage);
				self::$sharedConnection = new \Nette\Database\Context(
					$conn,
					$structure,
					new \Nette\Database\Conventions\StaticConventions()
				);

			}
			$sql = "SELECT xxxx FROM $table_id WHERE ? BETWEEN ipfrom AND ipto LIMIT 1";
			$row = self::$sharedConnection->query($sql, $lip_addr)->fetch();

Editoval whatever122 (30. 10. 0:50)

Marek Bartoš
Nette Blogger | 1274
+
+16
-

Takže sis to zpomalil tím, že jsi zrušil cache, heslo máš přímo v kódu a kdo má kód zná i tvoje produkční heslo, více instancí appky taky nemůžeš mít (local, test, produkce mají obvykle jiný login) a při kompilaci DIC se nedozvíš, že máš ve službách nějaký problém. Perfektní.

Sahat na jiné databáze můžeš ať už se to sestaví v DI nebo manuálně, to na problém vůbec nemá vliv. Konfigurace ti jen volí výchozí databázi. Když má uživatel přístup na obě, můžeš sahat na obě, i přes jednu instanci třídy Connection.

A jestli takhle píšeš sql dotazy i s parametry chodícími od uživatele, tak gratuluju, máš v kódu sql injection a každý s trochou zkušeností si může přečíst nebo smazat tvojí databázi.

Že tomu nerozumíš ještě neznamená že to nemá dobrý důvod.

whatever122
Člen | 9
+
-16
-

Cache na ten proces fakt nepotřebuju, ale pro tvoji informaci si cache dělá MySQL, takže zase řešíte nesmysly v tom Nette.
Dozví se heslo, a co jako? Si myslíš, že člověk nemá plný přístup na FTP a tím i ke configu.
Vy fakt jako přemýšlíte úplně v pohádkách u těch frameworků… a abych odpověděl na tvou další otázku, proč tu sem, tak protože klient zadal práci, jinak bych to smazal celé ten web. Zdar.

Kamil Valenta
Člen | 820
+
+11
-

Asi nemá moc smysl reagovat, ale aby na to někdo později nenarazil, tak přeci:

whatever122 napsal(a):

Cache na ten proces fakt nepotřebuju

To je určitě možné. Ale pokud takový postup někomu na fóru radíš, musí být obecně použitelný, tazateli nepomůže Tvůj jednoúčelový workaround, který může být v tom jednom jediném případě „efektivní“.

ale pro tvoji informaci si cache dělá MySQL

Což je ale zcela jiná cache a obsahuje zcela jiná data.

Dozví se heslo, a co jako?

Ví i Tvůj klient, že máš takový přístup k jeho produkčním datům? Mnohé klienty to zajímá.

Si myslíš, že člověk nemá plný přístup na FTP a tím i ke configu.

Ano, to si mnozí myslíme. Mnozí už taky dávno nepoužíváme prolomený protokol FTP. A ve větších týmech je zcela běžné, že ne všichni mají přístup ke všemu. To, že ty to tak nemáš, nebo nepoužíváš, neznamená, že to neexistuje.

proč tu sem, tak protože klient zadal práci, jinak bych to smazal celé ten web. Zdar.

Bohužel to nebyla šťastná volba, ale to asi klient neví.

whatever122
Člen | 9
+
-15
-

Je to zvláštní ty frameworky…
Většina lidí (zejména začátečníci) je volí na malé bezvýznamné weby, kde jako řešit nějaký sekundární „cache“ je úplně k ničemu.
A klienta asi nepotěší, když mu řeknu, že jsem 5 hodin řešil, jak definovat „správně“ druhou databázi :D
A to jenom proto, že Nette neumí normálně udělat SQL s výběrem jiné databáze, přičemž dané DB spojení má přístup k několika databázím, takže ke druhému spojení fakt není důvod. Hlavně, že tam máte cache se zápisem na disk a nevím co, radši nevědět :D

No, a pokud s NETTE tvoříš větší web, protože na tom dělá 20 začátečníků, tak je to optimalizované asi tak jako Wordpress…

Framework má podle mě smysl jen když člověk dělá jeden web za druhým, což není můj případ, a v tomhle případě bych tam opět mrskl hardcoded skript a čau.

Editoval whatever122 (31. 10. 19:32)

larryx
Člen | 8
+
+7
-

Vykasli sa na programovanie.

To ze sa nechces ucit a posuvat dalej je tvoj problem, ale radsej sa zdrz, tychto priblblich rad ludom co o to maju zaujem.

Presne tak sa to robilo pred 20 rokmi. Nie teraz.

FW su tu preto aby ti pracu ulahcili.

Ked setup connection do druhej DB by ti v nette zabral 5h, tak si na strasne slabej urovni. Je to totiz to vec 5 min, stym ze 4 min len si najdes a precitas k tomu dokomuntaciu. Ale to by si musel tomu trocha rozumiet. Ty urcite este pises svoje weby proceduralne ze? Ved naco objekty…

Ako nehnevaj sa, ale kasli radsej nato… bud sa dovzdelaj, alebo radsej nic nepis.

Dakujem

Marek Bartoš
Nette Blogger | 1274
+
+7
-

Vidíš, naše weby napsané v Nette lítají do 30 ms i bez pokročilejších optimalizačních technik i s vyššími miliony záznamů a díky tomu že ho využíváme naplno a dá se v něm narvhnout velmi slušné jádro CMS, tak zvládneme jednoduché weby za den, komplikované administrace v rámci týdnů.
Bezpečnostních chyb jsem za posledních 7 let řešil jednotky, daly se vyřešit vždy hromadnou aktualizací a jen jednou nešlo o prevenci.
Aktualizace jsme se nebáli nikdy, s pravidelnými aktualizacemi a phpstanem se většina problémů vyřešila ještě před tím, než jsem si aplikaci vůbec spustil.
Je to jen o přístupu a snaze se učit.

A k těm připojením k databázi specificky – když máš v databázi uživatele, který má přístup k oběma databázím, tak skutečně dvě připojení nepotřebuješ. A to v Nette funguje.
Když ale je pro každou databázi uživatel jiný (nebo je databáze dokonce na jiném serveru), tak už se bez dvou připojení neobejdeš ať už používáš cokoliv.

savalo
Člen | 12
+
0
-

whatever122 napsal(a):

Nebo si to udělej hardcoded a je to za minutu, místo toho sepisovat 100 dalších řádků do 10 souborů…
Fakt jakože extrém toto.
V normálním PHP se napíše “SELECT * FROM databaze.tabulka” a je to hotovo.
Ale tady ne…
Tady se musí definovat config a stejně to nejde, jak člověk potřebuje.

Tady je select from table a je hotovo bez konfigu: https://dibiphp.com/…ocumentation#…

m.brecher
Generous Backer | 871
+
0
-

@whatever122

A klienta asi nepotěší, když mu řeknu, že jsem 5 hodin řešil, jak definovat „správně“ druhou databázi :D

I když bych já osobně nikdy nevyměnil Nette za čisté PHP, v tomto konkrétním případě plně chápu, že když člověk multifactory musí použít, tak jít na to podle dokumentace může člověka dost potrápit.

Já jsem multifactory zkoušel před cca 2 lety a došel jsem k závěru, že nefungují a proto je nepoužívám. Toto vlákno mne vyhecovalo, abych se pokusil znovu a našel, kde dělám chybu. Chybu jsem neudělal, použil jsem kód přesně podle dokumentace, chyba je v nekompletnosti popisu v dokumentaci jak vícenásobnou továrnu / accessor použít.

Dokumentace:

https://doc.nette.org/…tion/factory#…

Uvedené příklady kódu:

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}
interface MultiFactoryAlt
{
	function get($name): PDO;
}

Jiné informace než tyto sample code v dokumentaci nenajdeme. Tyto sample code samy o sobě nefungují a řešení proč jsem našel v jednom vlákně na fóru zde:

https://forum.nette.org/…di-3-0-beta1

@DavidGrudl zde uvádí správné a kompletní příklady PHP kódu jak vícenásobnou továrnu / accessor použít:

interface MultiAccessor
{
	function get($name): Listener
}
services:
	- App\Model\CacheCleanerAuthSubscriber
	- App\Model\LoggerAuthSubscriber
	third: App\Model\DummyAuthSubscriber

	- MultiAccessor(
		sub1: @App\Mode\CacheCleanerAuthSubscriber
		sub2: @App\Model\LoggerAuthSubscriber
		sub3: @third
	)
$sub1 = $multiAccessor->get('sub1'); // vrací objekt App\Mode\CacheCleanerAuthSubscriber
$sub2 = $multiAccessor->get('sub2'); // vrací objekt App\Model\LoggerAuthSubscriber

Z tohoto sample kódu se dá snadno pochopit, že multi accessor musí mít v konfiguraci v konstruktoru předané příslušné služby pod jménem klíče, který odpovídá jménu pod kterým je později získáván z accessoru. Analogické pravidlo platí i pro variantu s metodami get<ServiceName>().

Na fóru najdeme více příkladů, kdy se komunita s velkými obtížemi snažila poradit nováčkovi jak správně nakonfigurovat vícenásobný accessor. Přitom stačí doplnit do dokumentace chybějící zásadní informace pro funkční použití této zajímavé feature a je po problému. Správná konfigurace více připojení do databáze je pak otázkou ne 5 hodin, ale 2 minut.

Podám do dokumentace příslušný PR

Editoval m.brecher (1. 11. 13:33)

Šaman
Člen | 2663
+
+3
-

Jen bych připomněl, že se jednalo o tři roky staré vlákno, než ho Whatever122 znovu otevřel a končilo Davidovým příspěvkem, že chyba byla od začátku v chybějící hvězdičce phpDoc anotace. Od té doby je to jen offtopic diskuze o tom, jestli jsou všechny ty frameworky zlo, nebo nejsou.

m.brecher
Generous Backer | 871
+
+1
-

@Šaman

Jen bych připomněl, že se jednalo o tři roky staré vlákno

Kritický příspěvek @whatever122, který řešil 5 hodin multi factory, mě připomnělo, že ani mě se před lety nepodařilo podle dokumentace multi factory zprovoznit. Přitom je to trivialita a mělo by to zabrat tak 2 minuty. Zkusil jsem to znovu a dopadl stejně.

Proč ?

Před odstavcem Multi továrny je odstavec Generované továrny, kde není potřeba žádná speciální konfigurace a factory pozná jaký objekt vytvořit podle návratového typu metody create(). V odstavci Multi továrny je pak ukázka kódu se dvěma metodami a jejich návratovými typy. Vypadá to že to funguje analogicky. Jenže ono to analogicky podle návratového typu nefunguje, ale služby se musí předat do konstruktoru multi factory v konfiguraci. To ale lze zjistit až pracným prohrabáváním příspěvků na fóru.

Tak doplníme dokumentaci a je problém vyřešený. Někomu se Nette nelíbí a má potřebu nám to sdělovat na fóru ? Nechme ho být, ale když už někde najdeme chybu, tak je škoda ji neodstranit. Vynikající framework pak bude ještě lepší.