Základní práce s databází

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Croc
Člen | 270
+
0
-

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)

trejjam
Backer | 65
+
0
-

Zkus nám ukázet co máš, takto nikdo neví kde je zakopaný pes :)

Croc
Člen | 270
+
0
-

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)

trejjam
Backer | 65
+
0
-

V Tvém kódu nepracuješ s výsledkem query, ale s připojením:

	$stms=$database->query("SELECT * FROM stat");
	$row= $stms->fetch();

	var_dump((array)$row);

FetchAll vrátí:

[
	$id=>$row,
	...
]
Croc
Člen | 270
+
0
-

OMG, nechápu jak jsem to mohl přehlídnout… Díky moc

Croc
Člen | 270
+
0
-

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
+
0
-

pomoci di, prikladem je treba UserManager v sandboxu, nezapomen to registrovat jako sluzbu

Croc
Člen | 270
+
0
-

Díky za rychlou odpověď, ale nevím jestli to je to pravé. Nepoužívám MVC strukturu ani config.neon.

matopeto
Člen | 395
+
+3
-

Tak by si mal zacat

Croc
Člen | 270
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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)

Croc
Člen | 270
+
0
-

Tak jsem si trochu víc početl o MVC a popřemýšlel. Zkusím vytvořit jednoduchou stránku v MVC s integrovaným SMF. Jestli se SMF podaří plně integrovat, tak do MVC půjdu :)

LuBoss
Člen | 21
+
0
-

Je prosím možné někde najít jednoduchý příklad typického použití a konfigurace presenteru + modelu + mysql?
Jde mi o vhodný způsob vytvoření a předávání databázového připojení do presenteru a do vlastních tříd (modelů).

Šaman
Člen | 2666
+
0
-

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
+
0
-

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
+
0
-

@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
+
0
-

No jo, dědičnost je dobrý nápad.

Mám otázky:

  1. 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?

  1. 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
+
0
-
  1. https://doc.nette.org/…introduction
  2. https://doc.nette.org/…dependencies

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
+
+1
-

Vždyť jsem ti posílal ukázku, jak jsi chtěl.

  1. 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)
  2. 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)
  3. 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.
  4. 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).
  5. 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
+
+3
-

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
+
+1
-

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).

Taps
Člen | 168
+
0
-

luboss
Díky za návod, bohužel se mi však vypisuje

Cannot load presenter 'Error', class 'ErrorPresenter'

Presenter Error existuje a rovněž jsem do něj umístil namespace App\Presenters;

Můžeš mi poradit, kde by mohl být problém. Děkuji

LuBoss
Člen | 21
+
0
-

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.