RobotLoader má podporu filtrů (a jaké triky se s tím dají dělat)

před 5 lety

David Grudl
founder | 6706
+
+1
-

Přidal jsem RobotLoaderu podporu pro filtry: tedy funkce, skrz které je možné zdrojový soubor prohnat. Asi si říkáte, k čemu takový nesmysl je :-) Má to docela cool potenciál!

Příklad č.1:

Potřebujete v rámci testování mockovat objekty a narazíte na problém, že třída je buď final nebo má final metody. V takovém případě nelze plnohodnotný mock vůbec vytvořit. Dokud tedy nepoužijeme filtry:

$loader = new RobotLoader;
// pro použití filtrů je potřeba nastavit druhý storage
$loader->setCacheStorage(new FileStorage(...), new PhpFileStorage(...));

// filtr pro *.php soubory, co z kódu odstraní všechny klíčové slova "final"
$loader->filters['php'] = function($input) {
    $code = '';
    foreach (@token_get_all($input) as $token) {
        if (!is_array($token)) {
            $code .= $token;
        } elseif ($token[0] !== T_FINAL) {
            $code .= $token[1];
        }
    }
    return $code;
};

$loader->register();

V tuto chvíli bude RobotLoader načítat všechny třídy tak, že nebudou mít finální modifikátory a půjdou mockovat.

Pokud bychom chtěli takto načítat třídy přímo z Nette, je nutné RobotLoader předřadit NetteLoaderu, což se udělá parametrem TRUE:

$loader->register(TRUE);

Příklad č.2

V PHP se oproti JavaScriptu zatím nerozmohly kompilované jazyky, jako je například CoffeeScript, ale dle mého je to jen otázkou času. Jejich nasazení maximálně ulehčí RobotLoader, díky němuž můžete zcela transparentně kombinovat soubory napsané v PHP se soubory v jiném jazyce.

Pokud bych třeba chtěl psát v LadyPHP, přidám si do RobotLoaderu filter:

$loader->filters['lady'] = function($input) {
    return Lady::parse($input);
};

A kteroukoliv třídu v rámci projektu mohu mít v souboru *.lady a vše bude fungovat. Soubory se budou automaticky za běhu kompilovat a překompilují se i při změně zdrojového souboru.

před 5 lety

juzna.cz
Člen | 249
+
0
-

Supr!! Prakticky to same jsme s HosipLanem resili v PhpEhnancer, kde jsme ale narazili na neprivetivost RobotLoaderu a vrtat se nam v nem nechtelo (tak jsme pouzili PSR-0 loader z Composeru).
PS: Najdete tam take dalsi ukazky filteru, se kterymi jsme si hrali.

před 5 lety

Jan Tvrdík
Nette guru | 2536
+
0
-

Nice! Tak kdo první napíše filtr, který bude z něčeho jako

<?php
class Foo
{
    /**
     * @var MyService
     * @inject
     */
    public $myService;
}

generovat něco jako

<?php
class Foo
{
    /**
     * @var MyService
     */
    private $myService;

    public function injectMyService(MyService $service)
    {
        $this->myService = $service;
    }
}

před 5 lety

David Ďurika
Člen | 341
+
0
-

Jan Tvrdík napsal(a):

Nice! Tak kdo první napíše filtr, který …

no predsa ten kto sa pyta :)

před 5 lety

David Grudl
founder | 6706
+
0
-

A co třeba filtr konvertující on-the-fly Nette pro PHP 5.3 do verze PHP 5.2!

před 5 lety

David Ďurika
Člen | 341
+
0
-

David Grudl napsal(a):

A co třeba filtr konvertující on-the-fly Nette pro PHP 5.3 do verze PHP 5.2!

zbytocna strata casu… 5.2 by som uz uplne zavrhol…

před 5 lety

Elijen
Člen | 174
+
0
-

Jen jestli to neni tak trochu evil feature :)

Co podpora $array = [1, 2, 3]; pro PHP 5.3 a starsi?

před 5 lety

Honza Marek
Člen | 1674
+
0
-

David Grudl napsal(a):

A co třeba filtr konvertující on-the-fly Nette pro PHP 5.3 do verze PHP 5.2!

Jakože RobotLoadet nebude v žádném namespacu?

před 5 lety

David Grudl
founder | 6706
+
0
-

Nejprve se zavolá filtr na něj ;-)

před 5 lety

vvoody
Člen | 911
+
0
-

Jan Tvrdík napsal(a):

Nice! Tak kdo první napíše filtr, který bude z něčeho jako

Mam hotovo len narazil som na problem.

Page not found. Missing template '…\temp\cache\templates\Homepage\default.latte'

Teraz ako a kde to slusne poriesit?

před 5 lety

Jan Tvrdík
Nette guru | 2536
+
0
-

Pokud to David nebude řešit systémově, tak bych prozatím s klidným srdcem přepsal formatTemplateFiles() v BasePresenteru.

před 5 lety

unu
Člen | 2
+
0
-

Elijen napsal(a):
Co podpora $array = [1, 2, 3]; pro PHP 5.3 a starsi?

Zrovna dneska jsem to přidal do LadyPHP, tady je zjednodušená verze pro PHP: https://gist.github.com/3724437

před 5 lety

jansfabik
Člen | 193
+
0
-

vvoody napsal(a):

Napadlo mě řešení. RobotLoader by soubory neincludoval přímo, ale přes stream wrapper. Pak by se includnulo filter://{filename}. PHP skripty by tento stream proháněl filtrem, ostatní soubory by nechal tak, jak jsou.

Vlastně by se ta věc dala úplně vyčlenit z RobotLoaderu a mohla by dostat výstižnější název, třeba FilterStream. Pak by se to dalo používat takhle:

<?php // bootstrap.php

require LIBS_DIR . '/Nette/loader.php';

$stream = new FilterStream('filter');
$stream->setCacheStorage(new FileStorage(...));
$stream->filters['php'] = function($input) { ... };
$stream->registrer();

require 'filter://' . APP_DIR . '/real-bootstrap.php';
<?php // real-bootstrap.php

$loader = new RobotLoader;
$loader->setCacheStorage(new FileStorage(...));
$loader->addDirectory('filter://' . APP_DIR);
$loader->register();

// ...

Vyřešilo by to veškeré problémy s voláním šablon a umožnilo filtrovat nejen PHP ale i jiné formáty. Dále by to usnadnilo běh PHP 5.3 kódu na PHP 5.2, protože bych si mohl nechat filtrovat i bootstrap.

Jen doufám, že by to moc nezhoršilo výkon. Nevím, jestli v takovém případě PHP cachuje bytecode.

Co si o tomto nápadu myslíte?

Editoval jansfabik (15. 9. 2012 20:38)

před 5 lety

Milo
Moderator | 1031
+
0
-

Také by se tím dala dělat typová kontrola parametrů :-)

před 5 lety

jtousek
Člen | 956
+
0
-

@Milo: Nebylo by to moc pomalé?

před 5 lety

jansfabik
Člen | 193
+
0
-

Problém by mohl nastat případě, že je předán parametr s jiným typem. Má se provést přetypování nebo vyhodit výjimka?

Napadlo mě, že by se takto mohl generovat kód podle PHP doc tagů.

/**
 * @param  int     {@required}
 * @param  string  {@convert}
 */
public function foo($bar, $baz)
{
    if (!is_int($bar)) {
        throw new InvalidArgumentException('Parameter $bar must be an integer, ' . gettype($bar) . ' given.');
    }

    $baz = (string) $baz;

    // ...
}

Ale mnohem čistější mi připadá vytvořit si na tohle makro v editoru.

před 5 lety

jtousek
Člen | 956
+
0
-

Připadá mi vhodnější vyhodit výjimku a případné přetypování vynutit už při volání.

Makro v editoru? Jakože aby tyhle kousky kódu generovalo? To snad ne, filtr by byl skvělý v tom, že bys ty kousky neviděl.

Mimochodem by bylo nutné ten filtr napsat tak aby neměnil čísla řádků kvůli laděnce.

před 5 lety

jansfabik
Člen | 193
+
0
-

To vynucení přetypování při volání zní docela rozumně. A jak by vypadala syntax? Měly by se převádět úplně všechny @param tagy nebo by bylo lepší k nim dávat ještě nějaký flag?

před 5 lety

jtousek
Člen | 956
+
0
-

@jansfabik: On by nebyl až takový problém aby fungoval přímo typehint:

function neco(int $cislo, string $retezec) {}

Filtr by jej jednoduše odebral a přidal by své podmínky a výjimky.

před 5 lety

jansfabik
Člen | 193
+
0
-

Co kdybych chtěl v jednom parametru přijímat více různých typů, např. string|int?

před 5 lety

pekelnik
Člen | 468
+
0
-

Ja vam nevim! Vsechno to zni velice zajimave, ale ma to jeden zasadni problem:

Zavislost na RobotLoaderu

od chvile zavedeni PSR-0 jsem rad ze jsem se RobotLoaderu zbavil a opravdu si nedovedu predstavit jeho znovu-zavadeni kvuli temto „opicarnam“, bez urazky…

před 5 lety

jansfabik
Člen | 193
+
0
-

Tohle myslím řeší ten FilterStream, viz výše. Ten by se staral čistě jen o filtrování PHP a loader si můžeš použít jaký chceš.

před 5 lety

Filip Procházka
Moderator | 4692
+
0
-

@pekelnik: my to s @juzna už řešili, tohle byl ovšem chybějící článek, protože spousta lidí robota používá.

@jansfabik: streamy máme taky vyřešené.

Editoval HosipLan (22. 9. 2012 11:35)

před 5 lety

jansfabik
Člen | 193
+
0
-

@Hosiplan: Super. Ještě bych to chtělo pár změn a myslím, že by to bylo použitelné:

  • Ten stream wrapper by měl umět i pracovat se složkami, viz manuál. Potom by se RobotLoader vůbec nemusel hackovat, jenom by se mu před cestu ke složce přidalo enhance://.
  • Soubory, které nejsou s příponou .php by se nechaly jak jsou, což vyřeší problém se šablonami, viz výše.
  • Přidalo by se nějaké cachování.

před 5 lety

unu
Člen | 2
+
0
-

@jansfabik: Pokud by se ten stream wrapper napsal tak jak říkaš a fungovaly by s ním všechny operace jako s běžný souborovým systémem, proč už rovnou nenahradit defaultní wrapper file://? Potom by se to dalo použít v jakémkoli projektu pouhým přidáním dvou řádku a nemusel by se řešit žádný autoloader. Jen nevím jak velký vliv by to mělo na rychlost načítání souborů.

před 5 lety

jansfabik
Člen | 193
+
0
-

Tak jsem si to napsal (tady se můžete podívat) a zjistil jsem, že PHP je hrozně zabugované.

  • Pokud uvnitř funkce url_stat() zavolám fopen, tak to vyhodí RuntimeException a PHP na konci skriptu skončí se Segmentation Fault.
  • Funkce glob(), realpath() neumí se stream wrappery pracovat.

RobotLoader mi fungoval, když jsem v něm zakomentoval funkci realpath tady.

Navíc to ani vůbec rychlé. Vytvořil jsem si filtr, který nedělá nic a zkusil jsem přečíst všechny soubory frameworku.

$iterator = new RecursiveDirectoryIterator($netteDir);
$iterator = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::LEAVES_ONLY);

foreach ($iterator as $file) {
    if ($file->isFile()) {
        file_get_contents($file->getPathname());
    }
}

Bez filtru mi tohle zabere asi 13ms, s filtrem 250ms. Když jsem k tomu přidal ještě cachování, tak to trvalo dokonce 400ms!

Stream wrappery proto asi nebudou tou správnou cestou. Spíš mi připadá lepší si při každém nahrání na server ty skripty předzpracovat.

před 5 lety

Milo
Moderator | 1031
+
0
-

Dost často je typehint v PHP zbytečný nebo se předává objekt kde typehint lze. Ale někde by se mi to vysloveně hodilo :-)

public function printMe(array|Traversable $fields, int $flags)
{
}

jtousek napsal(a):

@Milo: Nebylo by to moc pomalé?

Určitě by to vzalo nějaký čas, než by se „přegeneroval“ PHP kód souboru, ale pak by to vzalo stejně času, jako by se typehint dělal ručně. Jen mě napadlo, že by to šlo.

Také se s tím dá obejít omezení v PHP, kdy lze proměnným objektu přiřadit jen „skalární“ defaultní hodnotu.

Také se s tím dá obejít celé PHP ;-)

před 5 lety

jtousek
Člen | 956
+
0
-

Milo napsal(a):

jtousek napsal(a):
@Milo: Nebylo by to moc pomalé?

Určitě by to vzalo nějaký čas, než by se „přegeneroval“ PHP kód souboru, ale pak by to vzalo stejně času, jako by se typehint dělal ručně. Jen mě napadlo, že by to šlo.

Nemyslím to generování, to se přegeneruje jednou a pak se loaduje z cache, to je v pohodě. Zajímá mne o kolik zpomalí aplikaci ty přidané podmínky na začátku téměř každé funkce. – Když už bych to používal, tak všude.

Editoval jtousek (23. 9. 2012 20:52)

před 5 lety

redhead
Člen | 1315
+
0
-

Mě se to všechno hrozně líbí. Fakt! Jedinej obrovskej problém (pro mě) je, že NB nebo kterýkoliv jiný normální IDE bude svítit celý červeně a nebo pokud někdo alespoň udělá obarvovač, tak ztratím code completion, hinty a všechno ostatní. Opravdu nejsem masochista, abych používal PSPad nebo Sublime Text. Tohle pro mě bude vždy překážkou. Sakra, ale jak já bych tohle tak rád používal.

před 5 lety

hrach
Člen | 1781
+
0
-

Prosim jdete to resit mimo changelog. Toto sem nepatri.