přechod z Nette 3.2 na 4.0 problém s inicializací dabataze

iru
Člen | 110
+
0
-

Pokouším se převést aplikaci z Nette 3.2 do 4.0, ale nemůžu se dostat přes inicializaci databáze. Hlásí mi to chybu:
Typed property App\Presenters\BasePresenter::$database must not be accessed before initialization

V BasePresenter to mám takto:

public  function __construct(private Nette\Database\Explorer $database, private MenuModel $model)
    {
        $this->database=$database;
    }

Nevím kde hledat problém.

Původně jsem měla BasePresenter takto:

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    public $database;
     /**
    @var \App\Model\MenuModel  @inject*/
    public $model;
    /** @persistent */
    public $locale;

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


    }

Ale v Nette4 mi to hlásilo chybu v načítání modelu, tak jsem to opravila viz výše. Ostatní presentery dědí z tohoto BasePresenteru. Další dotaz je, jak to mám řešit v dalších presenterech, když potřebuji načítat jiný model. Třeba v ClankyPresenter to mám takto:

final class ClankyPresenter extends BasePresenter
{
      /** @persistent */
    public $url;
    public $ImagesDir_2='images/foto';


    public  function __construct(private MenuModel $model, private GalerieModel $modelGaleries, private GalerieFotoModel $modelFoto )
    {

    }

Prosím o radu a pokud možno vysvětlení, abych to pochopila také proč. Děkuji moc.

nightfish
Člen | 472
+
0
-

@iru

Na první pohled:

  1. Pokud chceš $database z BasePresenteru i v jeho potomcích, udělej $database alespoň protected.
  2. $this->database=$database; můžeš odstranit, protože používáš constructor property promotion, takže se přiřazení už provedlo automaticky

Dále je pak otázka, ve kterém presenteru ti PHP uvedenou chybu hlásí – předpokládal bych, že je to v některém z potomků BasePresenteru , protože v potomkovi máš uvedeno public $database; nebo protected $database; a nestaráš se o naplnění té property (a zároveň není vidět $database z BasePresenteru, protože jsi v bodu 1 použila viditelnost private).

Poznámka k předávání závislostí do BasePresenteru: závislosti, které v BasePresenteru předáváš přes konstruktor, pak musíš vyžadovat i ve všech poděděných presenterech, takže se často všechny závislosti BasePresenteru předávají přes atribut #[Inject] nebo metodou inject*() – viz dokumentaci. Stranou nyní nechávám otázku toho, jak moc preferované je používat univerzální předky (BasePresenter, BaseModel), protože je to na delší diskusi.

A ještě nesouvisející podotek ohledně struktury aplikace na závěr: Do svých presenterů si nikdy nepředávám jako závislost databázové spojení, protože práce s databází není zodpovědností presenteru. Místo toho mám databázový přístup obalený do služby, která logiku čtení/zpracování dat zapouzdřuje a teprve tuto službu používám v presenteru. Pravděpodobně něco podobného jako tvůj App\Model\MenuModel.

iru
Člen | 110
+
0
-

nightfish napsal(a):

@iru

Na první pohled:

  1. Pokud chceš $database z BasePresenteru i v jeho potomcích, udělej $database alespoň protected.
  2. $this->database=$database; můžeš odstranit, protože používáš constructor property promotion, takže se přiřazení už provedlo automaticky

Dále je pak otázka, ve kterém presenteru ti PHP uvedenou chybu hlásí – předpokládal bych, že je to v některém z potomků BasePresenteru , protože v potomkovi máš uvedeno public $database; nebo protected $database; a nestaráš se o naplnění té property (a zároveň není vidět $database z BasePresenteru, protože jsi v bodu 1 použila viditelnost private).

Poznámka k předávání závislostí do BasePresenteru: závislosti, které v BasePresenteru předáváš přes konstruktor, pak musíš vyžadovat i ve všech poděděných presenterech, takže se často všechny závislosti BasePresenteru předávají přes atribut #[Inject] nebo metodou inject*() – viz dokumentaci. Stranou nyní nechávám otázku toho, jak moc preferované je používat univerzální předky (BasePresenter, BaseModel), protože je to na delší diskusi.

A ještě nesouvisející podotek ohledně struktury aplikace na závěr: Do svých presenterů si nikdy nepředávám jako závislost databázové spojení, protože práce s databází není zodpovědností presenteru. Místo toho mám databázový přístup obalený do služby, která logiku čtení/zpracování dat zapouzdřuje a teprve tuto službu používám v presenteru. Pravděpodobně něco podobného jako tvůj App\Model\MenuModel.

Díky za přehledné vysvětlení. Takže v BasePresenter jsem to udělala takto. Změnila $databaze na protected a zároveň další závislosti předávám pomocí #[Inject]:

 #[Inject]
    public MenuModel $model;

    public  function __construct(protected Nette\Database\Explorer $database)
    {

    }

Tady se jedná o převod starší aplikace, takže toho nechci zase tolik předělávat, ale pro nový projekt zvážím zapouzdření databázových věcí přes model.
Ty univerzální předky BasePresenter používám kvůli navigaci a dalším věcem, které potřebuji ve všech presenterech, přijde mi to praktičtější mít na jednom místě. Jak to řešit lépe?

m.brecher
Generous Backer | 736
+
0
-

@iru

V zásadě je odpověď na Tvoje otázky jednoduchá a vše bylo již řečeno výše, posílám to jenom shrnuté do jednoduchého kopyta, které sám používám a všem ho doporučuji

hierarchie presenterů

Používat BasePresenter? Určitě to má smysl, ale pouze pro věci, které se používají napříč celým webem. BasePresenter by měl být takový poloprázdný, jenom pár věcí co jsou úplně všude. Pokud aplikace má veřejnou část a administraci, tak se vyplatí mít dvě úrovně abstraktních presenterů:

abstract BasePresenter
   abstract FrontPresenter
      final HomepagePresenter
      .......
   abstract AdminPresenter
      final AdminEntryPresenter
      final ArticlePresenetr
      .....

Tuto hierarchii lze velmi vhodně doplnit použiutím trait PHP, které se ideálně hodí na vkládání věcí, co jsou v několika presenterech paralelně, ale ne ve všech. Traitami lze šikovně zpřehlednit i jeden jediný příliš rozsáhlý presenter.

předání služeb v presenterech

Předání služeb (závislostí) do presenterů, je v PHP 8.0 a Nette 4.0 velmi jednoduché:

a) abstraktní presentery

use Nette\DI\Attributes\Inject;  // toto se nesmí zapomenout!

abstract class BasePresenter extends Presenter
{
    #[Inject]
	public MenuModel $menuModel;  // musí být public, nesmí být readonly !!

	// zde NESMÍ být konstruktor !!!
}

b) finální presentery

nová syntaxe >= PHP 8.0:

final class ArticlePresenter extends AdminPresenter
{
    // #[Inject] zde nepoužívat

   public function __construct(private ArticleModel $articleModel, ....)  // používat konstruktor
   {}
}

stará syntaxe pro < PHP 8.0, funguje stále, ale používej tu jednodušší novou:

final class ArticlePresenter extends AdminPresenter
{
   private ArticleModel $articleModel;

    public function __construct(ArticleModel $articleModel, ....)  // používat předání přes konstruktor
   {
      $this->articleModel = $articleModel;
   }
}

předání služeb v modelových třídách

V modelových třídách se používá předání konstruktorem – stejné jako u finálních presenterů

registrace služeb

předávané objekty do presenteru/modelu jsou buďto vestavěné služby Nette Frameworku (Explorer, User, …), nebo Tvoje vlastní objekty – ArticleModel apod…, které musíš registrovat jako služby v konfiguraci ***.neon:

a) jednotlivě jako anonymní služby:

services:
   - App\Model\ArticleModel
   - App\Model\PhotoGalleryModel
   ......

Lepší způsob je pomocí search registrovat hromadně určitý typ tříd, nemusíš potom ručně dokola editovat konfiguraci:

search:
	default:
		in: %appDir%
		classes:
			- *Model

Tento search Ti zaregistruje úplně všechny třídy končící na ***Model jako služby.

konvence názvosloví

pro modelové třídy se často používají názvy jako ArticleFacade, nebo ArticleRepository. Když ale v Nette používám databázovou knihovnu Explorer a presentery jsou uloženy v /Presenters/ArticlePreesenter.php je logické ukládat modelové třídy do /Model/ArticleModel.php – děláš to správně ;).

Aby se Ti mozek zbytečně nezatěžoval, používej:

a) názvy tabulek v databázi, názvy presenterů, modelových tříd a všech dalších tříd – pouze anglicky
místo ClankyModel/ClankyPresenter – > ArticleModel/ArticlePresenter
mixování českých a anglických slov znepřehledňuje kód

b) pro property stejný název jako je v názvu třídy – kód je pak přehlednější, dle Nette Coding Standard nejprve specifické, pak obecné tj. ArticlePresenter, nikoliv PresenterArticle, nebo ArticleModel nikoliv ModelArticle

final class ArticlePresenter extends AdminPresenter
{
   public function __construct(
      private ArticleModel $articleModel,
      private GalleryModel $galleryModel,
      private GelleryPhotoModel $galleryPhotoModel,
   )
   {}
}

Dalo by se pokračovat, ale nechci toto téma zahlcovat. Dobře navržené postupy a konvence ušetří obrovské množství času a námahy.

m.brecher
Generous Backer | 736
+
0
-

@iru

Takže v BasePresenter jsem to udělala takto. Změnila $databaze na protected a zároveň další závislosti předávám pomocí #[Inject]

Pozor!! Závislosti předávané #[Inject] musí být vždycky public, protected nefunguje !!