Blog: Co se nevešlo na Twitter - PHP

Publikování na facebooku jako stránka

Včera se za mnou stavil kamarád s úplatky, že by potřeboval pomoct rozběhat automatické přispívání na Facebook Page. Protože Facebook zase nedávno udělal novou verzi api, tentokrát v2.3, tak spousta návodů jak publikovat na stránky jménem stránky je trošku obsolete a hlavně, ještě nikdo nenapsal návod pro Kdyby/Facebook :) Tak jsem na to s ním sedl, opravil pár compatibility drobností v Kdyby/Facebook a během půl hodinky nám to fungovalo.

Pokračovat ve čtení ...

Presentery v DI Containeru

V Nette Frameworku funguje instanciování presenterů tak, že v PresenterFactory se nějak přeloží název presenteru v "Nette tvaru", třeba Front:Homepage na název třídy, třeba na FrontModule\HomepagePresenter (což závisí na konvenci a jde to samozřejmě změnit).

V tenhle moment známe název třídy, která se má instanciovat a spustit. Jenže tato třída má nějaké závislosti a je potřeba správně vytvořit instanci a tyto závislosti předat. Jak na to? Dříve to fungovalo tak, že se prostě přes reflexi kouklo na konstruktor a proběhl autowire.

Před pár měsíci (nebo roky?) byla přidána podpora pro "vytahování" instancí presenterů z DI Containeru. Což je strašně fajn, protože si pak můžete presentery zaregistrovat do DI Containeru. Analýza závislostí se pak provede compile-time, tedy právě jednou a výkon i čas sežere také právě jednou.

Pokračovat ve čtení ...

Kdyby/Monolog + syslog + papertrail

U každé aplikace, která vydělává nějaké peníze, dříve nebo později zjistíte, že potřebujete logovat někam co se děje a co kdo udělal. Někteří k tomu používají databázi a _audit tabulky, ale ty se nehodí na všechno. Někdy je prostě jednodušší to nasázet do souborů, a pak už se k nim nikdy nevrátit, protože to nikoho nebaví chodit kontrolovat :) Ale co si budem, v té databázi by to taky nikdo nekontroloval :)

Kombinaci nástrojů z titulku používáme v DameJidlo.cz i Rohlik.cz a budu ji rozhodně používat všude kde to půjde, pojďme si ukázat proč a jak :)

Pokračovat ve čtení ...

Kdyby/RabbitMq aneb asynchronní Kdyby/Events

Pokud právě čtete tento článek, tak jste jistě četli i Eventy a Nette Framework a pokud ne, tak doporučuji si na něj odskočit, já tu počkám.

Jak teď už všichni víme, Eventy jsou strašně silný nástroj, jenže i s Eventy se můžeme dostat do situace, že toho už sice nedělají příliš naše třídy, ale tentokrát celá aplikace. Takové poslání emailu, komunikace s platební bránou, poslání smsky, komunikace s externím pokladním API a kdo ví co ještě nějaký čas zabere.

Nebylo by fajn, kdyby se některé naše eventy zpracovaly asynchronně, na pozadí, abychom nezdržovali uživatele? Tak přesně od toho je RabbitMQ a tedy i Kdyby/RabbitMq.

Nebudu vás ale zatěžovat teorií, tu sepsal už Jakub Kohout i s přehledem několika šikovných nástrojů, které se vám budou hodit. Tak až si to přečtete, pojďme rovnou skočit rovnýma nohama do praxe.

Pokračovat ve čtení ...

Kniha: Persistence in PHP with Doctrine ORM

book-doctrine-orm

Před pár měsíci mi www.packtpub.com napsali (jako jednomu z mnoha), jestli bych pro ně nechtěl napsat knihu o Doctrine 2 ORM. Byť o Doctrine něco málo vím, nepřipadal jsem si jako vhodný kandidát, tak jsem to odmítl.

Myslím, že jsem udělal dobře, protože ji nakonec napsal Kévin Dunglas, člověk který přispívá do opensource projektů a ekosystémů jako jsou Symfony, JavaScript a Ubuntu.

Pokračovat ve čtení ...

MySQL fulltext: prosil bych jeden čaj

Mějme tabulku jídel na kterou chceme napsat hledání.

CREATE TABLE `food` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_czech_ci NOT NULL,
  `description` text COLLATE utf8_czech_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;

INSERT INTO `food` (`name`, `description`) VALUES
('Semtex', 'energiťák'), ('Čaj', 'heřmánkovej');

Máme dvě možnost, buďto použijeme search engine (ElasticSearch, Sphinx, ...) nebo se s tím budeme srát v MySQL. No a aby to bylo zajímavé, tak se s tím pojďme srát :)

Pokračovat ve čtení ...

Obarvěte si Adminer

Na desktopových klikátkách na databáze jste už určitě viděli obarvené záložky podle cíle spojení.

adminer-colored_navicat Obarvené záložky v Navicatu

Na co je to dobré? No tak například nemusíte číst název spojení a hned víte kde jste. Já barvičky použivám na odlišení "důležitosti" databáze - localhost/dev/produkce.

Jenže nepoužívám Navicat ale Adminer.

Pokračovat ve čtení ...

Aop v Nette Frameworku

Znáte termín Aspektově orientované programování Stejně jako u "Kdyby/Events":/blog/eventy-a-nette-framework, pointou je rozbít systém na menší logické celky, ovšem každý přístup to dělá maličko jinak.

Hranice mezi Eventy a AOP je strašlivě tenká a rozhodnout se který přístup v konkrétním případě použít nemusí být vůbec lehké. A aby to náhodou nebylo moc jednoduché, tak Eventy jsou teoreticky nahraditelné AOPčkem, ale naopak to nejde.

AOP má simulovat skládání různých chování (behaviour) do jednoho objektu bez mnohonásobné dědičnosti z venku, aniž by o tom tento objekt věděl. Kdežto událostí je si sám vědom, protože to on je vyvolává, ale už neví o listenerech, které na ně naslouchají.

Pokračovat ve čtení ...

NewRelic: monitoring aplikace na Nette Frameworku

Má Vaše aplikace víc než pět návštěv denně? Pak není od věci nějakým způsobem monitorovat, co se děje. Za tímhle účelem vznikají nejrůznější placené i opensource řešení. Některé lepší, některé horší. Několik měsíců zpátky jsem řešil, jaký monitoring nasadím na svoje sexy VPS od Wedosu (na kterém běží i tento blog).

Nebudu to protahovat, zvolil jsem nakonec NewRelic, který mi poradil Honza Doleček. Jeho jediné mínus je, že je docela drahý. Ale po měsíci trial verze a tričku zdarma už se mi nechtělo nikam migrovat.

Pak jsem dlouho monitoring neřešil a teď máme NewRelic i v Damejidlo.cz. Na své VPS mám pár malých webíků, ale tady už začíná být kritické, mít vše pod dohledem.

Před pár dny jsem objevil killer feature NewRelicu a o tu bych se s Vámi chtěl zde podělit. Je to jeho PHP API. Abych to trošku rozvedl, NewRelic se instaluje tak, že do phpčka zavedete modul a nastavíte IDčko aplikace. V ten moment začne rozsíření odesílat data na jejich servery a já se můžu kochat krásnými grafy :)

newrelic-overview

A to prý je Doctrine2 pomalá ;)

Stejným způsobem, prostou instalací balíku, jde rozchodit i monitoring systému.

newrelic-pizza-overview

Náš dev server se moc nenadře :)

Pokračovat ve čtení ...

Eventy a Nette Framework

Vyčleňuji právě svoji integraci Doctrine do Nette Frameworku a jedna její část řeší údálosti.

Doctrine má na události jednoduchý systém - existuje třída EventManager, do které se registrují listenery a když se "něco stane", vyvoláme nad ní událost a ta se předá příslušným listenerům. Pro detaily si můžete odskočit do podrobné dokumentace.

Nette Framework má také události. Používáte je nejspíše každý den ve formulářích, když nastavujete $form->onSuccess[] = $callback;.

A mě napadlo: co kdybych to sjednotil?

(Pro plné pochopení článku je nutné znát použití obou systémů, tak si to skočte přečíst, já tu počkám)

Pokračovat ve čtení ...

Debuggujeme s PhpStormem

Nejprve si nainstalujeme a nastavíme Xdebug, pomocí pecl, který by měl být součastí všech instalací PHP.

$ sudo pecl install xdebug

Dále nás zajímá, odkud bere PHP konfiguraci

$  php -i |grep ini
Configuration File (php.ini) Path => /usr/local/lib

V mém případě složka obsahuje několik .ini souborů

$ ls /usr/local/lib |grep php
php-cli.ini
php-fpm.ini
php.ini

Do všech těchto souborů zkopírujeme následující řádky na úplný konec (většinou jsou nutná root práva).

[xdebug]
zend_extension=xdebug.so

xdebug.remote_enable=1
xdebug.remote_connect_back=On
; xdebug.remote_host=127.0.0.1
; xdebug.remote_port=9001
xdebug.remote_autostart=1
xdebug.remote_log="/var/log/php/xdebug.log"
xdebug.idekey=PHPSTORM
; xdebug.profiler_enable=1
; xdebug.profiler_output_dir=/tmp/xdebug-profiler

Tohle nastaví Xdebug na velice agresivní režim. Na zbytečné rozšíření do prohlížeče (pokud jste nějaké používali) zapomeňte, nejsou potřeba - ukážeme si za moment.

Může se nám také stát, že nějaká aplikace nebo služba bude sedět na portu 9000, který je standardní pro Xdebug - od toho je tu xdebug.remote_port.

Každý operační systém má konfiguraci trošku jinak. Pokud máte Xdebug již nainstalovaný a jste zvyklí ho konfigurovat jinak, tak nejdůležitější jsou tyto volby.

xdebug.remote_enable=1
xdebug.remote_connect_back=On
xdebug.remote_autostart=1

Nezapoměňte restartovat apache, nebo php-fpm ;)

Pokračovat ve čtení ...

Composer a PhpStorm

Composer je skvělý nástroj na správu závislostí pro PHP. A PhpStorm je docela kvalitní (ale hlavně rychlé) IDE. Když se sejdou dva takhle užitečné nástroje, někoho by napadlo, že by mohly spolupracovat.

O nativní podporu Composeru v PhpStormu se již snažíme a s trochou optimismu by to příští Vánoce mohlo být hotové ;) Ale někdo to prostě nevydrží a podporu si přidá sám. Za nápad moc děkuji Vojtěchovi

Přes External Tools jde velice snadno vytvořit klikátka na externí nástroje.

phpstorm-tools-composer

Které jdou spouštět z různých kontextových nabídek

phpstorm-tools-composer1

A výsledek operace zobrazí tak jako v konzoli

phpstorm-tools-composer-run

Kde stažení: "phpstorm-tools.jar":/content/phpstorm-tools.jar (File > Import Settings)

Další zajímavý způsob integrace je použít "Command line tool support". Více na PhpStorm blogu.

Jaké nástroje máte v PhpStormu (nebo v jiném IDE) podobně integrované vy?

Pokračovat ve čtení ...

Barvičky v RSS

Chcete mít krásný výstup v RSS čtečkách? Já taky!

FSHL generuje do "zvýrazněného" kódu "pouze" css třídy. To má zjevné výhody - stylovat můžete z jednoho místa, v CSS souboru, a ve výsledném kódu pak není zbytečný bordel. Takový obarvený kód je pak na webu krásný, ale v RSS čtečkách už to taková sláva není, protože ty neví co znamenají naše CSS třídy.

Nejprve mě napadlo zkusit posílat i CSS styly, vždy na konci článku. Jenže lenost testovat, jestli to funguje, zvítězila a raději třídy nahrazuji přímo inline stylem.

"Do své entity, která mi představuje článek"((já vím, že to není ideální, ale v systému tohoto blogu je to good-enought místo)), jsem si tedy přidal metodu, která příjme cestu k CSS souboru a všechny CSS třídy z HTML kódu nahradí jejich stylem z předaného souboru.

/** @var array */
private static $languages = array(
    'php', 'neon', 'config', 'sh', 'texy', 'js', 'css', 'sql', 'html'
);

/**
 * @param string $cssFile
 * @return string
 */
public function getRssContent($cssFile = NULL)
{
    if (!$cssFile) {
        return $this->htmlContent;
    }

    $cssDefs = file_get_contents($cssFile);
    $langs = self::$languages;
    return Strings::replace($this->htmlContent, '~class=(?:"|\')?([^"\'>]+)(?:"|\')?~i', function ($class) use ($cssDefs, $langs) {
        $style = NULL;
        foreach (Strings::split($class[1], '~\s+~') as $class) { // jednotlivé třídy
            if (count($parts = explode('-', $class, 2)) !== 2 || !in_array($parts[0], $langs)) {
                // pokud třída není ve tvaru "<jazyk>-<klíčové slovo>", tak přeskoč
                // pokud jazyk není ve slovníku, tak přeskoč
                continue;
            }

            if ($css = Strings::match($cssDefs, '~.' . preg_quote($class) . '\s*\{([^}]*?)\}~')) {
                // nahrazení stylem ze souboru
                $style .= Strings::replace($css[1], array('~[\n\r]+~' => '')) . ';';
            }
        }

        return $style ? 'style="' . htmlspecialchars($style, ENT_QUOTES) . '"' : NULL;
    });
}

A jak obarvujete kód ve svých RSS vy? :)

Pokračovat ve čtení ...

Hrátky s Texy! na blog

Davídek nám ukázal, jak má nastavené texy na http://nette.org, takže jsem toho využil a napsal si nad Texy! vrstvičku.

Stará se o zvýrazňování kódu a taky zpracovává magické "meta makra". Potřeboval jsem také, aby do zvárazněného kódu generoval seznamy, tak jako to ve své dokumentaci dělá Twitter Bootstrap, takže jsem povolil ol v elementu pre.

Další požadavek, na kterém jsem se docela zapotil, bylo odstraňování hlavního nadpisu z výsledného kódu a kontrola, jestli obsahuje odkaz. Chci si totiž nadpis renderovat nad článkem zvlášť sám, kvůli tomu, aby se stejný HTML kód dal použít v RSS a nebyl v něm 2x nadpis. Implementace je dost naivní, ale dělám to pro sebe, tak si budu muset pamatovat, že to funguje pouze pokud odkaz obaluje celý obsah nadpisu.

Tohle fungovat bude

"**nadpis**":http://example.com
***

ale tohle už fungovat nebude

**"nadpis":http://example.com**
***

Texy! je nastaveno s vědomím, že výsledek bude na mém blogu - dovolí mi skoro vše.

use Nette\Utils\Html;
use Nette\Utils\Strings;

class Processor extends Nette\Object
{
    /** @var \Texy */
    private $lastTexy;

    /** @var \FSHL\Highlighter */
    private $highlighter;

    /** @var array */
    private $meta = array();

    /** @var string */
    private $title;

    /** @var array */
    private static $highlights = array(
        'block/code' => TRUE,
        'block/php' => 'FSHL\Lexer\Php',
        'block/neon' => 'FSHL\Lexer\Neon',
        'block/config' => TRUE, // @todo
        'block/sh' => TRUE, // @todo
        'block/texy' => TRUE, // @todo
        'block/javascript' => 'FSHL\Lexer\Javascript',
        'block/js' => 'FSHL\Lexer\Javascript',
        'block/css' => 'FSHL\Lexer\Css',
        'block/sql' => 'FSHL\Lexer\Sql',
        'block/html' => 'FSHL\Lexer\Html',
        'block/htmlcb' => 'FSHL\Lexer\Html',
    );

    public function __construct(\FSHL\Highlighter $highlighter)
    {
        $this->highlighter = $highlighter;
    }

    public function process($text)
    {
        $this->meta = array();
        $this->title = array('link' => NULL, 'heading' => NULL, 'el' => NULL);
        return $this->createTexy()->process($text);
    }

    public function getMeta()
    {
        return $this->meta;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function getLastTexy()
    {
        return $this->lastTexy;
    }

    protected function createTexy()
    {
        $texy = new \Texy();

        // obecné nastavení
        $texy->allowedTags = \Texy::ALL;
        $texy->linkModule->root = '';
        $texy->tabWidth = 4;
        $texy->phraseModule->tags['phrase/strong'] = 'b';
        $texy->phraseModule->tags['phrase/em'] = 'i';
        $texy->phraseModule->tags['phrase/em-alt'] = 'i';

        // nadpisy
        $texy->headingModule->top = 1;
        $texy->headingModule->generateID = TRUE;
        $texy->addHandler('afterParse', array($this, 'headingHandler'));

        // čísla řádků pro twitter bootstrap
        $texy->dtd['pre'][1]['ol'] = 1;

        // vypne generování bílých znaků ve výsledném kódu,
        // aby se neroztahoval kód v elementu <pre>
        $texy->htmlOutputModule->indent = FALSE;

        // <code>
        $texy->addHandler('block', array($this, 'blockHandler'));

        // meta
        $texy->registerBlockPattern(
            array($this, 'metaHandler'),
            '#\{\{([^:]+):([^:]+)\}\}$#m', // block patterns must be multiline and line-anchored
            'metaBlockSyntax'
        );

        // return
        return $this->lastTexy = $texy;
    }

    /**
     * Metoda vykuchá element hlavního nadpisu z výsledného HTML
     * a taky koukne, jeslti nadpis neobsahuje odkaz.
     * @internal
     */
    public function headingHandler(\Texy $texy, \TexyHtml $DOM, $isSingleLine)
    {
        list($title) = $texy->headingModule->TOC;

        // zkopírovat element
        $titleEl = Html::el($title['el']->getName(), $title['el']->attrs);
        foreach ($title['el']->getChildren() as $child) {
            $titleEl[] = $child;
        }

        // uklidit
        $title['el']->attrs = array();
        $title['el']->removeChildren();
        $title['el']->setName(NULL);

        // parsování odkazu
        foreach ($titleEl->getChildren() as $i => $child) {
            $matches = Strings::matchAll(
                $texy->unProtect($child), // texy magie
                '~<([\\w]+)([^>]*?)(([\\s]*\/>)|(>((([^<]*?|<\!\-\-.*?\-\->)|(?R))*)<\/\\1[\s]*>))~sm',
                PREG_OFFSET_CAPTURE
            );
            if (!$matches) break;
            list($tag) = $matches;

            $titleEl[$i] = $el = Html::el($tag[1][0] . ' ' . $tag[2][0]);
            $el->setHtml($tag[6][0]);

            if ($el->getName() === 'a') {
                $this->title['link'] = $el->attrs['href'];
            }
        }

        // obsah nadpisu
        $this->title['heading'] = $titleEl->getText();
        $this->title['el'] = $titleEl;
    }

    /**
     * Parsuje meta značky {{key: value}}
     * @internal
     */
    public function metaHandler(\TexyParser $parser, array $matches, $name)
    {
        list(, $metaName, $metaValue) = $matches;
        $this->meta[] = array(
            trim(Strings::normalize($metaName)),
            trim(Strings::normalize($metaValue))
        );
    }

    /**
     * Zýrazňuje kód
     * @internal
     */
    public function blockHandler(\TexyHandlerInvocation $invocation, $blockType, $content, $lang, $modifier)
    {
        if (isset(self::$highlights[$blockType])) {
            list(, $lang) = explode('/', $blockType);
        } else {
            return $invocation->proceed($blockType, $content, $lang, $modifier);
        }

        $texy = $invocation->getTexy();
        $content = \Texy::outdent($content);

        // zvýraznění syntaxe
        if (class_exists($lexerClass = self::$highlights[$blockType])) {
            $content = $this->highlighter->highlight($content, new $lexerClass());
        } else {
            $content = htmlspecialchars($content);
        }

        $elPre = \TexyHtml::el('pre');
        if ($modifier) $modifier->decorate($texy, $elPre);
        $elPre->attrs['class'] = 'src-' . strtolower($lang) . ' prettyprint linenums';

        // čísla řádků
        $elOl = $elPre->create('ol', array('class' => 'linenums'));
        foreach (Strings::split($content, '~[\n\r]~') as $i => $line) {
            $elLi = $elOl->create('li', array('class' => 'L' . $i));
            $elLi->create('span', $texy->protect($line, \Texy::CONTENT_BLOCK));
        }

        return $elPre;
    }

}

Kvůli tomu, že každý řádek nyní obaluji prvkem <li>, je potřeba upravit FSHL, aby se nám nekřížily tagy přes řádek.

class FshlHtmlOutput implements \FSHL\Output
{
    private $lastClass = null;

    public function template($part, $class)
    {
        $output = '';
        if ($this->lastClass !== $class) {
            if (null !== $this->lastClass) $output .= '</span>';
            if (null !== $class) $output .= '<span class="' . $class . '">';
            $this->lastClass = $class;
        }
        $part = htmlspecialchars($part, ENT_COMPAT, 'UTF-8');
        if ($this->lastClass && strpos($part, "\n") !== FALSE) {
            $endline = "</span>\n" . '<span class="' . $this->lastClass . '">';
            $part = str_replace("\n", $endline, $part);
        }
        return $output . $part;
    }

    public function keyword($part, $class)
    {
        $output = '';
        if ($this->lastClass !== $class) {
            if (null !== $this->lastClass) $output .= '</span>';
            if (null !== $class) $output .= '<span class="' . $class . '">';
            $this->lastClass = $class;
        }
        return $output . htmlspecialchars($part, ENT_COMPAT, 'UTF-8');
    }
}

Výsledný Processor pak používám následovně

$processor = new Processor(new FSHL\Highlighter(new FshlHtmlOutput()));
$html = $processor->process($texy);
$meta = $processor->meta;

Byl jsem krapet v šoku, když jsem zjistil, že tento krásný blok s kódem není ve standardní distribuci Twitter Bootstrap. Kdo je líný kuchat to z jejich webu, tak CSS je zde:

.prettyprint {
    padding: 8px; background-color: #f7f7f9; border: 1px solid #e1e1e8;
}
.prettyprint.linenums {
    -webkit-box-shadow: inset 45px 0 0 #fbfbfc, inset 46px 0 0 #ececf0;
    -moz-box-shadow: inset 45px 0 0 #fbfbfc, inset 46px 0 0 #ececf0;
    box-shadow: inset 45px 0 0 #fbfbfc, inset 46px 0 0 #ececf0;
}
ol.linenums {
    margin: 0 0 0 43px; /* IE indents via margin-left */
}
ol.linenums li {
    padding-left: 6px; color: #bebec5; line-height: 20px; text-shadow: 0 1px 0 #fff;
}
ol.linenums li > span {
    color: black;
}

Pokračovat ve čtení ...