phpFashion

Na navigaci | Klávesové zkratky

Rubrika PHP

Dibi 3.0 je venku, Texy na cestě

Zmodernizoval jsem kód knihoven Dibi a Texy, třídy přenesl do jmenných prostorů a využil syntaxe PHP 5.4. Zároveň jsem doplnil mechanismus, aby knihovny fungovaly i s existujícím kódem, který používá původní názvy tříd.

Výsledkem je Texy 2.8 (release notes) a Dibi 3.0 (release notes).

Dibi mělo nést původně označení 2.4, protože krom vnitřního refactoringu jsem nechtěl přidávat nebo měnit jakoukoliv funkčnost, jako u Texy 2.8, ale nakonec jsem pár vychytávek přidal a výsledkem je právě verze 3.0:

  • nové výjimky Dibi\ConstraintViolationException, ForeignKeyConstraintViolationException, NotNullConstraintViolationExceptionUniqueConstraintViolationException
  • MySQL: sloupec TIME se převádí na objekty DateInterval namísto DateTime (BC break)
  • SqlsrvDriver: doplněna podpora pro LIMIT & OFFSET
  • vylepšen Dibi\Fluent při použití limit & offset

Zmizely ovladače pro SQLite 2 a MsSqlDriver, které nejsou od PHP 5.3 podporované, a MsSql2005Driver se nyní jmenuje SqlsrvDriver (funguje i starý název). Statická třída dibi zůstává mimo jmenné prostory. Pokud si píšete vlastní ovladač, došlo ke změně v rozhraní u metod escape() & unescape() (viz).

Minimální požadovaná verze PHP je 5.4.4, obě knihovny jsou plně funkční i pod PHP 7. Minifikovaná verze je ve formě archívu PHAR.

Dibi postupně pokrývám testy. Jelikož nepoužívám MS SQL Server, je tento driver víceméně v rukou komunity. Pokud jej používáte, zkuste prosím zjistit, proč neprocházejí testy používané pro jiné servery a co je potřeba změnit.

Velmi pozvolna vzniká i Texy 3.0, ze kterého zmizí dnes už překonané funkce, jako je třeba podpora jiného kódování než UTF-8, jiného formátu než HTML5 atd.


Jak vyvíjet komfotrněji?

Nová verze Nette 2.3.7 přináší spoustu vylepšení, jedno z nich si ale rychle zamilujete. Jsou to chybové hlášky, které se vám pokusí napovědět, pokud uděláte překlep.

Určitě jste už někdy narazili na podobnou chybu:

Chcete v šabloně vykreslit komponentu a ona prý neexistuje. Může to mít celou řadu příčin, od nějakého opomenutí na straně presenteru, až po chybu v šabloně. Nebo se komponenta jmenuje jinak?

Nejhorší ze všeho jsou triviální přelkepy, které nevidíte, takže několikrát prověříte všechny možnosti a strávíte na tom dost času, než chybu odhalíte.

Nejnovější verze Nette má ale šikovnější chybovou hlášku:

Did you mean ‚signInForm‘? Aha! Hned je jasné, že na vině byl překlep a můžete ho rovnou opravit.

Pokud jste se někdy dlouze zasekli na velikosti písmenek, tj. že vám {control MyComponent} hlásil chybu Component with name ‚MyComponent‘ does not exist, o to více oceníte dovětek did you mean ‚myComponent‘?.

Pojďme k Nette\Database. Chybka v názvu databázového sloupce? Opět ji dostanete na stříbrném podnose:

Mimochodem, stejná feature bude i v příští verzi dibi, kterou prosím před vydáním otestujte.

Nette napovídá překlepy v názvech funkcí, metod, proměnných atd. Pokud vyvíjíte v IDE, neměly by se vám podobné chyby stávat, na druhou stranu málokteré IDE dokáže plnohodnotně napovídat třeba v šablonách. Ať už se spletete v názvu filtru nebo makra:

Případně v názvu proměnné:

Hláškou did you mean novinky zvyšující pohodlí nekončí. Nette vás nově upozorní na celou řadu dalších, dříve špatně odhalitelných, chyb. Jako například chybějící []

$myForm->onSuccess = [$this, 'myFormSucceeded'];
// namísto správného
$myForm->onSuccess[] = [$this, 'myFormSucceeded'];

nebo chybějící ()

{foreach $form->getErrors as $error}
// namísto správného
{foreach $form->getErrors() as $error}

či docela nebezpečné opomenutí, jelikož $user->isLoggedIn je vždy truthy:

{if $user->isLoggedIn} ... něco tajného ... {/if}
// namísto správného
{if $user->isLoggedIn()} ... něco tajného ... {/if}

To nyní povede k varování Did you forget parentheses after isLoggedIn?

Poznámka: pokud záměrně píšete v kódu metodu bez závorek, tj. $cb = $obj->getItems, protože chcete využít vlastnosti Nette\Object, která takto do $cb uloží callback na metodu getItems, a objeví se varování, použijte prosím standardní PHP zápis, tj. $cb = [$obj, 'getItems']. Při korektních běžných použitích se varování nezobrazuje.

Dále Latte vás upozorní, když použijete modifikátor na místě, kde se ignoruje, jako například:

{if $var |filter}

Did you mean „komfortněji“?

Nová verze Nette je tu od toho, aby vám usnadnila a zpříjemnila vývoj. A jak je to napovídání boží si doopravdy uvědomíte, až to vyzkoušíte.


Chrome a nekonečné přesměrování

Chrome 44 (beta) odesílá nově hlavičku HTTPS: 1, která může způsobovat problémy.

Na některých hostinzích (z těch co používám třeba WebSupport už to opravili) si pak PHP myslí, že požadavek je pod šifrovaným spojením HTTPS. Tj. proměnná $_SERVER['HTTPS'] === 'on'.

U aplikací v Nette, které neběží pod https, to pak způsobí nekonečný redirect. Aplikace si prostě myslí, že k ní přistupujete přes URL https://example.com a přesměrovává na http://example.com.

Můžete to vyzkoušet z příkazové řádky pomocí:

curl -I --header "HTTPS: 1" http://example.com`

Že je hlavička HTTPS: 1 problematická se už ví, takže je možné, že se změní a do Chrome nedostane. Každopádně jako rychlý workaround, aby nedocházelo ke smyčce přesměrování v betaverzi Chrome, je přidat na začátek bootstrap.php:

unset($_SERVER['HTTPS']);

Zároveň je dobré si uvědomit, že na některých hostinzích lze detekci šifrovaného spojení velmi snadno ošálit.

Doplnění: Chrome 45 už hlavičku HTTPS: 1 neodesílá.


Jak na převod array() na [] v Gitu

Jak jsem v projektech, které používají PHP 5.4 a vyšší, převáděl starou syntaxi pro zápis polí array() na novou [].

Samotný převod PHP souborů je úplně jednoduchý. Stačí použít PHP 5.4 Short Arrays Converter a v repozitáři zavolat:

php /php54-arrays/convert.php .

Nástroj zamění syntax ve všech souborech *.php a *.phpt v aktuálním adresáři a také ve všech podadresářích. Změněné soubory jsem pak komitnul (příklad).

Oříšek je ale rebasování dalších větví na takto změněný master.

Nakonec jsem na to šel přes filtry. Ale plně zautomatizovat se mi to nepovedlo.

Nejprve je vhodné všechny větve rebasovat na master těsně před samotnou změnou syntaxe.

Poté jsem si vytvořil filtr nazvaný phparray, který bude on-the-fly překládat v PHP souborech [] na array() při checkoutu a obráceně při komitování. Tedy aby slučování probíhalo při použití staré syntaxe, ale komitnulo se s novou.

Filtr se vytvoří v souboru .git/config, v mém případě to vypadalo takto:

[filter "phparray"]
    clean = c:/php/php.exe w:/php54-arrays/convert.php
    smudge = c:/php/php.exe w:/php54-arrays/convert.php -r
    required

Aby se filtr při slučování používal (ale i při každém checkoutu, cherry-picku atd), je nutné doplnit do .git/config ještě následující:

[merge]
    renormalize = true

Filtr se bude aplikovat na soubory *.php a *.phpt, což se definuje v souboru .git/info/attributes (nepoužívejte .gitattributes, protože jde o dočasnou záležitost a nechceme ji komitovat):

*.php filter=phparray
*.phpt filter=phparray

Teď by ve větvi mělo fungovat git rebase master. Bez konfliktů, které by bylo nutné ručně řešit. Jenže ouha, konflikty se mi vytvářely (na Windows; je možné, že na Linuxu to půjde) a když jsem je chtěl řešit v TortoiseGit, objevovala se hláška, že na souboru .git/index.lock je zámek atd. Zkoušel jsem experimentovat s dalšími nastaveními pro merge, ale bez výsledku.

Zkusil jsem dělat rebase ručně: což znamená nejprve větev resetnout na master (git reset master --hard) a pak jednotlivé komity přidávat pomocí git cherry-pick <hash>. Nedělal jsem to z příkazové řádky, ale pomocí TortoiseGit. I nadále mi hlásil, že některé soubory neumí automaticky sloučit a je vyžadován ruční zásah, ale vždy stačilo soubor rozkliknout do TortoiseGitMerge a rovnou stisknout Mark as resolved.

Bylo to otravné, ale fungovalo to a postupně jsem „rebasoval“ všechny větve. Poté jsem filtr z .git/config a .git/info/attributes smazal.

Proč to nešlo úplně automaticky, nemám páru, nicméně bylo snazší hodinu klikat, než dva dny studovat Git.


PHP 5.4 Short Arrays Converter

Command-line script to convert between array() and PHP 5.4's short syntax []. It uses native PHP tokenizer, so conversion is safe. The script was successfully tested against thousands of PHP files.

Download from GitHub

To convert all *.php and *.phpt files in whole directory recursively or to convert a single file use:

php convert.php <directory | file>

To convert source code from STDIN and print the output to STDOUT use:

php convert.php < input.php > output.php

To convert short syntax [] to older long syntax array() use option --reverse:

php convert.php --reverse [<directory | file>]

Composer: jak na různé varianty instalace

Composer, nejdůležitější nástroj pro PHP vývojáře, umožňuje 3 způsoby, jak instalovat balíčky:

  • lokální composer require vendor/name
  • globální composer global require vendor/name
  • jako projekt composer create-project vendor/name

Lokálně

Lokální instalace se používá nejčastěji. Mám projekt, ve kterém chci třeba použít Tracy, tak v kořenovém adresáři projektu zadám:

composer require tracy/tracy

a Composer zaktualizuje (nebo vytvoří) soubor composer.json a stáhne Tracy do podsložky vendor. Zároveň vygeneruje autoloader, takže v kódu jej stačí inkludovat a můžu rovnou Tracy použít:

require __DIR__ . '/vendor/autoload.php';
Tracy\Debugger::enable();

Jako projekt

Diametrálně odlišná situace nastává tehdy, pokud místo knihovny, jejíž třídy ve svém projektu používám, instaluji nástroj, který jen spouštím z příkazové řádky.

Příkladem může být třeba ApiGen pro generování přehledných API dokumentací. V takovém případě se použije třetí způsob:

composer create-project apigen/apigen

Composer vytvoří novou složku (a tedy i nový projekt) apigen a do ní stáhne celý nástroj a nainstaluje jeho závislosti.

Bude mít vlastní composer.json a vlastní podsložku vendor.

Tímto způsobem se instaluje i třeba Nette Sandbox nebo CodeChecker. Nikoliv však testovací nástroje jako je Nette Tester nebo PHPUnit, protože jejich třídy naopak v testech používáme, voláme Tester\Assert::same() nebo dědíme od PHPUnit_Framework_TestCase.

Bohužel Composer umožňuje instalovat nástroje jako je ApiGen i pomocí composer require a nevypíše ani žádné varování.

Což je totéž, jako když donutíte dva vývojáře, kteří se ani neznají a kteří pracují na úplně jiném projektu, aby sdíleli společnou složku vendor. Na to se dá říci:

  • Proboha proč by to měli dělat?
  • Vždyť to přece nemůže fungovat!

Ano, není žádný rozumný důvod to dělat, nic to nepřinese, naopak to přestane fungovat v momentě, kdy dojde ke kolizi používaných knihoven. Je to jen otázka času, stavění domečku z karet, který se dřív nebo později sesype. Jeden projekt bude vyžadovat knihovnu XY ve verzi 1.0, druhý ve verzi 2.0 a v tu chvíli to přestane fungovat.

Globálně

Rozdíl mezi variantou 1) a 2), tj. mezi composer require a composer global require, je pak v tom, že nepůjde o dva cizí vývojáře, ale o deset cizích vývojářů a deset nesouvisejících projektů. Tedy je to nesmysl na druhou.

Totiž composer global je špatné řešení úplně vždy, neexistuje use case, kdy by bylo vhodné jej použít. Výhodou je jen to, že když si globální adresář vendor/bin přidáte do PATH, můžete snadno spouštět takto nainstalované knihovny.

Rekapitulace

  • composer require vendor/name pokud chcete používat třídy knihovny
  • composer global require vendor/name nikdy!
  • composer create-project vendor/name pro nástroje volané jen z příkazové řádky

Poznámka: npm používá odlišnou filosofii danou možnostmi JavaScriptu a každou knihovnu instaluje jako „samostatný projekt“, s vlastním adresářem vendor (resp. node_modules). Ke konfliktu verzí tak dojít nemůže. V případě npm naopak platí, že globální instalace nástrojů, jako je například LESS CSS, jsou velmi užitečná a příjemná věc.


Jak na GitHub a pull request z příkazové řádky

Tuhle jsem zveřejnil skript na cherry-pickování přímo z GitHubu, který dodnes používám, ale bylo otravné tím stahovat celé pull requesty, pokud obsahovaly víc komitů. Takže jsem ho naučil stahovat je na jeden zátah. Opět stačí jako argument uvést URL:

php pullpick.php https://github.com/nette/tracy/pull/58

Oproti cherry-picku je potřeba navíc zjistit zdrojový repozitář a větev, k čemuž použijeme GitHub API. Skript vypadá takto:

<?php
$url = @$_SERVER['argv'][1];

if (!preg_match('#github.com/([^/]+)/([^/]+)/pull/(\w+)#', $url, $m)) {
    die('Invalid URL');
}

list(, $name, $repo, $pull) = $m;

$context = stream_context_create(array('http' => array('user_agent' => 'Me')));
$info = file_get_contents("https://api.github.com/repos/$name/$repo/pulls/$pull", false, $context);
$info = json_decode($info);
if (!isset($info->head->repo->clone_url, $info->head->ref)) {
    die('Missing repo info.');
}
passthru("git checkout -b pull-$pull master");
passthru("git pull --no-tags {$info->head->repo->clone_url} {$info->head->ref}");

Pull request se stáhne do nové větve s názvem jako pull-123.

Mám i skript na vytvoření nového pull requestu. Spustíte jej ve větvi, ze které chcete PR vytvořit, bez parametrů. On větev pushne do vašeho forku a poté otevře prohlížeč s formulářem pro vytvoření pull requestu:

<?php
$remote = 'dg'; // tady dejte název 'remote' vedoucí k forku na GitHubu

exec('git remote -v', $remotes);

$repo = null;
foreach ($remotes as $rem) {
    if (preg_match('#^' . preg_quote($remote) . '\tgit@github.com:(.+)\.git \(#', $rem, $m)) {
        $repo = $m[1];
        break;
    }
}

if (!$repo) {
    die('Not Github repo');
}

exec('git rev-parse --abbrev-ref HEAD', $branch);
$branch = $branch[0];
if (!$branch) {
    die('Unable to retrieve branch name');
}

echo "Pushing to $repo & $branch\n";
exec("git push --set-upstream $remote $branch");

$url = "https://github.com/$repo/compare/$branch?expand=1";
exec('start "" ' . $url); // tohle otevře prohlížeč pod Windows. Pro jiné OS si upravte.

Proč Nette nedodržuje standardy PHP-FIG / PSR?

„Proč Nette musí jít za každou cenu proti proudu a odmítá respektovat standardy PHP-FIG?“

Tohle je sugestivní otázka, často kladená v souvislosti s tím, proč Nette odsazuje tabulátory, a nikoliv mezerami. Pojďme to rozebrat postupně.

V první řadě, PHP Framework Interoperability Group (PHP-FIG) netvoří obecné standardy, jde o soubor pravidel, která si tvoří určitá viditelná skupina vývojářů sama pro sebe. Ve FAQ se uvádí, že původní název „PHP Standards Group“ byl změněn právě proto, že byl zavádějící. Doslova říkají „pokud chcete dodržovat naše standardy, prosím, ale není to našim záměrem.

Jelikož naprostá většina pravidel coding standardů PHP-FIG se shoduje se standardem Nette, dá se říci, že Nette je, až na drobné výjimky, dodržuje také.

Dokument PSR-0 Autoloading Standard specifikuje, že každá třída musí být uložena v souboru, jehož cesta přesně odpovídá názvu třídy. Jelikož Nette disponuje pokročilejším autoloadingem (a výkonnějším, viz níže) než mají ostatní frameworky, nemusí se tohoto pravidla držet z technické nutnosti, nicméně jde o přehledný způsob organizace souborů, proto jej také používá. Jedinou odchylkou jsou definice výjimek, které je někdy praktičtější umístit do jednoho souboru – jsou tak pěkně pohromadě a např. adresář Utils zůstává výrazně přehlednější.

PSR-0 odchylky netoleruje, právě kvůli technickým omezením, které se Nette netýkají (a v dnešní době Composeru se netýkají vlastně žádné knihovny). Volání po rigidním dodržení PSR-0 by bylo zbytečným krůčkem zpět.

Dokument PSR-1 Basic Coding Standard definuje základní pravidla pro obsah PHP souboru, která se plně slučují s coding standardem Nette.

Dokument PSR-2 Coding Style Guide je obsáhlou definicí přesných pravidla pro zápis kódu. Takřka kompletně se slučuje se standardem Nette, krom těchto bodů:

  • zatímco PSR vyžaduje psát true, false, null malými písmeny, Nette (mimochodem v souladu s dokumentací PHP) používá velká písmena Nette již používá malá písmena
  • Nette odsazuje pomocí tabulátorů, PSR-2 vyžaduje mezery.

Dokument PSR-3 Logger Interface definuje rozhraní Psr\Log\LoggerInterface, čímž opouští roli kodifikátora pravidel psaní kódu. Dle mého je rozhraní definováno špatně a řeší problém, který neexistuje. Ale tohle téma nechci probírat, ani v komentářích. Můžete si přečíst třeba názor ircmaxella + pokračování. Chystaný PSR-11 Container Interface je pak příkladem naprostého nepochopení Dependency Injection.

Dokument PSR-4 Autoloader určuje, jak má být implementovaný autoloader pro PSR-0. Jak jsem zmiňoval, tohle téma je pro Nette a Composer passé. Navíc se v praxi ukázalo, že jde o řešení s negativním dopadem na výkon, pročež Composer nabízí volbu --optimize, která převede PSR-0 na classmap, kterou právě používá Nette RobotLoader.

Když už je shoda mezi PSR a standardem Nette tak velká, proč nezměnit těch pár drobností a nebýt 100% vyhovující? Je v tom tvrdohlavost? Nebo zmíněná snaha jít proti proudu?

Ani jedno. Ohledně znaku pro odsazování jsem se po celoživotním používání mezer nechal netvrdohlavě přesvědčit a přešel na tabulátory. Stalo se tak rok před vznikem PHP-FIG, tedy onoho proudu, proti kterému bych měl jít.

Zajímavé je, že dokonce i řada samotných členů PHP-FIG používají tabulátory. Jeden ze zakladatelů Paul M. Jones mi k tomu napsal: „Yes, the vote was roughly 2:1 in favor of spaces. The tab folk are as a result not PSR-2 compliant.“

Jednoduše prostě neznám důvod, proč takhle zasáhnout do kódu a leccos tím zkomplikovat. Nemáme jen jeden jediný standard, tabulátory používá třetina všech projektů. Navíc otázka tabulátoru vs mezery je naprosto malicherná a nemá naprosto žádný vliv na interoperabilitu knihoven.

Jediné podstatné je být konzistentní a v celém kódu dodržovat stejná pravidla. Nesmí být poznat, že kód psalo více lidí. Což Nette striktně odjakživa respektuje.


Jak v PHP detekovat chybu? No těžko…

Do žebříčku 5 největších zrůdností jazyka PHP rozhodně patří nemožnost zjistit, zda volání nativní funkce skončilo úspěchem, nebo chybou. Ano, čtete správně. Zavoláte funkci a nevíte, zda došlo k chybě a k jaké.

Teď si možná klepete na čelo a říkáte: selhání přece poznám podle návratové hodnoty, ne? Hmmm…

Návratová hodnota

Nativní (nebo interní) funkce obvykle vracejí v případě neúspěchu false. Jsou tu výjimky, například json_decode, která vrací null, pokud je vstup nevalidní nebo překročí limit zanoření. Což najdeme v dokumentaci, potud ok.

Tato funkce slouží k dekódování JSONu i jeho hodnot, tedy volání json_decode('null') také vrátí null, tentokrát ale jako korektní výsledek. Musíme tedy rozlišovat null jakožto správný výsledek a null jakožto chybu:

$res = json_decode($s);
if ($res === null && $s !== 'null') {
    // došlo k chybě
}

Je to hloupé, ale pámbů zaplať, že to vůbec lze. Existují totiž funkce, u kterých nelze z návratové hodnoty poznat, že k chybě došlo. Např. preg_grep nebo preg_split vrací částečný výsledek, tedy pole, a nepoznáte vůbec nic (více v Zrádné regulární výrazy).

json_last_error & spol.

Funkce informující o poslední chybě v určitém rozšíření PHP. Bohužel bývají mnohdy nespolehlivé a je obtížné zjistit, co to vlastně ta poslední chyba je.

Například json_decode('') neresetuje příznak poslední chyby, takže json_last_error vrací výsledek nikoliv pro poslední, ale pro nějaké předchozí volání json_decode (viz How to encode and decode JSON in PHP?). Obdobně ani preg_match('neplatývýraz', $s) neresetuje preg_last_error. Pro některé chyby nemají tyto funkce kód, takže je vůbec nevrací, atd.

error_get_last

Obecná funkce vracející poslední chybu. Bohužel je nesmírně komplikované zjistit, zda se chyba týkala vámi volané funkce. Onu poslední chybu totiž mohla vygenerovat úplně jiná funkce.

První možností je přihlížet ke error_get_last() jen ve chvíli, kdy návratová hodnota značí chybu. Bohužel třeba funkce mail() umí vygenerovat chybu, i když vrátí true. Nebo naopak preg_replace v případě neúspěchu nemusí chybu generovat vůbec.

Druhou možností je před voláním naší funkce „poslední chybu“ vyresetovat:

@trigger_error('', E_USER_NOTICE); // reset

$file = fopen($path, 'r');

if (error_get_last()['message']) {
    // došlo k chybě
}

Kód je zdánlivě jasný, chyba může vzniknout pouze při volání funkce fopen(). Ale není tomu tak. Pokud je $path objekt, bude převeden na řetězec metodou __toString. Pokud je to jeho poslední výskyt, bude volán i destruktor. Mohou se volat funkce URL wrapperu. Atd.

Tedy i zdánlivě nevinný řádek může vykonat spoustu PHP kódu, který může generovat jiné chyby, z nichž poslední pak vrátí error_get_last().

Musíme se proto ujistit, že k chybě došlo skutečně při volání fopen:

@trigger_error('', E_USER_NOTICE); // reset

$file = fopen($path, 'r');

$error = error_get_last();
if ($error['message'] && $error['file'] === __FILE__ && $error['line'] === __LINE__ - 3) {
    // došlo k chybě
}

Ona magická konstanta 3 je počet řádků mezi __LINE__ a voláním fopen. Prosím bez komentáře.

Tímto způsobem už chybu odhalíme (tedy pokud ji funkce emituje, což třeba zmíněné funkce pro práci s regulárními výrazy zpravidla nedělají), ale nejsme schopni ji potlačit, tedy zabránit tomu, aby se zalogovala apod. Použití například shut-up operátoru @ je problematické v tom, že zatají vše, tedy veškerý další PHP kód, který se v souvislosti s naší funkcí volá (viz zmíněné destruktory, wrappery atd.).

Vlastní error handler

Šíleným, ale zřejmě jediným možným způsobem, jak zjistit, zda určitá funkce vyhodila chybu s možností ji potlačit, je instalace vlastního chybového handleru pomocí set_error_handler. Jenže není sranda to udělat správně:

  • vlastní handler musíme také odstranit
  • musíme jej odstranit i v případě, že se vyhodí výjimka
  • musíme zachytávat skutečně jen chyby vzniklé v inkriminované funkci
  • a všechny ostatní předat původnímu handleru

Výsledek vypadá takto:

$prev = set_error_handler(function($severity, $message, $file, $line) use (& $prev) {
    if ($file === __FILE__ && $line === __LINE__ + 9) { // magická konstanta
        throw new Exception($message);
    } elseif ($prev) { // volej předchozí uživatelský handler
        return call_user_func_array($prev, func_get_args());
    }
    return false; // volej systémový handler
});

try {
    $file = fopen($path, 'r');  // o tuhle funkci nám jde
    restore_error_handler();
} catch (Exception $e) {
    restore_error_handler();
    throw $e;
}

Co je magická konstanta 9 už víte.

No a tak my v PHP žijem, no.


Jak komitovat se záruční smlouvou?

Už jsem odpověděl na spoustu pull requestů „Can you add tests?“ Ale ne proto, že bych byl testofil, nebo abych dotyčného buze prudil.

Pokud posíláte pull request, který opravuje nějakou chybu, tak pochopitelně musíte před odesláním vyzkoušet, jestli skutečně funguje. Kolikrát si člověk myslí, že něco snadno fixne a ejhle, rozbije to ještě víc. Nechci se opakovat, ale tím, že to vyzkoušíte, jste vyrobili test, tak ho jen přiložte.

(Bohužel někteří lidé svůj kód doopravdy nevyzkouší. Kdyby to šlo, dával bych měsíční bany za pull requesty vytvořené přímo ve webovém editoru Githubu.)

Ale to stále není ten nejhlavnější důvod: Test je jediná záruka, že vaše oprava bude fungovat v budoucnu.

Už mnohokrát se stalo, že někdo poslal pull request, který mi nebyl užitečný, ale upravoval funkcionalitu důležitou pro něj. Zejména pokud to byl někdo, koho znám, a vím, že je dobrý programátor, tak jsem to mergnul. Pochopil jsem, k čemu to chce, nevadilo to ničemu jinému, tak jsem PR přijal a v tu chvíli vypustil z hlavy.

Pokud svůj pull request doplnil testem, tak jeho kód dodnes funguje a bude fungovat i nadále.

Pokud ho testem nedoplnil, tak se klidně může stát, že mu to nějaká další úprava rozbije. Ne schválně, prostě se to stane. Nebo už se to stalo. A nemá smysl láteřit, jaký jsem vůl, že jsem mu už potřetí rozbil jeho kód, ačkoliv jsem před 3 lety přijal jeho pull request, si to snad musím pamatovat né, takže mu to snad dělám naschvál… Nedělám. Nikdo si nepamatujeme, co jsme měli před třemi lety na svačinu.

Pokud vám na nějaké funkcionalitě záleží, přiložte k ní test. Pokud vám na ni nezáleží, vůbec ji neposílejte.


phpFashion © 2004, 2019 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.