Připojení k více databázím zároveň
- DrakMC
- Člen | 35
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
@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
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
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
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
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)
Díky za rychlou odpověď. Nyní mi to zobrazuje tuto chybovou hlášku: https://i.imgur.com/NI1TuZ9.png
- Marek Bartoš
- Nette Blogger | 1274
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
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 metodygetSomething()
, ne jenget()
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
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
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
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
Nepředává se ti služba. Rozepiš var a inject anotace na více řádků, inline může být jen jedna.
- Marek Bartoš
- Nette Blogger | 1274
/**
* @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
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?
- DrakMC
- Člen | 35
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
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
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
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
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
@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
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
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
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
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
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
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
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
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
@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
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
@Š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ší.