Vytvoření \Nette\Database\Context v předkovi

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

Dobrý den,
koukám na tutoriál na devbooku a použil jsem třídu Base:
http://www.itnetwork.cz/…ework-modely
ta je potomkem mého modelu, problém je ale s tím že se mi neinicializuje proměnná $database.
můj potomek vypadá nějak takto:

class Pokus extends Base{
   public function __construct($neco, $neco1){

   }

   public function ulozDoDb(){
      $this->database->query("INSERT INTO...");
   }
}

v config.neon mám tohle:
base: App\Model\Base

Díky za radu

qteck
Člen | 164
+
0
-

bych řekl že to bude tím konstruktorem?

bluray
Člen | 178
+
0
-

No musel bych tady do konstruktoru dávat Context že jo? bez toho to nejde? to mi přijde dost svazující, když mám vždycky předepsáno jak bude konstruktor vypadat. a pak i ta třída Base ztrácí skoro smysl. můžu si proměnnou database přenést sem…

qteck
Člen | 164
+
0
-

Tak jde spíš o to, že když tam napíšeš novej kontruktor tak ten rodičovskej přepíšeš, takže by si měl volat parent:__construct().

Měl jsem s tím podobné obtíže jako ty a nakonec jsem to vyřešil tak, že jsem se tomu vyhnul a konstruktory v dceřiných (nebo jak se to nazývá) třídách nepíšu.

ale mě moc neber za slovo, začínám.

bluray
Člen | 178
+
0
-

No parent::__construct() je jasné že tam napíšu. ale on chce Context. takže stejně budu muset v každém potomkovi vytvořit Context. Já bych byl rád, abych ho měl v potomcích vždy už připraven a nemusel jsem ho tam vytvářet..

mystik
Člen | 313
+
0
-

Context by sis neměl vytvářet, ale předávat si ho jako parametr konstruktoru i do potomků.

bluray
Člen | 178
+
0
-

No jo, ale to potom toho potomka musím uvést v config.neon že jo? a tím pádem už nemůže mít jiné parametry…

Šaman
Člen | 2666
+
0
-

Jak to, že by nemohl mít jiné parametry? V první řadě si ujasni, jestli chceš třidu pokus vytvářet ručně operátorem new (u služeb k tomu většinou není důvod), nebo používat jako službu v DI kontejneru.
Pokud ručně, tak si ten DbContext injectni do presenteru (nebo kde ji budeš vytvářet) a každému potomkovi ho předáš.
Pokud jako skužbu, kterou si budeš vytvářet v DI kontejneru, tak samozřejmě můžeš v potomcích parametry přidávat.

<?php
class Base
{
   protected $db;

   public function __construct(NS\Context $db)
   {
      $this->db = $db;
   }
}

class FooBar extends Base
{
   public function __construct(NS\Context $db, NS\JinaSluzba $jinaSluzba)
   {
      parent::__construct($db);
      …
   }
}
?>

Editoval Šaman (1. 6. 2014 19:31)

bluray
Člen | 178
+
0
-

No tuhle třídu budu vytvářet pomocí faktory v jiné třídě. ale to první řešení v presenteru je rozumné, už tomu pomalu začínám rozumět. Ale moc nerozumím tomu s DI kontejnerem, jak to bude fungovat? můžeš mi to prosím trochu rozepsat? v potomcích nechci předávat jiné služby, ale i nějaké proměnné.
Já se omlouvám, možná jsem trochu natvrdlý ale je to pro mě nové a přijde mi že je to dost komplikovaný. Navíc to člověku dost svazuje ruce s nějakým objektovým návrhem, s návrhovými vzory, když musím předávat třídy frameworku…
Nevidím v tom žádnou výhodu.

Šaman
Člen | 2666
+
0
-

Jestli ji budeš vytvářet ručně, tak té tovární třídě musíš injectovat Context a pak už si ho předávat.
Nikdo tě nenutí vytvářet služby v kontejneru, ale ten pomáhá právě s tím předáváním závislostí.

Samozřejmě, že pokud vytváříš nějaké instance které nejsou služby, tak to neděláš přes kontejner, ale služba bude ta tovární třída.

Oli
Člen | 1215
+
0
-

bluray: On ten OOP přístup má tu výhodu, že by to mělo „přiblížit“ programování reálný situaci. Je to jinej pohled na problém. Ty návrhový vzory nemusíš znát. Pravdou ale je, že návrhový vzor je best practice, neco co se pro daný problém ustálilo jako optimální řešení, proto, pokud to myslíš vážně se jim asi stejně nevyhneš. Alespoň nějakých 5 vzorů budeš postupně využívat ;-)

Jednu z největších praktických výhod vidím v oddělení zodpovědností a závislostí. Například, když máš vypsat data uživatele, tak víš, že se o to stará třída UserManager. Ta třída má závislost (kterou zpravidla najdeš v konstruktoru) např na UserRepository, která tahá ty data z databáze. Jsou to oddělené zodpovědnosti a pokud máš takových objektů (jako je User) třeba 50, tak to potom hodně zpřehlední.

Chci pracovat s komentářema pod článkem, otevřu si třídu CommentsManager, která se o to stará. Potřebuju dostat komentáře z databáze: vlezu do CommentsRepository, protože vím, že to je můj „přístupový bod“ do databáze…

Doufám, že nepopisuju kolo, ale podle tvého příspěvku mě přišlo, že právě tohle ti není uplně jasné. Jestli jsem to blbě pochopil, tak se omlouvám za OT :-)

bluray
Člen | 178
+
0
-

Pochopil jsi to správně. návrhové vzory používám běžně, jsem zvyklý na nějakou strukturu modelů, ale tady díky předávání toho Contextu mi to přišlo omezující.
Ale to že oddělím přístupy do databáze a logiku je docela dobrý nápad.
Děkuji za radu.

Džůny
Člen | 19
+
0
-

Uf, no že to dalo práce, najít téma, ve kterém se řeší můj problém (místo zakládání nového). :) Tušil jsem, že tohle už někdo prostě řešit musel. Takže, jen abych se ujistil, vážně není jiná možnost, než předávat Context i v konstruktorech potomků? Můj problém ve zkratce:

Mám v modelu abstraktní třídu Base, která obsahuje klasické získání závislosti přes konstruktor (DB):

namespace App\Model;

use Nette;

abstract class Base extends Nette\Object
{
    /** @var Nette\Database\Context */
    public $database;

    public function __construct(Nette\Database\Context $database)
    {
		$this->database = $database;
    }
	…

A nějaké obecné metody (selectById(), selectAll(), update(), delete() a tak dále). Od této třídy odvozuji další součásti (třídy) patřící do modelu. Co když chci v potomkovi další závislosti? Musím to řešit nějak takhle:

public function __construct(Nette\Database\Context $database, Cities $cities, Nette\Http\Request $httpRequest)
{
		parent::__construct($database);
		$this->cities = $cities;
		$this->httpRequest = $httpRequest;
}

Zkrátka, musím znovu předávat Context. Připadá mi to takové hloupé. Jak už psal někdo nahoře: jedním z důvodů proč nějakou třídu Base mám, bylo právě to, abych nemusel v každé nové třídě modelu znovu do konstruktoru vypisovat Context. Ale co já vím, jak by řekl Homer: Jsem tady první den. :)

A na závěr otázka, proč mi v modelu nefunguje

@inject

? Co jsem kde přehlídnul?

Díky všem za odpovědi a čas.

Edit: A teď mi ještě došlo, že i tak mám smůlu, pokud nechám Base jako abstraktní, protože bez instance se konstruktor volá těžko, žejo. :D

Editoval Džůny (10. 5. 2015 14:52)

dyamon
Člen | 11
+
+2
-

Polovina tvojí otázky, ale vůbec nesouvisí s nette. Přepisování konstruktoru je věcí objektového návrhu PHP a je to logické. Při vytváření potomka voláš konstruktor potomka, takže pokud chceš předat závislosti i jeho bázové třídě (skrz konstruktor) musí je vyžadovat i jeho vlastní konstruktor.

Co se týká anotace "@inject" tak mám dojem, že je její funkčnost omezená pouze na presentery… rozhodně by se nikde jinde používat neměla.

David Matějka
Moderator | 6445
+
+1
-

Jak pise @dyamon, jen doplnim, ze to inject muzes zapnout i jinde prepsanim:

services:
	- MyModel

na

services:
	-
		class: MyModel
		inject true
#respektive zkracene na
	- {class: MyModel, inject: true}

Od 2.3 pak toto muzes zapnout treba pro vsechny potomky Base modelu:

decorator:
	App\Model\Base:
		inject: true

Ale pouzivani inject properties neni mimo presentery doporucovano

Šaman
Člen | 2666
+
+3
-

Nedoporučuji zapínat inject, ale můžeš si rovnou do všech potomků nastavit databázi, třeba pomocí setteru, takže ti konstruktor zůstane volný. Ukázku máš tady.
P.S. Jen pro pořádek připomínám, že se to provede u všech potomků, které vytváříš v DI kontejneru (tedy jsou zapsané v configu).

Editoval Šaman (11. 5. 2015 16:44)

Azathoth
Člen | 495
+
+1
-

Mohl bych se zeptat, jak moc je používání takovéhle abstraktní base třídy pro model používané? Přijde mi to jako docela antipattern. Jsem v tom sám nebo to tak připadá více lidem?

Šaman
Člen | 2666
+
0
-

Naopak, jen se tomu často neříká model (tím je celá část aplikace), ale třeba Repository

Azathoth
Člen | 495
+
0
-

sice je pravda, že insert a findById mám v každé repository třídě…ale proboha, když mám objekty, tak tohle neřéká, jaký objekt mi to z té databáze vrací…

Šaman
Člen | 2666
+
0
-

Nikoliv, neříká. To taky nebylo v zadání a tenhle projekt ani žádné objektové entity nezná. Je to jen ukázka primitivního repozitáře nad NDbT.

Jestli chceš používat ORM, tak tak tam už nějaké base třídy máš, ne? Tam už přece nepředáváš ručně databázi každé entitě, nebo repository/DAO.

Džůny
Člen | 19
+
0
-

@dyamon To je mi jasné. Chtěl jsem se dozvědět, jestli není možnost hodit tuhle práci na Nette (DI Container) a nějak to zautomatizovat (jinak než pomocí @inject, když to není doporučováno a defaultně povoleno).

@DavidMatějka, @Šaman díky za informace a nabídnutí možností.

Ještě se teda zeptám, proč @inject není dobré používat mimo presentery? Proč je to u BasePresenter(u) OK a v modelu ne?

@Azathoth Nejsi sám, ono se to tady dost řeší, co jsem tak zběžně koukal. Ale viděl jsem to v několika projektech takhle, hádám, že je to zkrátka nejsnazší a nejjednodušší. Když se nad tím člověk zamyslí (a nad alternativama), tak mi to připadá prašť jak uhoď…

Šaman
Člen | 2666
+
+1
-

Proč nepoužívat inject mimo presentery?

  1. krátká verze: protože funguje jen v presenterech (lze obejít, ale v základu tomu tak je)
  2. a proč je to povolené jen v presenterech je dlouhá verze. (Už je to staré vlákno, ale princip je stejný. Dnes je další důvod ten, že anotace fungují jen na public property, tedy porušují zapouzdření.)
Azathoth
Člen | 495
+
0
-

a co takhle používat setterovou inkjekci? To by problém nevyřešilo?

Šaman
Člen | 2666
+
0
-

Však přesně to jsem radil.

Azathoth
Člen | 495
+
0
-

Aha, pardon, už to vidím, jsem slepý.

Džůny
Člen | 19
+
0
-

@Šaman Ok, ještě jednou díky za informace a za rady.