Da sa tento kod napisat efektivnejsie resp krajsie?

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

Ahojte

Mam jeden presenter a jednu latte sablonu. Zda sa mi ale ze nato idem prilis komplikovane a nejaky konkretny priklad nato som nenasiel.
Princip je vypisanie produktov s filtrom.

Subory je mozne stiahnut tu:
"":http://www.mki.sk/…resenter.txt
"":http://www.mki.sk/…lt.latte.txt

Presneter:

<?php

use Nette\Application\UI\Form;

/**
 * @User(loggedIn)
 */
class AdminProductPresenter extends BaseAdminPresenter {

    private $category_authorRepository;
    private $category_manufacturerRepository;
    private $category_piecesRepository;
    private $category_themeRepository;
    private $category_theme_relRepository;
    private $puzzleRepository;

    public function renderDefault() {
        if ($this->themeFilter == "" || $this->themeFilter == null) $this->themeFilter = "%";
        if ($this->piecesFilter == "" || $this->piecesFilter == null) $this->piecesFilter = "%";
        if ($this->manufacturerFilter == "" || $this->manufacturerFilter == null) $this->manufacturerFilter = "%";
        if ($this->authorFilter == "" || $this->authorFilter == null) $this->authorFilter = "%";

        $this->template->puzzles = $this->puzzleRepository->findAll()->
                where(
                    "category_manufacturer.id LIKE '" . $this->manufacturerFilter . "'" .
                    " AND category_pieces.id LIKE '" . $this->piecesFilter . "'" .
                    " AND category_author.id LIKE '" . $this->authorFilter ."'"  .
                    " AND category_theme.id LIKE '" . $this->themeFilter ."'"
                );

        $this->setLayout('layoutAdmin');
    }

    protected function createComponentProductFilter() {
        $themes = $this->category_themeRepository->findAll()->select("id, name")->fetchPairs('id', 'name');
        $authors = $this->category_authorRepository->findAll()->select("id, name")->fetchPairs('id', 'name');
        $manufacturers = $this->category_manufacturerRepository->findAll()->select("id, name")->fetchPairs('id', 'name');
        $pieces = $this->category_piecesRepository->findAll()->select("id, name")->fetchPairs('id', 'name');

        if ($this->themeFilter == "%") $this->themeFilter = null;
        if ($this->piecesFilter == "%") $this->piecesFilter = null;
        if ($this->manufacturerFilter == "%") $this->manufacturerFilter = null;
        if ($this->authorFilter == "%") $this->authorFilter = null;

        $form = new Form();
        $form->addSelect('theme', 'Theme:', $themes)->setPrompt("")->setDefaultValue($this->themeFilter);
        $form->addSelect('author', 'Author:', $authors)->setPrompt("")->setDefaultValue($this->authorFilter);
        $form->addSelect('manufacturer', 'Manufacturer:', $manufacturers)->setPrompt("")->setDefaultValue($this->manufacturerFilter);
        $form->addSelect('pieces', 'Pieces:', $pieces)->setPrompt("")->setDefaultValue($this->piecesFilter);
        $form->addSubmit('send', 'Search');
        $form->onSuccess[] = callback($this, "processProductFilter");

        if ($this->themeFilter == null) $this->themeFilter = "%";
        if ($this->piecesFilter == null) $this->piecesFilter = "%";
        if ($this->manufacturerFilter == null) $this->manufacturerFilter = "%";
        if ($this->authorFilter == null) $this->authorFilter = "%";


        return $form;
    }

    public function processProductFilter(Form $form) {
        $values = $form->getValues();

        $this->piecesFilter = $values->pieces;
        $this->themeFilter = $values->theme;
        $this->manufacturerFilter = $values->manufacturer;
        $this->authorFilter = $values->author;



        $this->redirect('default');
    }

    protected function startup() {
        parent::startup();
        $this->category_authorRepository = $this->context->category_authorRepository;
        $this->category_manufacturerRepository = $this->context->category_manufacturerRepository;
        $this->category_piecesRepository = $this->context->category_piecesRepository;
        $this->category_themeRepository = $this->context->category_themeRepository;
        $this->category_theme_relRepository = $this->context->category_theme_relRepository;

        $this->puzzleRepository = $this->context->puzzleRepository;
    }

}

Latte:

{block content}
<p><a href="admin:AddPiecesCategory" class="btn btn-link"><button class="btn btn-success">Add category</button></a></p>

{form productFilter}
    <table>
        <tr>
            <td>{label theme /}</td>
            <td>{label pieces /}</td>
            <td>{label author /}</td>
            <td>{label manufacturer /}</td>
            <td></td>
        </tr>
        <tr>
            <td>{input theme}</td>
            <td>{input pieces}</td>
            <td>{input author}</td>
            <td>{input manufacturer}</td>
            <td>{input send}</td>
        </tr>
    </table>
{/form}

<table id="sample-table-2" class="table table-striped table-bordered table-hover">
    <thead>
        <tr>
            <th>Name</th>
            <th>Image</th>
            <th>Pieces</th>
            <th>Manufacturer</th>
            <th>Author</th>
            <th>Theme</th>
            <th>Action</th>
        </tr>
    </thead>
    <tbody>
        {foreach $puzzles as $puzzle}
            <tr>
                <td>{$puzzle->name}</td>
                <td><img src="/product/{$puzzle->image_thumb}" alt="" style="height:50px;" /></td>
                <td>
                    {$puzzle->category_pieces->name}
                </td>
                <td>
                    {$puzzle->category_manufacturer->name}
                </td>
                <td>
                    {$puzzle->category_author->name}
                </td>
                <td>
                    {foreach $puzzle->related('category_theme_rel') as $theme}
                        {$theme->category_themee->name}
                    {/foreach}
                </td>
                <td class="td-actions">
                    <div class="action-buttons">
                        <a class="green" href="#"><i class="icon-pencil bigger-130"></i></a>
                    </div>
                </td>
            </tr>
        {/foreach}
    </tbody>
</table>
<p>&nbsp;</p>
<p><a href="admin:AddPiecesCategory" class="btn btn-link"><button class="btn btn-success">Add category</button></a></p>
{/block}

dakujem

sKopheK
Člen | 207
+
0
-

Opakuje se ti tam celkem casto nejaky kod, zacal bych stavet na tom. Precti si nejaky clanek o doporucenem psani kodu, zakladni principy apod. Na druhou stranu se vzdy da neco zlepsit :).

Tomáš Votruba
Moderator | 1114
+
0
-

@MKI-Miro: Např.

  1. celou metodu startup můžeš smazat a nahradit @inject
  2. Zkus také rovnou použít fechPairs() (nevím, jakou máš db vrstvu, ale mělo by to jít), tedy místo:
$this->category_themeRepository->findAll()->select("id, name")->fetchPairs('id', 'name');

pouze

$this->category_themeRepository->fetchPairs('id', 'name');

Editoval Tomáš Votruba (25. 3. 2014 17:04)

Jan Suchánek
Člen | 404
+
0
-

@MKI-Miro: … doplním, formulář bych měl v samostatné komponentě a tu bych injectoval do presenteru pomocí továrničky, komponentě bych předával fasádu (ve která bych používal jednotlivé repository).

Parametry (themeFilter, piecesFilter, manufacturerFilter, authorFilter) bych měl persistentní v komponentě nebo presenteru. Parametry bych předával ovalidované do dotazu v renderDefault (samozřejmě i tato část by šla vyčlenit do samostatné komponenty).

Pokud je to špatně tak mě opravte, předem dík.

Tomáš Votruba: + Wow, to sem nevěděl že jde.

MKI-Miro
Člen | 261
+
0
-

Tomáš Votruba napsal(a):

@MKI-Miro: Např.

  1. celou metodu startup můžeš smazat a nahradit @inject
  2. Zkus také rovnou použít fechPairs() (nevím, jakou máš db vrstvu, ale mělo by to jít), tedy místo:
$this->category_themeRepository->findAll()->select("id, name")->fetchPairs('id', 'name');

pouze

$this->category_themeRepository->fetchPairs('id', 'name');

Super dakujem velmi pekne obe veci funguju paradne
Cize presenter teraz vyzera takto:

<?php

use Nette\Application\UI\Form;

/**
 * @User(loggedIn)
 */
class AdminProductPresenter extends BaseAdminPresenter {

    /** @var \Puzzlemania\shippingRepository @inject */
    private $category_authorRepository;

    /** @var \Puzzlemania\category_manufacturerRepository @inject */
    private $category_manufacturerRepository;

    /** @var \Puzzlemania\category_piecesRepository @inject */
    private $category_piecesRepository;

    /** @var \Puzzlemania\category_themeRepository @inject */
    private $category_themeRepository;

    /** @var \Puzzlemania\category_theme_relRepository @inject */
    private $category_theme_relRepository;

    /** @var \Puzzlemania\puzzleRepository @inject */
    private $puzzleRepository;

    public function renderDefault() {
        if ($this->themeFilter == "" || $this->themeFilter == null)
            $this->themeFilter = "%";
        if ($this->piecesFilter == "" || $this->piecesFilter == null)
            $this->piecesFilter = "%";
        if ($this->manufacturerFilter == "" || $this->manufacturerFilter == null)
            $this->manufacturerFilter = "%";
        if ($this->authorFilter == "" || $this->authorFilter == null)
            $this->authorFilter = "%";

        $this->template->puzzles = $this->puzzleRepository->findAll()->
                where(
                "category_manufacturer.id LIKE '" . $this->manufacturerFilter . "'" .
                " AND category_pieces.id LIKE '" . $this->piecesFilter . "'" .
                " AND category_author.id LIKE '" . $this->authorFilter . "'" .
                " AND category_theme.id LIKE '" . $this->themeFilter . "'"
        );

        $this->setLayout('layoutAdmin');
    }

    protected function createComponentProductFilter() {
        $themes = $this->category_themeRepository->fetchPairs('id', 'name');
        $authors = $this->category_authorRepository->fetchPairs('id', 'name');
        $manufacturers = $this->category_manufacturerRepository->fetchPairs('id', 'name');
        $pieces = $this->category_piecesRepository->fetchPairs('id', 'name');

        if ($this->themeFilter == "%")
            $this->themeFilter = null;
        if ($this->piecesFilter == "%")
            $this->piecesFilter = null;
        if ($this->manufacturerFilter == "%")
            $this->manufacturerFilter = null;
        if ($this->authorFilter == "%")
            $this->authorFilter = null;

        $form = new Form();
        $form->addSelect('theme', 'Theme:', $themes)->setPrompt("")->setDefaultValue($this->themeFilter);
        $form->addSelect('author', 'Author:', $authors)->setPrompt("")->setDefaultValue($this->authorFilter);
        $form->addSelect('manufacturer', 'Manufacturer:', $manufacturers)->setPrompt("")->setDefaultValue($this->manufacturerFilter);
        $form->addSelect('pieces', 'Pieces:', $pieces)->setPrompt("")->setDefaultValue($this->piecesFilter);
        $form->addSubmit('send', 'Search');
        $form->onSuccess[] = callback($this, "processProductFilter");

        if ($this->themeFilter == null)
            $this->themeFilter = "%";
        if ($this->piecesFilter == null)
            $this->piecesFilter = "%";
        if ($this->manufacturerFilter == null)
            $this->manufacturerFilter = "%";
        if ($this->authorFilter == null)
            $this->authorFilter = "%";


        return $form;
    }

    public function processProductFilter(Form $form) {
        $values = $form->getValues();

        $this->piecesFilter = $values->pieces;
        $this->themeFilter = $values->theme;
        $this->manufacturerFilter = $values->manufacturer;
        $this->authorFilter = $values->author;

        $this->redirect('default');
    }

    protected function startup() {
        parent::startup();
    }

}

A este doplnim ako umna vyzera repository:

<?php

namespace Puzzlemania;

use Nette;
use Nette\Diagnostics\Debugger;

/**
 * Provádí operace nad databázovou tabulkou.
 */
abstract class Repository extends Nette\Object {

    /** @var Nette\Database\Context */
    protected $database;

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

    /**
     * Vrací objekt reprezentující databázovou tabulku.
     * @return Nette\Database\Table\Selection
     */
    protected function getTable() {
        // název tabulky odvodíme z názvu třídy
        preg_match('#(\w+)Repository$#', get_class($this), $m);
        return $this->database->table(lcfirst($m[1]));
    }

    /**
     * Vrací všechny řádky z tabulky.
     * @return Nette\Database\Table\Selection
     */
    public function findAll() {
        return $this->getTable();
    }

    /**
     * Vrací řádky podle filtru, např. array('name' => 'John').
     * @return Nette\Database\Table\Selection
     */
    public function findBy(array $by) {
        return $this->getTable()->where($by);
    }

    /** @return Nette\Database\Table\ActiveRow */
    public function findById($id) {
        return $this->findAll()->get($id);
    }

}
MKI-Miro
Člen | 261
+
0
-

jenicek napsal(a):

@MKI-Miro: … doplním, formulář bych měl v samostatné komponentě a tu bych injectoval do presenteru pomocí továrničky, komponentě bych předával fasádu (ve která bych používal jednotlivé repository).

Parametry (themeFilter, piecesFilter, manufacturerFilter, authorFilter) bych měl persistentní v komponentě nebo presenteru. Parametry bych předával ovalidované do dotazu v renderDefault (samozřejmě i tato část by šla vyčlenit do samostatné komponenty).

Pokud je to špatně tak mě opravte, předem dík.

Tomáš Votruba: + Wow, to sem nevěděl že jde.

Vdaka skusim

zabudol som dodat presenter od ktoreho dedia, kde su dane parametre (themeFilter, piecesFilter, manufacturerFilter, authorFilter) perzistentne

abstract class BaseAdminPresenter extends Nette\Application\UI\Presenter {

    /** @persistent */
    public $authorFilter;

    /** @persistent */
    public $piecesFilter;

    /** @persistent */
    public $themeFilter;

    /** @persistent */
    public $manufacturerFilter;

    public function beforeRender() {

    }

}
Jan Suchánek
Člen | 404
+
0
-

MKI-Miro: Teď nevim zda by neměla obsahovat i ten parent?

public function beforeRender() {
		parent::beforeRender();
		..
}

Každopádně já nevim kam by bylo nejlepší dávat ty parametry filter, co třeba nějaký pěkný query objekt?
Jakmile se objevej, prostě if ($this->themeFilter == "" || … ten kod jen dost kyne.
Persistentům můžeš dávat rovnou default hodnotu, navíc se mi zdá že je nevaliduješ
a že je teoreticky možný že by ti někdo mohl podhodit nějakou hodnotu.

Co se stane když do filtr parametru v url napíšu '?

Edit: Mělo by to být takto jak psal ViPErCZ?

$this->template->puzzles = $this->puzzleRepository->findAll()->where("
			category_manufacturer.id LIKE ? AND category_pieces.id LIKE ?
			AND category_author.id LIKE ? AND category_theme.id LIKE ?",
			$this->manufacturerFilter, $this->piecesFilter,
			$this->authorFilter, $this->themeFilter
);

Plus ještě nechápu jak může být like na id, ja myslel že id je int 11?

  • ty where by se měli skládat, tzn když není, tak nepoužívat ‚%‘, nebo k čemu tam je?
$rows = $this->puzzleRepository->findAll();

	if($this->manufacturer_id){
			$rows->where("category_manufacturer.manufacturer_id = ?", $this->manufacturer_id);
	}

	…

	$this->template->puzzlesRows = $rows;

Pokud v url musíš mít místo čísla nějaký řetězec, tak by šlo se nejdřív zeptat databáze zda vůbec takovej manufacturer existuje a potom ho použít v komplexnějším dotazu jen s číslem nebo s „in ?“.

Editoval jenicek (25. 3. 2014 22:44)

MKI-Miro
Člen | 261
+
0
-

jenicek napsal(a):

MKI-Miro: Teď nevim zda by neměla obsahovat i ten parent?

public function beforeRender() {
		parent::beforeRender();
		..
}

Každopádně já nevim kam by bylo nejlepší dávat ty parametry filter, co třeba nějaký pěkný query objekt?
Jakmile se objevej, prostě if ($this->themeFilter == "" || … ten kod jen dost kyne.
Persistentům můžeš dávat rovnou default hodnotu, navíc se mi zdá že je nevaliduješ
a že je teoreticky možný že by ti někdo mohl podhodit nějakou hodnotu.

Co se stane když do filtr parametru v url napíšu '?

Edit: Mělo by to být takto jak psal ViPErCZ?

$this->template->puzzles = $this->puzzleRepository->findAll()->where("
			category_manufacturer.id LIKE ? AND category_pieces.id LIKE ?
			AND category_author.id LIKE ? AND category_theme.id LIKE ?",
			$this->manufacturerFilter, $this->piecesFilter,
			$this->authorFilter, $this->themeFilter
);

Plus ještě nechápu jak může být like na id, ja myslel že id je int 11?

  • ty where by se měli skládat, tzn když není, tak nepoužívat ‚%‘, nebo k čemu tam je?
$rows = $this->puzzleRepository->findAll();

	if($this->manufacturer_id){
			$rows->where("category_manufacturer.manufacturer_id = ?", $this->manufacturer_id);
	}

	…

	$this->template->puzzlesRows = $rows;

Pokud v url musíš mít místo čísla nějaký řetězec, tak by šlo se nejdřív zeptat databáze zda vůbec takovej manufacturer existuje a potom ho použít v komplexnějším dotazu jen s číslem nebo s „in ?“.

To ze sa da viacnasobne pouzit where ma teda nenpadlo, usetrilo to kopec riadkov, dakujem

Validaciu nerobim kedze tam ma pristup jedine prihlaseny pouzivatel a tym som len ja. Kazdopadne dakujem za tip musim skontrolovat ako to mam vo frontend casti.

V tabulke je to samozrejme INT a to s % som si uvedomoval ze je to hlupost, len osetrit to by mi vygenerovalo este vacsi kod.

Teraz sa mi ten presenter uz zacinat pacit

<?php

use Nette\Application\UI\Form;

/**
 * @User(loggedIn)
 */
class AdminProductPresenter extends BaseAdminPresenter {

    /** @var \Puzzlemania\shippingRepository @inject */
    private $category_authorRepository;

    /** @var \Puzzlemania\category_manufacturerRepository @inject */
    private $category_manufacturerRepository;

    /** @var \Puzzlemania\category_piecesRepository @inject */
    private $category_piecesRepository;

    /** @var \Puzzlemania\category_themeRepository @inject */
    private $category_themeRepository;

    /** @var \Puzzlemania\category_theme_relRepository @inject */
    private $category_theme_relRepository;

    /** @var \Puzzlemania\puzzleRepository @inject */
    private $puzzleRepository;

    public function renderDefault() {
        $puzzles = $this->puzzleRepository->findAll();
        if($this->manufacturerFilter){
            $puzzles->where("category_manufacturer.id = ?", $this->manufacturerFilter);
        }
        if($this->piecesFilter){
            $puzzles->where("category_pieces.id = ?", $this->piecesFilter);
        }
        if($this->authorFilter){
            $puzzles->where("category_author.id = ?", $this->authorFilter);
        }
        if($this->themeFilter){
            $puzzles->where("category_theme.id = ?", $this->themeFilter);
        }
        $this->template->puzzles = $puzzles;

        $this->setLayout('layoutAdmin');
    }

    protected function createComponentProductFilter() {
        $themes = $this->category_themeRepository->fetchPairs('id', 'name');
        $authors = $this->category_authorRepository->fetchPairs('id', 'name');
        $manufacturers = $this->category_manufacturerRepository->fetchPairs('id', 'name');
        $pieces = $this->category_piecesRepository->fetchPairs('id', 'name');

        $form = new Form();
        $form->addSelect('theme', 'Theme:', $themes)->setPrompt("")->setDefaultValue($this->themeFilter);
        $form->addSelect('author', 'Author:', $authors)->setPrompt("")->setDefaultValue($this->authorFilter);
        $form->addSelect('manufacturer', 'Manufacturer:', $manufacturers)->setPrompt("")->setDefaultValue($this->manufacturerFilter);
        $form->addSelect('pieces', 'Pieces:', $pieces)->setPrompt("")->setDefaultValue($this->piecesFilter);
        $form->addSubmit('send', 'Search');
        $form->onSuccess[] = callback($this, "processProductFilter");

        return $form;
    }

    public function processProductFilter(Form $form) {
        $values = $form->getValues();

        $this->piecesFilter = $values->pieces;
        $this->themeFilter = $values->theme;
        $this->manufacturerFilter = $values->manufacturer;
        $this->authorFilter = $values->author;

        $this->redirect('default');
    }

    protected function startup() {
        parent::startup();
    }

}

mate este nejake napady navrhy ? (verim ze toto pomoze aj inym)

Jan Suchánek
Člen | 404
+
0
-

MKI-Miro: jo je to lepší, jen vyhoď ten startup ten tam být přece nemusí, když je prázný.

Jen mě docela irituje, že na fetchPairs musí být 5 repositářů.
Nebylo by lepší to sdružit do fasády? A mít tam v tom presenteru tedy jeden puzzleFacade.

Pokud bych chtěl ty výsledky fetchPairs používat i v jiných formulářích, například vkládací, editovací, jiný další, nebo v nějakym gridu, lze nějak, někam ukládat tyhle číselníčky, aby se ty selecty znova nepokládaly. Vím, že budou krátký, ale přecejn by mě to zajímalo.

Prostě, pokud výčet použiju na více místech, tak bych ho nětěl tahat třeba 2 a víckrát.

Jak se tohle dá řešit?

	// ne
		$form->onSuccess[] = callback($this, "processProductFilter");
	// ano
		$form->onSuccess[] = $this->processProductFilter;

Tohle už můžeš vyčlenit do samostatné komponenty.

protected function createComponentProductFilter() {
    $themes = $this->category_themeRepository->fetchPairs('id', 'name');
    $authors = $this->category_authorRepository->fetchPairs('id', 'name');
    $manufacturers = $this->category_manufacturerRepository->fetchPairs('id', 'name');
    $pieces = $this->category_piecesRepository->fetchPairs('id', 'name');

    $form = new Form();
    $form->addSelect('theme', 'Theme:', $themes)->setPrompt("")->setDefaultValue($this->themeFilter);
    $form->addSelect('author', 'Author:', $authors)->setPrompt("")->setDefaultValue($this->authorFilter);
    $form->addSelect('manufacturer', 'Manufacturer:', $manufacturers)->setPrompt("")->setDefaultValue($this->manufacturerFilter);
    $form->addSelect('pieces', 'Pieces:', $pieces)->setPrompt("")->setDefaultValue($this->piecesFilter);
    $form->addSubmit('send', 'Search');
    $form->onSuccess[] = callback($this, "processProductFilter");

    return $form;
}

Použíj tohle je to lepší Tvorba komponent s využitím autowiringu

protected function createComponentProductFilter() {
		$this->productFilter->create();
}

Kdybys chtěl pro filtrování udělat i resetovací tlačítko, které vrátí vše do defaultního stavu:

protected function createComponentProductFilter() {
		...
   	$form = new Form();
		...

		$sendButton = $form->addSubmit('send', 'Search');
		$resetButton = $form->addSubmit('reset', 'Reset');

		$sendButton->onClick[] = $this->processProductFilter;
		$resetButton->onClick[] = $this->resetProductFilter;

    return $form;
}

Editoval jenicek (26. 3. 2014 10:05)

Jan Suchánek
Člen | 404
+
0
-

Kam ukládate číselníky z fetchPairs, aby se tahali v celé aplikaci jednou a byli jednoduché?

$tableList1 = $this->table1Repository->fetchPairs('id', 'name');
$tableList2 = $this->table2Repository->fetchPairs('id', 'name');
$tableList3 = $this->table3Repository->fetchPairs('id', 'name');
$tableList4 = $this->table4Repository->fetchPairs('id', 'name');

Napadá mě PairsModel, který bude jako service a když zavolám v Presenteru $this->pairs->get(„table1“); vytáhne data pokud je už dávno nemá? Nebo je to špatné?

$list1 = $this->pairs->get('table1');
sKopheK
Člen | 207
+
0
-

Přijde mi přehlednější mít setDefaults() na jednom místě pro celý formulář, ne u každého elementu zvlášť. Ale to je do diskuze – duplikuje se zase záznam o formulářovém prvku.

Ten filtr určitě do komponenty a nějak ho zobecnit, je to tam pořád dokola.

Editoval sKopheK (26. 3. 2014 12:09)

Jan Suchánek
Člen | 404
+
0
-

@sKopheK: jj, aspoň se to nemusí furt někde hledat.