Základní práce s databází
- Croc
- Člen | 270
Moc prosím o radu. Zkouším si hrát s Nette/Database, ale mám problém s výpisem (uložením dat do pole) načtených hodnot. Nikde jsem nenašel jak je zápis pro fetch() nebo fetchAll().
Padá mi tam většinou hláška: Call to a member function closeCursor() on a non-object.
Bohůžel jsem nikde v dokumentaci nebo na fóru nenašel aktuální informace o zápisu fetch/fetchAll či closeCursor a kdy se používá…
P.S. Nepoužívám MVC strukturu
Editoval Croc (1. 1. 2015 17:23)
- Croc
- Člen | 270
Zkoušel jsem různé možnosti, ale aktuálně mám toto (dostávám výše uvedenou chybu):
$stat = array();
$stms->query('SELECT * FROM stat;');
$stat = $stms->fetch()->toArray();
Toto funguje a vypíše danou tabulku:
$stms->query('SELECT * FROM stat;')->dump();
Jde mi spíš o obecný zápis fetch() a fetchAll() pro Nette/Database do pole…Nikde jsem to nenašel a co jsem zkoušel nefunguje :(
Editoval Croc (1. 1. 2015 20:51)
- Croc
- Člen | 270
Ještě bych měl jeden začátečnický dotaz.
Jak jednoduše definovat připojení pro jakékoliv funkce/třídy? Nechce se mi do každé funkce používající databázi vkládat toto:
$dsn = 'xxxx';
$user = 'xxx';
$password = 'xxxx';
$connection = new Connection($dsn, $user, $password);
$database = new Context($connection);
Prosím o nasměrování (či uvedení příkladu) jak se dá efektivně připojení k DB používat v různých třídách/funkcích… Díky moc.
- David Matějka
- Moderator | 6445
pomoci di, prikladem je treba UserManager v sandboxu, nezapomen to registrovat jako sluzbu
- Croc
- Člen | 270
Tak se zeptám jinak. Když definuju připojení v config.neon, jak potom k němu přistupuji?
V config.neon mám:
php:
date.timezone: Europe/Prague
nette:
database:
default:
dsn: "xxx;dbname=xxx"
user: "xxx"
password: "xxx"
options: [PDO::MYSQL_ATTR_COMPRESS = true]
debugger: true # panel v debugger baru
explain: true # explain dotazů v debugger bar
reflection: discovered # nebo conventional nebo classname, výchozí je discovered
autowired: true
services:
database: @nette.database.default
Jak potom tedy volám dané připojení do DB?
- David Matějka
- Moderator | 6445
Pokud chces nette vyuzivat zcela (di, mvc…), tak zacni quickstartem
Pokud jen DI, tak si do sveho projektu pridej soubor podobny bootstrapu. Ten si pak requirnes jako tady a vytahnes s z toho potrebnou sluzbu.
- Croc
- Člen | 270
Díky, řekl bych že mám uvedený soubor (bootstrap) implementovaný. Akorát teď nevím jakou službu mám zavolat (co použít místo application) pro připojení k databázi:
config.neon:
php:
date.timezone: Europe/Prague
nette:
database:
default:
dsn: "xxx;dbname=xxx"
user: "xxx"
password: "xxx"
options: [PDO::MYSQL_ATTR_COMPRESS = true]
debugger: true # panel v debugger baru
explain: true # explain dotazů v debugger bar
reflection: discovered # nebo conventional nebo classname, výchozí je discovered
autowired: true
services:
database: @nette.database.default
bootstrap.php
<?php
require __DIR__ . '/../vendor/autoload.php';
$configurator = new Nette\Configurator;
$configurator->setDebugMode('XXX'); // enable for your remote IP
$configurator->enableDebugger(__DIR__ . '/../log');
$configurator->setTempDirectory(__DIR__ . '/../temp');
$configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
$configurator->addConfig(__DIR__ . '/config/config.neon');
$configurator->addConfig(__DIR__ . '/config/config.local.neon');
$container = $configurator->createContainer();
return $container;
index.php obsahuje:
use Tracy\Debugger,
Tracy\Dumper,
Nette\Database\Connection,
Nette\Database\Context;
$container = require __DIR__ . '/../app/bootstrap.php';
Debugger::enable(Debugger::DEVELOPMENT);
soubor s funkcemi include do index.php:
use Nette\Database\Connection,
Nette\Database\Context;
class States {
public function loadStates()
{
$connection = new Connection(database);
$database = new Context($connection);
$stmt = $database->table('stat');
foreach ($stmt as $state) {
$stat[$state->id_stat] = $state->nazev;
}
return $stat;
}
}
Hlásí mi to chybu: Use of undefined constant database – assumed ‚database‘.
Bohůžel nevím jestli to mám správně nastavené či jestli špatně volám to připojení ve funkci loadStates
Editoval Croc (3. 1. 2015 14:32)
- Croc
- Člen | 270
Když dám:
$connection = new Connection();
$database = new Context($connection);
Dostanu chybu: Missing argument 1 for Nette\Database\Connection::__construct(), což dává smysl. Jak tedy našíst ty údaje z config.neon? Měl jsem za to že se načítají automaticky pokud nedefinuju jiné připojení. Bohužel tomu tak není :(
- trejjam
- Backer | 65
Ono jde o to, že pokud používáš DI, tak nevytváříš služby ručně (žádné tebou volané new – krom vytvoření konfiguratoru)
require __DIR__ . "/vendor/autoload.php";
$configurator = new Nette\Configurator; //jediné new které bys měl potřebovat
define("APP_DIR", __DIR__ . "/app");
$configurator->createRobotLoader()
->addDirectory(APP_DIR)
->register();
$configurator->addConfig(APP_DIR . '/config/config.neon');
$container = $configurator->createContainer();
$database = $container->getService('nette.database.default.context');
Ale na takovýto přístup se vykašli a podívej se na sandbox a MVC . Potom můžeš řešit závislosti přes konstruktor, kde se díky DI vyřeší samy.
- Croc
- Člen | 270
MVC používat nebudu a ani nemůžu :)
Díky, tohle mi trochu pomohlo, doplnil jsem tyto 2 řádky (bootstrap mám již implementovaný):
$container = $configurator->createContainer();
$database = $container->getService('nette.database.default.context');
A pokud dám $database jako vstupní parametr dané funkce, připojení funguje. Ale to je problém, protože bych musel u každé funkce mít 1 vstupní parametr právě proměnou $database.
Vím že jsem asi natvrdlej, ale nějak nechápu použití DI v tomto případě.
Několikrát jsem prošel DI a Konfiguraci , ale není mi pořád jasné, který příklad by se dal použít (jestli vůbec) na můj případ…
Editoval Croc (4. 1. 2015 1:00)
- Mysteria
- Člen | 797
Kdyby to bylo celé Nette, tak bych do config.neon přidal do services toto:
services:
database: @nette.database.default
- States
A tvoje třída States by pak se stejnou funkčností jak píšeš nahoře vypadala takto:
class States {
/** @var \Nette\Database\Context */
private $database;
public function __construct(\Nette\Database\Context $database) {
$this->database = $database;
}
public function loadStates() {
return $this->database->table('stat')->fetchPairs('id_stat', 'nazev');
}
}
A pak použití:
$states = $container->getByType('States');
var_dump($states->loadStates());
Ale nikdy jsem nepoužíval rozkouskované Nette, takže netuším jestli to tak, jak to teď máš, bude fungovat (můžeš zkusit).
Ještě by mě zajímalo (pokud to není tajné) proč nemůžeš (nebo nechceš) použít MVC?
Editoval Mysteria (4. 1. 2015 10:59)
- Croc
- Člen | 270
Zkusi jsem tvůj příklad a dostávám chybu:
Class States used in service '23_States' not found or is not instantiable.
Aplikaci mám už nějakou dobu rozdělanou a částečně fungující, ale aktuálně ji dost předělávám. Z Nette jsem chtěl původně použít jen formuláře (ty už mám) a zpracování obrázků (ještě nemám). Ale docela se mi i líbí práce s databází, tak jsem chtěl zkusit jak to rozchodit bez MVC… Ježí se mi vlasy při představě, že bych celou aplikaci předělávál do MVC, ještě když se Nette teprve učím a poznávám…
Navíc mám integrované uživatelské fórum SMF, ze kterého využívám registraci a přihlašování…
Proto to potřebuji rozchodit bez MVC…
Jediný, co mi zatím fungovalo:
Na začátku indexu:
$container = $configurator->createContainer();
$database = $container->getService('nette.database.default.context');
funkce:
class States {
public function loadStates($database)
{
$stmt = $database->table('stat');
foreach ($stmt as $state) {
$stat[$state->id_stat] = $state->nazev;
}
return $stat;
}
}
volání funkce v indexu:
$staty = new States();
$staty->loadStates($database);
Má uvedené řešení nějaké vážné nevýhody? Například se spojením k db, atd?
Editoval Croc (4. 1. 2015 13:52)
- Šaman
- Člen | 2666
Začínás na Sandboxu? Tak ukázku předání databáze do modelu mášv UserManageru
Podobnou ukázku mám u sebe, databázi předávám jen do abstraktního repozitáře
a všechny další repozitáře
pro jednotlivé entity dědí od něho. Tenhle postup doporučuji.
Do presenteru bys neměl mít potřebu předávat databázi, ale když už, tak to uděláš pomocí inject metody, nebo anotace @inject.
Jo a vytváři se samozřejmě v configu. To už ale nesouvisí s dadabází, ale DI a obecnou ideou (nejen) Nette aplikací.
- LuBoss
- Člen | 21
Ano, používám Sandbox. V configu mám definováno připojení k databázi a snažil jsem se vytvořit službu, která by vracela připojení k DB. Podařilo se mi dosáhnout toho, že když tuto službu volám z presenteru, tak se jí „přičarují“ do konstruktoru údaje pro připojení k DB a vrátí mi DBconnection. Toto obdržené spojení pak z presenteru předávám do konstruktorů všech použitých modelů. Je to nešikovné, zbytečné a určitě špatně. Zkrátka jsem další z řady začátečníků, kterým chybí jednoduché funkční příklady s vysvětlením souvislostí.
Editoval LuBoss (7. 1. 2015 19:51)
- Mysteria
- Člen | 797
@LuBoss: Mrkni se sem, podobné věci se tu už
několikrát řešili a napsal jsem tam takové shrnutí, kde by to mělo být
snad snadno pochopitelné. Jinak samozřejmě si můžeš to téma přečíst
celé, je to zajímavé. :)
https://forum.nette.org/…senterom-atd#…
Samozřejmě co se týká toho příkladu, tak aby jsi u každýho modelu nemusel mít ten konstruktor, tak si můžeš udělat „BaseRepository“…
class BaseRepository extends \Nette\Object {
/** @var \Nette\Database\Context $database */
protected $database;
public function __constructor(\Nette\Database\Context $database) {
$this->database = $database;
}
}
class MyRepository extends BaseRepository {
public function getAllRows() {
return $this->database->table('someTable');
}
}
Editoval Mysteria (7. 1. 2015 21:17)
- LuBoss
- Člen | 21
No jo, dědičnost je dobrý nápad.
Mám otázky:
- Jak se do třídy BaseRepository „načaruje“ připojení k DB definované v config.neon?
To se udělá „samo“ jenom na základě anotace?
- Jak se takový model zavolá z prezenteru?
Nedává přeci smysl napsat $b = new BaseRepository($database)
protože bych musel znát $database
- Pavel Kravčík
- Člen | 1196
Pochopit mi to hodně pomohlo tohle: http://nette.matej21.cz/di.pdf
Pfff… než to člověk dopíše už je tu Clark Kent 21.
Editoval kzk_cz (8. 1. 2015 14:08)
- Šaman
- Člen | 2666
Vždyť jsem ti posílal ukázku, jak jsi chtěl.
- vytvoření
připojení k databázi v
config.local.neon
(můžeš i v hlavním configu, až budeš pracovat nad projektem z více počítačů (třeba vývoj doma a produkce na hostingu), tak pochopíš, proč se použivá local.neon) - injectování do modelu (abstraktní repozitář). Tenhle klidně použij jako odrazový můstek pro první modely. Konvence je taková, že get metody vrací přímo ActiveRow (konkrétní záznam), find metody vrací Selection (dá se nad nimi dál třeba řadit, nebo ještě přidat podmínky)
- konkrétní
repozitář pro danou entitu (u mě se používají metody abstraktního
repozitáře, ale není problém použít rovnou
$this->database->…
) Sem píšeš všechny operace nad databází, které týkají této entity. - připojení
modelu k presenteru. Tohle je maličko magie, ale dobře zdokumentovaná.
Presenter je vytvořený DI kontejnerem (tím samým, který podle údajů
z configu vytvořil databázi a který vytvořil i instance tříd modelu).
A tenhle kontejner, když vytvoří třídu presenteru, tak projde anotace a
pokud je nad public property anotace @inject a pokud tam
je i typ proměnné, tak z DI kontejneru zkusí dosadit do
property instanci třídy daného typu. U nás tedy
BookRepository
(nezapoeň v sekci use uvést namespace, nebo používat plné namespace i s lomítkem na začátku). - použití. Metoda findAll() je popsaná v abstraktním repozitáři. V presenteru bys už neměl pracovat s databází, ale s třídami modelu. Pokud potřebuješ nějakou speciální query, vytvoříš na ni metodu v konkrétním repository.
- LuBoss
- Člen | 21
Velice děkuji učiteli Šamanovi a přidám svou začátečnickou ukázku na
téma
„Jak použít model s MySQL databází za pomoci DI“.
Komentáře v kódu asi nejsou zcela správné a náležitě vědecké, ale
jedná se o funkční příklad o základech automatického předávání
parametrů do třídy (modelu) tak jak jsem jej já pochopil. Příklad je
vytvořen pouhou úpravou čtyř souborů z holého Nette sandboxu. Použil
jsem sandbox z verze Nette 2.2.7, ale to mělo být nepodstatné. Tři soubory
jsou upraveny a soubor „DbTestModel.php“ je vytvořen ve složce model. Je
nutno mít také nějakou funkční MySQL databázi a v ní nějakou
existující tabulky s daty, která si necháme vypsat na důkaz funkčnosti.
Podle toho poté upravit v config.local.neon údaje pro přístup k databázi
(server, databáze, login a heslo) a dále v souboru DbTestModel upravit SQL
dotaz podle toho, jaká tabulka je k dispozici.
config.neon
parameters:
php:
date.timezone: Europe/Prague
nette:
application:
errorPresenter: Error
mapping:
*: App\*Module\Presenters\*Presenter
session:
expiration: 14 days
services:
# třída s modelem definovaná jako služba
# Spojení k databázi se dosadí automaticky do konstruktoru třídy díky autowiringu,
# ale musí se pracovat (definováno v config.local.neon) pouze s jednou databází
# Pokud bychom jich nadefinovali víc, bralo by Nette jako defaultní tu první
- App\Model\DbTestModel
router: App\RouterFactory::createRouter
config.local.neon
parameters:
database:
# Definování připojení k testovací MySQL databázi
# host=adresa serveru s MySQl
# dbname=jméno databáze
# user a password samozřejmě jméno a heslo k MySQL
default:
dsn: 'mysql:host=localhost;dbname=test'
user: 'test'
password: 'test'
options:
lazy: yes
DbTestModel.php
<?php
namespace App\Model;
use Nette;
/**
* Třída (model) pro testování připojení k MySQL
*/
class DbTestModel extends Nette\Object
{
/** @var Nette\Database\Context */
private $dbContext;
// Do této proměnné se automaticky "načaruje" připojení k databázi, které je difonáno v config.neon, nebo moderněji v config.local.neon
// Toto se stane pomocí konstruktoru tak, že při vytváření třídy systémvý kontejner automaticky předá i spojení k DB
// Speciální komentář nad proměnnou říká, co má Nette kontejner v config souboru najít a dosadit do této proměnné
// Třída DbTestModel musí být uvedena jako služba v config.neon (řádek - App\Model\DbTestModel)
// Konstruktor třídy.
// Ten se ale NEvolá manuálně v presenteru v podobě ...$nějaká_proměnná = new DbTestModel($nějaké_připojení);
// Jeho zavolání v presenteru provede automaticky systémový kontejner a tuto třídu tím "přičaruje" do globální proměnné presenteru - public $dbTestModel;
// Zároveň se na základě uvedení této třídy jako služby v config.neon (řádek - App\Model\DbTestModel) při jejím automatickém vytváření
// doplní parametr $dbContext
// Nette ví, že má hledat v configu něco co je typ Nette\Database\Context a najde v config.local.neon položku "database.default"
// Název spojení "default" nedůležitý, protože se hledá shoda typů a nikoliv nějakých jmen
public function __construct(Nette\Database\Context $dbContext)
{
$this->dbContext = $dbContext;
}
// Nějaká funkce, kterou si vymyslíme, pak zkusíme zavolat a tím zjistit, že vše funguje
public function showInfo()
{
//Samozřejmě nejlepší důkaz funkčnosti je zkusit vybrat něco konkrétního pomocí SQL dotazu
//z nějaké existující tabulky v MySQL, ať je vidět že to něco dělá
$this->dbContext->query("SELECT * FROM table1")->dump(); //table1 = tabulka v databázi kterou jsem si vytvořil a vložil do ní pár libovolných řádků s daty
}
}
HomepagePresenter.php
<?php
namespace App\Presenters;
use Nette,
App\Model,
App\Model\DbTestModel; //přidat cestu k níže používanému modelu
class HomepagePresenter extends BasePresenter
{
/**
* @var DbTestModel
* @inject
*/
public $dbTestModel;
// Do této veřejné proměnné $dbTestModel se nainjektuje ("přičaruje") třída, které je uvedena výše ve speciálním komentáři
// Aby to ale fungovalo, tak musí být třída DbTestModel uvedena i jako služba (services) v config.neon (řádek "- App\Model\DbTestModel" v sekci services:)
// V config.neon musí být tedy uvedeny všechny třídy, které se mají v nějakém presenteru použít (automaticky injektovat)
// Sama třída (model) požaduje ke své práci připojení k MySQL databázi a to jí kontejner nette dodá automaticky
// do jejího konstruktoru, když ji vytváří. Více info přímo v nápovědě modelu.
public function renderDefault()
{
$this->dbTestModel->showInfo(); //zavolat nějakou existující funkci ve třídě modelu pro ověření funkčnosti
$this->terminate(); //předchozí info nám pro test stačí, takže ukončit presenter a už nevykreslovat šablonu (view)
}
}
V žádném případě neříkám, že tomu úplně přesně rozumím, ale snad to někomu alespoň trochu pomůže pochopit DI + MySQL, tedy předávání závislostí a připojení k databázi pomocí systémového kontejneru, což je pro začátečníka fakt šmígrus ;-)
- Šaman
- Člen | 2666
Pěkný souhrn. Jen dodám, že předávání závislostí konstruktorem funguje vždy a je to nejčistší způsob předání povinné závislosti (bez té závislosti ani nevytvořiš instanci závislého objektu). Tedy i u presenterů. Klidně to používej u koncových presenterů, anotace @inject se používá v ukázkách hlavně proto, že je kratší. Ale určená je primárně do abstraktních presenterů. Tady je pěkný článek, který ti taky možná ještě něco vysvětlí (anotace @inject vznikla v konečném důsledku toho článku).
- LuBoss
- Člen | 21
Taps:
A jaké používáš Nette? Ideální je stáhnout si aktuální verzi, což je
nyní 2.2.7
Nette
2.2.7.zip
Z celého balíčku Nette, který takto stáhneš tě zajímá v tuto chvíli jenom a pouze adresář „sandbox“ (nebo česky pískoviště), který uvnitř najdeš. Ten si zkopíruješ na svůj pokusný PHP server a hraješ si s ním. Vše ostatní zatím necháš ležet ladem.