Geneované továrničky, proč?

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

Ahoj,

už nějaký pátek píšu v nette, a většina věcí už je mi jasných, jediná věc která mi vůbec není jasná jsou továrničky na komponenty nebo celkově interfaces..

Zrovna píšu komponentu a přemýšlím nad předáním závislostí. Už asi 20× jsem četl různé best practises atp. Ale furt nechápu jako k čemu nebo v čem je to dorbý..

Dejme tomu že mám komponentu a potřebuju jí předat db connect tak jednoduše

class SomeControl extends UI\Control
{
    private $database;

    public function __construct(Nette\Database\Connection $database)
    {
        parent::__construct();
        $this->database = $database;
    }
}

A teď dejme tomu ikdyž se to asi nedělá si tu komponentu přidám jako službu do configu a injectnu v presenteru. To je celý. K čemu mi teda jsou nějaký továrničky ?

Díky za vysvětlení.

Editoval kleinpetr (24. 4. 2015 13:01)

David Matějka
Moderator | 6445
+
+4
-

kratce: komponenty nejsou sluzby

prakticky:

  • pomoci tovarny snadno predas parametr do konstruktoru
  • komponenta muze byt pripojena pouze jednou, to znamena, ze ji nemuzes pouzit 2× behem jednoho pozadavku – a to i kdyby treba doslo k internimu forwardu (treba na 404, kde tu komponentu chces take pouzit). Tim, ze pouzijes tovarnu, se vzdy vytvori nova instance komponenty.
akadlec
Člen | 1326
+
0
-

Aby sis tu komponentu mohl vygenerovat tolikrát kolikrát potřebuješ. Např. komponenta co ti dělá přihlašovací form. Chceš mít na stránce dva, jeden do modálního okna na otevření tlačítkem a druhou přímo ve stránce. Takže si pomoci továrničky udělat tu komponentu 2× ale pokaždé s jinými parametry. Nemusíš pak psát new JmenoComponenty(zavislosti,…) ale jen $factory->create()

kleinpetr
Člen | 480
+
0
-

Jo tak, takže vlastně vždy když píšu nějakou komponentu, tak bych jí měl napsat i továrnu, abych ji nemusel volat třeba přes new some().. a kde se obvykle píše ten interface ? normálně v souboru s komponentou nebo je to jedno ?

David Matějka
Moderator | 6445
+
0
-

Ja ten iface pisu vzdy do samostatneho souboru, ale prakticky je to jedno, hlavne aby to nasel nejaky loader (a kdyz to bude v app, tak to robot loader najde). Ale prijde mi prehlednejsi to davat do samotneho souboru, uz jen dle letmeho pohledu do adresarovy struktury vidim, ze to ma napsanou tovarnu

kleinpetr
Člen | 480
+
0
-

Ok tak já si s tím jdu nějak pohrát :) mě to totiž zmátlo, protože celkově jsem ifaces nikdy nepsal a pořádně nevim k čemu jsou, jako četl jsem, že jen deklarujou public fce daný třídy a dá se podle něho zjistit instanceof() nějakýho objektu, ale jinak v nich nevidím nic tak zázračnýho, a tomhle případě použití je to takový matoucí ;) každopádně díky za pomoc :)

David Matějka
Moderator | 6445
+
0
-

interface obecne jsou takove predpisy, jak ta trida ma vypadat, respektive co ma umet. Takze kdyz nekde vyzadujes interface, je ti jedno, jak je ta trida implementovana – jestli treba zapisuje na disk nebo do databaze, tebe zajima pouze to, aby se chovala, jak rika ten interface.

V pripade generovanych tovarnicek vytvoris interface a nette dle toho vygeneruje implementaci. Jak takova implementace vypada, muzes najit ve vygenerovanem DI kontejneru na konci souboru. (temp/cache/Nette.Configurator/…php)

kleinpetr
Člen | 480
+
0
-

Jo takhle, takže se to spíše používá u větších projektů na kterých pracuje více lidí. Aby věděli jaký fce můžou používat atd.. A tedy když nad tím tak přemýšlím a vytvořím si nějakou třídu s interfacem.

interface SomeClassInterFace
{
	public function hello();
}

class SomeClass implements SomeClassInterFace
{
	public function hello($world){
		echo 'Hello '.$world;
	}
}

Takže v tomhle případě když smažu v tý tříde SomeClass fci hello() tak hodí chybu.

A druhá věc, já si tedy nyní mohu vytvořit objekt pouze z toho IF ?

$some = new SomeClassInterFace();
$some->hello('world');

??

Michal Vyšinský
Člen | 608
+
0
-

Možná by chtělo si něco přečíst o interfacech: https://php.net/…terfaces.php

David Matějka
Moderator | 6445
+
+1
-

Prave ten objekt nevytvarej – ty nekde vyzadujes SomeClassInterFace (treba v konstruktoru, nebo jako parametr metody) a o jeho vytvoreni (tzn vyber, ktera implementace se pouzije, jak se nakonfiguruje atd.) se postara nekdo jiny.

Hezkym prikladem ze sveta nette je cache storage. Vsude, kde chces pracovat s cache, vyzadujes interface IStorage a je ti jedno, jestli dostanes FileStorage, RedisStorage nebo WhateverStorage. A o to predani spravneho storage se postara DI kontejner tim, ze konkretni storage zaregistrujes v config.neon

Šaman
Člen | 2640
+
+2
-

Nebo Mailer. Takhle vypadá můj testovací mailer. Ale protože používám rozhraní, mohu si ho v configu vytvořit (v tomto případě jako službu) a každá komponenta, nebo presenter, který si po kontejneru vyžádá instanci IMailer, tak dostane můj BarDumpMailer. A na ostrén serveru se místo toho nakonfiguruje třeba Nettí SmtpMailer. Tolik k používání rozhraní. Teoreticky by stejnou službu udělala i nějaká BaseClass, od které by všichni dědili, ale tam pak vzniká často obtěžující závislost na té BaseClass. Např. v tomhle případě není potřeba.

Služby i komponnety se dnes v configu zapisují stejně a rozdíl je jen v tom, jak si je vyžádáš. Přímý inject ti vrátí vždy stejnou instanci služby. Třeba tem můj mailer bude jen jeden pro celou aplikaci.
Továrnička ti zajistí vytvoření nové instance. To se používá třeba pro komponenty (tam si zpravidla každá instance komponenty žije vlastním životem).

kleinpetr
Člen | 480
+
0
-

Moc vám děkuju za vyčerpávající odpovědi :) už mi to trochu dává smysl, nejlepší bude když si s tím teď budu hrát a uvidím.

kleinpetr
Člen | 480
+
0
-

Tak už chápu podstatu těch továrniček :). a snažím se je tedy zprovoznit nějak takhle:

namespace App\Components;

new MyControl extends UI\Control
{
.
.
}
namespace App\Factories;
use App\Components;

interface IMyControlFactory
{
	/** @return Components\MyControl */
	function create(); //tady už phpStorm řve
}

Ta komponenta teda musí obsahovat tu fci create() ?

akadlec
Člen | 1326
+
0
-

a pak tu máme třeba ještě abstraktní třídy…ale to jsou základy PHPka že ;)

akadlec
Člen | 1326
+
0
-

ano musí, to je podstate nettích továrniček, že mají jen jednu metodu create

David Matějka
Moderator | 6445
+
+1
-

Ne, ta komponenta nema implementovat ten interface a nema obsahovat metodu create. To jsou dve veci – komponenta a tovarna na ni (ktera je zde definovana pouze interfacem a implementaci vytvori nette)

(btw, zmenil jsem question na diskuzi, at je to prehlednejsi)

kleinpetr
Člen | 480
+
0
-

@akadlec ta abstrakce mi nedošla :) díky.
@DavidMatějka ok tak už se asi pomalu blížím do cíle ještě se mu sice něco nelíbí Class App\Factories\IMyControlFactory used in service 'myControl' not found or is not instantiable, ale to už bude asi někde moje chyba :) Ještě mě tak napadá, že mě stačí když si tu továrnu injectnu na basePresenteru a můžu si z ní kdykoliv vytvořit komponentu kdekoliv v aplikaci :) což je super!

Editoval kleinpetr (24. 4. 2015 15:03)

kleinpetr
Člen | 480
+
0
-

Tak jsem se zatím nepohnul, musí mít ta komponenta nějakou konkrétní fci ?

David Matějka
Moderator | 6445
+
0
-

ukaz neon. Kde mas ten interface ulozeny?

kleinpetr
Člen | 480
+
0
-

normálně v services

#Factories
	myControl: App\Factories\IMyControlFactory

když to udělám jen přes pomlčku

- App\Factories\IMyControlFactory

tak vyhodí
Class App\Factories\IMyControlFactory used in service '26_App_Factories_IMyControlFactory' not found or is not instantiable

Editoval kleinpetr (24. 4. 2015 15:32)

romiix.org
Člen | 343
+
0
-

To vyzerá na zlý namespace. Je 100% dobrý? Prípadne ti RobotLoader nenájde súbor. Premazal si cache?

Michal Vyšinský
Člen | 608
+
0
-

Ne ten namespace není správně. Podle toho cos sem dal (https://forum.nette.org/…arnicky-proc#…) to má být App\Components\IMyControlFactory

EDIT: sorry blbě jsem se podíval – pravděpodobně je potřeba smazat cache, nebo jestli nepoužíváš RobotLoader ale třeba composer tak dumnout autoload

Editoval Michal Vyšinský (24. 4. 2015 15:44)

David Matějka
Moderator | 6445
+
0
-

@MichalVyšinský u ty factory ma uveden namespace App\Factories

@kleinpetr jeste jsem se ptal, kde to mas ulozeny

kleinpetr
Člen | 480
+
0
-

@DavidMatějka cache jsem promazal a mam to ulozeny v App/factories/IMyControlFactory.php

@MichalVyšinský používám composer, to s tím má spojitost ?

Editoval kleinpetr (24. 4. 2015 16:16)

David Matějka
Moderator | 6445
+
0
-

Ale predpokladam, ze robot loader mas zapnuty ?

App/factories/IMyControlFactory.php to je predpokladam jen typo a je to ve slozce app stejne jako zbytek aplikace?

kleinpetr
Člen | 480
+
0
-

ano smozrejme je zapnuty, ano ta factory je ve slozce app/factories a nette 2.3.1

David Matějka
Moderator | 6445
+
0
-

Jak si psal „tady už phpStorm řve“, co tam phpstorm rve? ukaz jak presne ted vypada ten soubor s tovarnou?

kleinpetr
Člen | 480
+
0
-
<?php
/**
 * Created by PhpStorm.
 * User: kleinpetr
 * Date: 4/24/15
 * Time: 2:34 odp.
 */

namespace App\Factories;

use App\Components;

abstract class IMyControlFactory
{
    /** @return Components\MyControl */
    abstract function create();
}

řval kvůli tomu, že nebyl abstract

Editoval kleinpetr (24. 4. 2015 16:35)

David Matějka
Moderator | 6445
+
0
-

ma to byt interface, ne abstraktni trida…

kleinpetr
Člen | 480
+
0
-

já jsem vůl :D, to ani nebudu komentovat. Díky za trpělivost a celkově za vysvětlení

Šaman
Člen | 2640
+
+4
-

IMHO jsi začal tím složitějším, a to jak se taková továrnička napíše v Nette s využitím generovaných továrniček a DI containeru.

Základ v čistém PHP je:
Služba (service): vytvořím ji jen jednou a pak už pokaždé předávám tuto jednu instanci. (Např. připojení k databázi.) Kdysi se na to používal „návrhový vzor“ singleton. Nette má daleko lepší nástroj – DI kontejner, ve kterém se vytváří všechny třídy a ve kterém se pak skládají dohromady. Tento kontejner tedy vytvoří jedinou instanci služby a pak ji předá všem, kteří ji potřebují.

Továrna na třídy (factory): je to služba, která umí vytvářet nové instace. Kontejner ji tedy předá jako službu, ale v kódu už si nad touto službou zavoláš metodu create() a ta vytvoří novou instanci.

Nejjednodušší tovární třída bez vazby na nette by vypadala třeba takhle:

<?php
class MyControlFactory
{
    public function create()
    {
        return new MyControl;
    }
}
?>

Výhoda oproti použití operátoru new místo továrny je jen jedna, ale důležitá. Pokud bys potřeboval změnit způsob vytváření té komponenty, stačí upravit továrnu a nikoliv procházet celou aplikaci.
Takovou si můžeš udělat i v Nette a zaregistrovat jako službu a pak ji používat: $myControl = $this->myControlFactory->create();

Problém je, když potřebuješ předávat nějaké další závislosti. Všechny pak musíš injectovat do té továrny jen proto, aby je ta továrna zase předala té komponentě. Spousta rutinního kodu. Proto vznikly (už jako Nette specialitka) generované továrny, které umí Nette vytvořit samo z rozhraní, kterým popíšeš, co má ta továrna vracet. (Proto jsou všechny anotace povinné, podle nich se určí, co má továrna dělat.) Název metody create() je jen Nette konvence, kdyby sis to psal sám, klidně ji můžeš pojmenovat jinak.

A tohle celé nijak nesouvisí s kódem té komponenty MyControl! Celé je to jen o tom, jak vytvořit třeba v presenteru instanci komponenty co nejvýhodnějším způsobem (upravitelnost, testovatelnost, neskrývání závislostí, …). Ta komponenta vůbec neví, jestli použiješ tenhle aprát s metodou create(), nebo někde na prasáka zapíšeš $myControl = new MyControl;.

Editoval Šaman (24. 4. 2015 17:49)

kleinpetr
Člen | 480
+
0
-

Díky @Šaman už jsem to pochopil a uvědomuju si ty výhody a je to supr :) takže díky všem

ChocoTUx
Člen | 31
+
0
-

Ahoj,

prosím, když chci při vytváření komponenty přes továrnu předat nějakou jinou třídu. U mě spojení na databázi? Jde to nějak zařídit?

Aby to fungovalo nějak takhle, jak je v továrně co jsem si napsal ručně?

protected $analytics;
	protected $db;

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

	public function create()
	{
		$this->analytics = new \App\Model\Analytics($this->db);
		return $this->analytics;
	}
jiri.pudil
Nette Blogger | 1029
+
0
-

Prostě si v konstruktoru komponenty řekni o závislosti, jinak se vůbec nic nemění, tzn. v rozhraní bude create metoda bez parametrů. Nette si samo zjistí, co je té komponentě potřeba předat, a ve vygenerované továrničce jí to předá.

ChocoTUx
Člen | 31
+
0
-

Jo to jsem zkoušel a nefungovalo to (pak jsem smazal starou továrnu – kterou nette najde a i když nepoužívá tak ji zvaliduje). Super díkec.

Ale ještě když si s tím hraji, tak jsem narazil na problém. Když bych místo instance jiné třídy tam chtěl z konfigu dostat nějakou konstantu. Šel jsem na to podle tohoto návodu, ale nějak mi to padá. http://www.zeminem.cz/…nitive-guide

- implement: App\Model\IAnalyticsFactory
	create: App\Model\Analytics(test: %promena%)
interface IAnalyticsFactory
{
    /** @return \App\Model\Analytics */
    function create();
}
function __construct($test){
		\Tracy\Debugger::barDump($test);
	}

Tak mi to napíše tuhle chybu – Service ‚68‘: Parameter $test in App\Model\Analytics::__construct() has no type hint, so its value must be specified.

A porůznu když zkusím nějaké kombinace, takt o vždy na něčem spadne. Když zkusím v configu místo create tam dát arguments, tak to zahlásí že nezná službu arguments… jiná kombinace mě už nenapadla :/