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

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8218
+
+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.

juzna.cz
Člen | 248
+
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.

Jan Tvrdík
Nette guru | 2595
+
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;
	}
}
David Ďurika
Člen | 328
+
0
-

Jan Tvrdík napsal(a):

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

no predsa ten kto sa pyta :)

David Grudl
Nette Core | 8218
+
0
-

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

David Ďurika
Člen | 328
+
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…

Elijen
Člen | 171
+
0
-

Jen jestli to neni tak trochu evil feature :)

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

Honza Marek
Člen | 1664
+
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?

David Grudl
Nette Core | 8218
+
0
-

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

vvoody
Člen | 910
+
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?

Jan Tvrdík
Nette guru | 2595
+
0
-

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

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

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)

Milo
Nette Core | 1283
+
0
-

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

jtousek
Člen | 951
+
0
-

@Milo: Nebylo by to moc pomalé?

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.

jtousek
Člen | 951
+
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.

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?

jtousek
Člen | 951
+
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.

jansfabik
Člen | 193
+
0
-

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

pekelnik
Člen | 462
+
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…

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š.

Filip Procházka
Moderator | 4668
+
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)

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í.
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ů.

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.

Milo
Nette Core | 1283
+
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 ;-)

jtousek
Člen | 951
+
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)

redhead
Člen | 1313
+
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.

hrach
Člen | 1838
+
0
-

Prosim jdete to resit mimo changelog. Toto sem nepatri.