Nette a Doctrine

Účast na mé první Posobotě předčila veškeré očekávání. Perfektní workshop, skvělé přednášky a úžasní lidé kteří nic netají. Na workshopu jsem díky Filipovi a Chemixovi pronikl do základů Doctrine. Zkusím tyto informace sepsat.

Instalujeme Nette

Ani nemyslete na to že to budete zkoušet bez composeru 🙂

composer create-project nette/sandbox nette-doctrine

nette-doctrine je název složky do které se sandbox vytvoří.

Instalujeme Kdyby/Doctrine

cd nette-doctrine
composer require kdyby/doctrine

Pokud máte PHP verzi alespoň 5.4 hned z konzole můžete spustit web server:

php -S localhost:8888 -t www

webserver se spustí na portu 8888. Pokud do prohlížeče zadáte http://localhost:8888 uvidíte stránku Nette Congratulations!

Práda! Můžeme začít spouštět IDE a zažneme kódovat.

PhpStorm a live templaty pro Doctrine

Rozhodně doporučuji PhpStorm do kterého Filip napsal skvělé live templaty. Ušetří spoustu psaní a času.

Live templaty stáhněte a nahrajte do:

<your home directory>\.WebIde<version>\config\templates

Rozhodně doporučuji soubory: Doctrine.xml, Nette.icls, Nette.xml

Po nahrání je nutné znovu spustit PhpStorm.

Vytvoření databáze

Pojďme si vytvořit databázi a uživatele pro přístup k databázi:

CREATE DATABASE `doctrine_devel` COLLATE 'utf8_czech_ci';
CREATE USER 'doctrine'@'localhost' IDENTIFIED BY 'doctrine_pass';
GRANT USAGE ON * . * TO 'doctrine'@'localhost' IDENTIFIED BY 'doctrine_pass' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;
GRANT ALL PRIVILEGES ON `doctrine\_%` . * TO 'doctrine'@'localhost';
FLUSH PRIVILEGES;

Příkaz vykonáme v Admineru: http://localhost:8888/adminer

Klikneme na tlačítko SQL příkaz. Do okna vložíme kód výše a jeho vykonání potvrdíme tlačítkem Provést.

Tím jsme vytvořili databázi doctrine_devel a uživatele doctrine s heslem doctrine_pass.

Zaregistrování Doctrine do Nette

Do config.neon zamergujeme následující kód:

parameters:
    database:
        host: localhost
        dbname: production_db
        user: production
        password: production_pass

extensions:
    console: Kdyby\Console\DI\ConsoleExtension
    events: Kdyby\Events\DI\EventsExtension
    annotations: Kdyby\Annotations\DI\AnnotationsExtension
    doctrine: Kdyby\Doctrine\DI\OrmExtension

doctrine:
    dbname      : %database.dbname%
    host        : %database.host%
    user        : %database.user%
    password    : %database.password%
    metadata:
        App: %appDir%

Do config.local.neon uložíme přístupové údaje do DB pro vývoj u nás na localhostu:

parameters:
    database:
        host: localhost
        dbname: doctrine_devel
        user: doctrine
        password: doctrine_pass

Podle dokuentace Kdyby/Doctrine si vytvoříme první entitu:

app/model/Article.php

<?php

namespace App;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Article extends \Kdyby\Doctrine\Entities\BaseEntity
{

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $title;

}

V entitě Article definuje id (celé číslo, které se bude inkrementovat) a title (text).

Vytvoření tabulky z entity

V první řadě ověříme zda funguje Doctrine/Console:

php ./www/index.php

Výstup by měl vypadat takto a měl by zobrazovat všechny dostupné příkazy pro Doctrine:

D:\www\nette-doctrine>php ./www/index.php
Nette Framework version 2.2.7

Usage:
 [options] command [arguments]

Options:
 --help (-h)           Display this help message
 --quiet (-q)          Do not output any message
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
 --version (-V)        Display this application version
 --ansi                Force ANSI output
 --no-ansi             Disable ANSI output
 --no-interaction (-n) Do not ask any interactive question

Available commands:
 help                       Displays help for a command
 list                       Lists commands dbal
 dbal:import                Import SQL file(s) directly to Database. orm
 orm:clear-cache:metadata   Clear all metadata cache of the various cache drivers.
 orm:clear-cache:query      Clear all query cache of the various cache drivers.
 orm:clear-cache:result     Clear all result cache of the various cache drivers.

 orm:convert-mapping        Convert mapping information between supported formats.
 orm:convert:mapping        Convert mapping information between supported formats.
 orm:generate-entities      Generate entity classes and method stubs from your mapping information.
 orm:generate-proxies       Generates proxy classes for entity classes.
 orm:generate:entities      Generate entity classes and method stubs from your mapping information.
 orm:generate:proxies       Generates proxy classes for entity classes.
 orm:info                   Show basic information about all mapped entities
 orm:schema-tool:create     Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
 orm:schema-tool:drop       Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output.
 orm:schema-tool:update     Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.
 orm:validate-schema        Validate the mapping files.

Provedeme validaci:

php ./www/index.php orm:validate-schema

OK, to je v pořádku:

[Mapping]  OK - The mapping files are correct.
[Database] FAIL - The database schema is not in sync with the current mapping file.

Podíváme se jaký SQL kód nám Doctrine nabízí pro synchronizaci databáze:

php ./www/index.php orm:schema-tool:update --dump-sql

SQL kód pro synchronizaci databáze:

CREATE TABLE article (id INT AUTO_INCREMENT NOT NULL, author_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, text VARCHAR(255) DEFAULT NULL, INDEX IDX_23A0E66F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
CREATE TABLE author (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
CREATE TABLE tag (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
CREATE TABLE tag_article (tag_id INT NOT NULL, article_id INT NOT NULL, INDEX IDX_300B23CCBAD26311 (tag_id), INDEX IDX_300B23CC7294869C (article_id), PRIMARY KEY(tag_id, article_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
ALTER TABLE article ADD CONSTRAINT FK_23A0E66F675F31B FOREIGN KEY (author_id) REFERENCES author (id);
ALTER TABLE tag_article ADD CONSTRAINT FK_300B23CCBAD26311 FOREIGN KEY (tag_id)
REFERENCES tag (id) ON DELETE CASCADE;
ALTER TABLE tag_article ADD CONSTRAINT FK_300B23CC7294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE;

Tento kód můžete vykonat v admineru, neb pokud máte koule můžete ho nechat vykonat přímo z příkazové řádky (hlavouni to nedoporučují):

php ./www/index.php orm:schema-tool:update --force

Nyní když znovu spustíme validaci, vidíme:

D:\www\nette-doctrine>php ./www/index.php orm:validate-schema
[Mapping]  OK - The mapping files are correct.
[Database] OK - The database schema is in sync with the mapping files.

Namapování a databáze jsou OK, pojďme na ukládání a zobrazení dat.

Doctrine – ukládání dat

V HomepagePresenter.php si uděláme add akci na přidání záznamů do entity Article:

<?php

namespace App\Presenters;

use App\Article;
use Nette;
use Kdyby;

/**
 * Homepage presenter.
 */
class HomepagePresenter extends BasePresenter
{

	/**
	 * @inject
	 * @var Kdyby\Doctrine\EntityManager
	 */
	public $entityManager;

	public function actionAdd()
	{
		$daoArticle = $this->entityManager->getRepository(Article::getClassName());

		$article = new Article();
		$article->title= "Slabikář";
		$daoArticle->save($article);

		$article = new Article();
		$article->title= "Čítanka";
		$daoArticle->save($article);

		exit();
	}

}

Definovali jsme $entityManager do které jsme injectli Kdyby\Doctrine\EntityManager.

V actionAdd jsme vytvořili 2 články které uložíme do DB. První s názvem Slabikář a druhý s názvem Čítanka. V DB se můžete přesvědčit že došlo k jejich uložení do tabulky article.

Doctrine výpis dat

Velice podobným způsobem jako jsme ukládali data je budeme vypisovat. Opět budeme potřebovat EntityManager:

<?php

namespace App\Presenters;

use App\Article;
use Nette;
use Kdyby;

/**
 * Homepage presenter.
 */
class HomepagePresenter extends BasePresenter
{

	/**
	 * @inject
	 * @var Kdyby\Doctrine\EntityManager
	 */
	public $entityManager;

	public function renderDefault()
	{
		$daoArticle = $this->entityManager->getDao(Article::getClassName());
		$this->template->articles = $daoArticle->findAll();
	}
}

v šabloně tyto články zobrazíme:

{block content}

{foreach $articles as $article}

    {$article->title}<br/>

{/foreach}

Jednoduché že ano?

Přidání nového položky do entity

Naše aplikace je známá po celém světě a používají ji milony lidí kteří píší podměty že jim pouze název článku nestačí a chtěli by ještě text článku 🙂 Vyhovíme jim a přidáme do entity Article ještě položku text.

Nyní nám perfektně poslouží Filipovo live templaty. Otevřeme si soubor Article.php a pod proměnou private $title odřádkujeme a napíšeme: col <tab> (napíšeme col a stiskneme klávesu Tab). Ide nám předvygeneruje následující kód:

    /**
     * @ORM\Column(type="string", nullable=TRUE)
     * @var string
     */
    protected $;

ve kterém se pohybujeme klávesou Tab. Napíšeme název proměnné (text) a definujeme typ (text). Výsledek vypadá takto:

    /**
     * @ORM\Column(type="text", nullable=TRUE)
     * @var text
     */
    protected $text;

Provedeme validaci:

php ./www/index.php orm:validate-schema
D:\www\nette-doctrine>php ./www/index.php orm:validate-schema
[Mapping]  OK - The mapping files are correct.
[Database] FAIL - The database schema is not in sync with the current mapping file.

která nám oznámí že nemáme synchronizovanou databázi. Podíváme se jaké úpravy databáze nám Doctrine nabízí:

D:\www\nette-doctrine>php ./www/index.php orm:schema-tool:update --dump-sql
ALTER TABLE article ADD text LONGTEXT DEFAULT NULL;

Kód vykonáme v admineru, nebo 🙂

php ./www/index.php orm:schema-tool:update --force

Následná validace by měla být OK. Tím máme přidán sloupec text a můžeme do něho rovnou ukládat data. Vytáhneme námi uložené publikace a donastavíme jim text:

    public function actionUpdate()
    {
        $daoArticle = $this->entityManager->getRepository( Article::getClassName() );

        $article = $daoArticle->findOneBy( array( "title" => "Slabikář" ) );
        $article->text = "Text knihy slabikář";

        $article = $daoArticle->findOneBy( array( "title" => "Čítanka" ) );
        $article->text = "Text knihy čítanka";

        $this->entityManager->flush();
        exit();
    }

Upravíme šablonu abychom vypisovali text:

{block content}

{foreach $articles as $article}

    <h2>{$article->title}</h2>
    <p>{$article->text}</p>

{/foreach}

 Vyšší liga – vazby mezi entitami

Toto jsme splodili na Workshopu:

Article.php

<?php

namespace App;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Article extends \Kdyby\Doctrine\Entities\BaseEntity
{

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $title;

    /**
     * @ORM\Column(type="string", nullable=TRUE)
     * @var string
     */
    protected $text;

    /**
     * @ORM\ManyToOne(targetEntity="\App\Author", inversedBy="articles", cascade={"persist"})
     * @var Author
     */
    protected $author;

    /**
     * @ORM\ManyToMany(targetEntity="\App\Tag", mappedBy="articles", cascade={"persist"}, orphanRemoval=true)
     * @var \App\Tag[]|\Doctrine\Common\Collections\ArrayCollection
     */
    protected $tags;


    function __construct()
    {
        $this->tags = new ArrayCollection();
    }

    public function addTag(Tag $tag)
    {
        $tag->addArticle($this);
        $this->tags->add( $tag );
    }

}

Author.php

<?php

namespace App;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity
 * @property $name
 */
class Author extends \Kdyby\Doctrine\Entities\BaseEntity
{


    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * @ORM\OneToMany(targetEntity="\App\Article", mappedBy="author", cascade={"persist"})
     * @var Article[]|\Doctrine\Common\Collections\ArrayCollection
     */
    protected $articles;

    function __construct()
    {
        $this->articles = new ArrayCollection();
    }

    public function addArticle(Article $article)
    {
        $this->articles->add( $article );
    }
}

Tag.php

<?php


namespace App;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity
 * @property $name
 */
class Tag extends \Kdyby\Doctrine\Entities\BaseEntity
{

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $name;


    /**
     * @ORM\ManyToMany(targetEntity="\App\Article", inversedBy="tags", cascade={"persist"})
     * @var \App\Article[]|\Doctrine\Common\Collections\ArrayCollection
     */
    protected $articles;


    function __construct()
    {
        $this->articles = new ArrayCollection();
    }

    public function addArticle(Article $article)
    {
        $this->articles->add( $article );
    }
}

HomepagePresenter.php

<?php

namespace App\Presenters;

use App\Author;
use App\Tag;
use Nette,
	App\Model;
use App\Article;

/**
 * Homepage presenter.
 */
class HomepagePresenter extends BasePresenter
{

	/**
	 * @inject
	 * @var \Kdyby\Doctrine\EntityManager
	 */
	public $EntityManager;

	public function renderDefault()
	{
		$this->template->anyVariable = 'any value';
		$dao = $this->EntityManager->getRepository(Article::getClassName());
		$this->template->articles = $dao->findAll();

	}

	public function actionAdd()
	{

		$daoArticle = $this->EntityManager->getRepository(Article::getClassName());
		$daoAuthor = $this->EntityManager->getRepository(Author::getClassName());

		//$author = new Author();
		//$author->name = "Pokusne jmeno";

		$author = $daoAuthor->findOneBy( array("name" => "Pokusne jmeno"));

		$article = new Article();
		$article->title = "titulke sntagem";
		$article->text = "text novinky s tagem";
		$article->author = $author;
		$author->addArticle($article);

		$tag = new Tag();
		$tag->name = "kniha";

		$article->addTag($tag);

		$tag = new Tag();
		$tag->name = "kniha dvojita";

		$article->addTag($tag);

		$daoArticle->save($article);

		exit();
	}

}

Šablona:

{block content}

{foreach $articles as $article}

    {$article->title}<br>
    {$article->author->name}<br/>
    {dump $article->tags}
    Tags: {if $article->tags}{foreach $article->tags as $tag}{$tag->name}, {/foreach}{/if}<br/>
<hr/>
{/foreach}

{/block}

 

Celé databázové schéma smažeme a vytvoříme znovu:

php ./www/index.php orm:schema-tool:drop --force

php ./www/index.php orm:schema-tool:create

Celou ukázku jsem nahrál na GitHub: https://github.com/venca-x/nette-sandbox-doctrine

Composer: jak nainstalovat Nette 2.3.0-beta

Po vystřízlivění ze včerejší posoboty nastal čas vyzkoušet Nette 2.3.0-beta.

Upgrade je jednoduchý. Stačí upravit composer.json:

...
  "require": {
    ...
    "nette/nette": "~2.3.0",
    ...
  },
  "minimum-stability": "beta",
  "prefer-stable": true
...

Velice důležitý je řádek minimum-stability: beta a prefer-stable: true

Následně do konzole pustíte příkaz composer update a můžete testovat Nette 2.3.0-beta

Nette: zmenšení průhledného PNG, zachování průhlednosti

Při zmenšování průhledného PNG obrázku v Nette jsem měl problém se zachováním průhlednosti. Místo průhlednosti byla černá barva. Nakonec jsem problém vyřešil takto:

$photo->alphaBlending(true);
$photo->resize( 1000, 1000, Nette\Utils\Image::SHRINK_ONLY );
$photo->alphaBlending(false);
$photo->saveAlpha(true);
$photo->save( ... );

Kód resize je obalen alphaBlending a saveAlpha

Debian 7 (wheezy) instalace PHP 5.6

Dnes jsme se při vývoji webu eshopvikend.cz zasekli na PHP 5.3. Knihovna pro přihlášení uživatelů přes sociální sítě vyžaduje alespoň PHP 5.4

Inu, mám trochu času tak se podívám na mé VPS na kterém běží Debian 7 Wheezy a PHP 5.4.36. Většina mých projektů je na Nette (verze 2.2) nebo aktualizovaný WordPress. S přechodem na PHP 5.6 nebude problém 🙂

Po motivačním videu od Petra Krčmáře se jde na to!

Dávejte pozor a před aktualizací pro jistotu zazálohujte vaši konfiguraci! Rovněž nezapomeňte že při upgrade na PHP 5.6 na Debianu budete potřebovat Apache 2.4 (který má trochu jiné konfigurační soubory)

Ze stable repozitáře Debianu (Wheezy) není možné nainstalovat vyšší verzi PHP než 5.4. Připadá v úvahu ruční kompilace, ruční instalace zkompilovaného balíčku nebo backports.

Backports má výhodu v tom, že nainstalované programy bude možné dále aktualizovat a pokud chceme instalovat z backports musíme při instalaci zadat určitý identifikátor.

Větve Debianu:

  • stable – bylíčky z větve testing se přibližně jednou za 2 roky vezmou a překlopí do větve stable
  • testing – balíček který se 14 dní neopravoval ve větvi unstable propadne do větve testing
  • unstabe – neznamená nestabilní (lépe řečeno pohyblivá) – často přichází nové verze balíčků

Pokud chceme novější PHP, použijeme větev testing.

Ze všeho nejdřív doporučuji nainstalovat apt-show-version:

apt-get install apt-show-versions

 

Nastavení výchozí větev pro instalaci balíčků

vim /etc/apt/apt.conf.d/00release

APT::Default-Release "stable";

Nyní přidáme testing repozitář:

vim /etc/apt/sources.list přidáme řádek:

deb http://ftp.zcu.cz/mirrors/debian/ testing main

Uděláme update balíčků:

apt-get update

Podíváme se na dostupné verze PHP:

apt-show-versions -a php5

Kde vidět aktuálně nainstalovaná verze 5.4.36-0+deb7u1 a dostupná verze 5.6.4+dfsg-1 z testing větve.

php5:all 5.4.36-0+deb7u1 install ok installed
php5:all 5.4.4-14+deb7u14 wheezy  ftp.zcu.cz
php5:all 5.4.36-0+deb7u1  wheezy  security.debian.org
No stable-updates version
php5:all 5.6.4+dfsg-1     testing ftp.zcu.cz
php5:all/wheezy 5.4.36-0+deb7u1 uptodate

Přidaný testing repozitář nená žádný vliv na instalací balíčků. Pro instalaci z testing větve je nutné uvést parametr. Přesvědčme se o tom. Zobrazme si verzi PHP která se nabízí k instalaci:

aptitude show php5

Vidíme že instalátor nabízí: Version: 5.4.36-0+deb7u1

Pokud chceme instalovat balíčky z testing větve použijeme parametr: -t testing

Nyní si zobrazme verzi PHP nainstalovaného z testing větve:

aptitude show php5 -t testing

Vidíme že instalátor nabízí: Version: 5.6.4+dfsg-1 který nainstalujeme:

apt-get install php5 -t testing

 

 

CoffeeScript + Node.js => Hello world

Poslední dobou ujíždím na Node.js. Neskutečně rychlý nástroj! K tomu se pokouším naučit CoffeeScript. Blog dost často používám jako „tahák“ co jsem jak dělal. Pojďme si ukázat jak na první Hello World.

CoffeeScript můžeme nainstalovat globálně (bude přístupný v celém OS):

npm install -g coffee-script

Inicilizujeme Node.js projekt – vyplníme požadované údaje, čímž se vytvoří soubor package.json:

npm init

Node přidáme jako závislost do projektu (packages.json):

npm install coffee-script --save

server.coffee:

#toto je komentar

http = require 'http'
http.createServer (req, res) ->
        res.writeHead 200
        res.end 'Hello, World!'
        .listen 5000
console.log "listening on port 5000"

Spustíme coffee script v Node.js:

coffee server.coffee

V prohlížeči na adrese http://localhost:5000/ vidíme výsledek

Velice zajímavý mi přijde modul express, což je minimalistický a rychlý framework pro pohodlnější generování stránek:

npm install express --save
express = require('express')

app = express()
port = 5000

app.get '/', (req, res) ->
  res.send 'Hello world!'

app.get '/contact', (req, res) ->
  res.send 'contact'

app.listen port, () ->
  console.log "listening on port #{port}"

Spustíme příkazem

coffee server.coffee

a o správné funkčnosti se opět přesvědčíme na portu 5000

Node.js crawling data

Pokud někdy budete chtít získávat obsah webových stránek, doporučuji kvůli rychlosti použít Node.js a modul nazvaný jsdom. Tento modul analyzuje webovou stránku ze které získá DOM, který můžete parsovat přes jQuery.

Před nainstalováním jsdom je nutné nainstalovat Python (verze 2.x) Při instalaci je nutné zaškrtnou volbu Python do PATH (App python.exe to Path).

Nainstalujeme jsdom:

npm install jsdom --save

example.js

// Count all of the links from the Node.js build page
var jsdom = require("jsdom");

jsdom.env(
  "http://nodejs.org/dist/",
  ["http://code.jquery.com/jquery.js"],
  function (errors, window) {
    console.log("there have been", window.$("a").length, "nodejs releases!");
  }
);

spustíme příkazem:

C:\node>node example.js
there have been 245 nodejs releases!

 

Jak nainstalovat Grunt pod Windows

Pojďme si ukázat nástroj, který za mne dělá špinavou práci. Všechny procesy které děláte stále ručně do kola se většinou dají zautomatizovat. Já je nechávám na Gruntu aby je udělal za mne. Stroj nedělá chyby a je rychlý.

Dnes si ukážeme úvod do Gruntu. S Gruntem spolupracuje další skvělý nástroj – Bower. Ale o tom až někdy příště.

Pro zprovoznění programu Grunt je nutné nainstalovat node.js

Z webu stáhneme instalaci (tlačítko INSTALL) a nainstalujeme. Instalaci stačí odklikat. Po dokončení instalace pro jistotu restartujeme PC.

Přesvědčíme se že máme správně nainstalovaný node.js. Spustíme příkazovou řádku ( Start -> Příslušenství -> Příkazový řádek ). Do příkazové řádky zadáme příkaz node a potvrdíme klávesou ENTER:

node-test-run

Součástí každého projektu by měl být soubor package.json. Vytvoříme ho příkazem:

npm init

package.json obsahuje základní informace o projektu ( autor, verze, verzi Node, závislé balíčky a jejich verze, …) je to něco jako composer.json pro php. Pokud si projekt s tímto souborem stáhnete, jednoduše nainstalujete všechny potřebné balíčky příkazem:

npm install

Instalace Grunt

npm install -g grunt
npm install -g grunt-cli
npm install grunt --save-dev       //musim provest i tento prikaz

Parametr -g říká, že Grunt instalujeme globálně.

Úspěšné nainstalování Gruntu prověříme příkazem:

grunt

node-test-run-grunt

Z hlášky v konzoli je vidět že nemáme soubor Gruntfile.js pojďme si ho vytvořit (ten musíme vytvořit ručně):

module.exports = function(grunt) {
   grunt.initConfig({
      section: {
         foo: [1, 2, 3],
         bar: 'hello world',
         baz: false
      }
   })
   grunt.task.registerMultiTask('section', 'run section.', function() {
      grunt.log.writeln(this.target + ': ' + this.data);
   });
   grunt.task.registerMultiTask('section', 'run section.', function() {
      grunt.log.writeln(this.target + ': ' + this.data);
   });

   grunt.registerTask('only-baz', ['section:baz']);
}

Vytvořili jsme 2 tasky:

  • section – multiTast vykoná všechny tasky v sekci section
    node-test-run-grunt-run
  • only-baz – vykoná pouze 1 task ze sekce section ( section -> baz )
    node-test-run-grunt-run-1task

Neužitečný příklad výstupu, ovšem nutný pro pochopení fungování Gruntu. Příště si ukážeme zajímavější použití v praxi 🙂

Nette: handle (signál)

Předpokládejme že máme TODO list ve kterém budeme chtít vybraný úkol nastavit jako splněný.

Na takový úkon je vhodný signál:

public function handleMarkDone( $taskId )
{
    $this->table->where(array('id' => $taskId))->update(array('done' => 1));
    $this->redirect('this');
}

Signál v šabloně zavoláme jednoduše:

<a n:href="markDone! $task->id">hotovo</a>

 

Grunt – task pro automatický release nové verze

Dělám na několika doplňcích pro WordPress. Kámen úrazu je verzování. S vydáním nové verze jsem musel přepisovat verzi ve zdrojovém kódu, vytvořit nový tag v Gitu a tuto změnu commitnout a pushnout. Ale protože jsem lajdák vždy jsem na něco zapoměl…

Při vydání nové verze dělám:

  • nastavit novou verzi ve zdrojových souborech ( Version: 0.0.1 => Version: 0.0.2 )
  • coomitnout tuto změnu
  • vytvořit nový tag ( 0.0.2 )
  • pushnout tyto změny

To jsem dělal ručně do doby než jsem poznal Grunt. Nyní mám tento proces automatizovaný a tuto nudnou opičí práci za mne udělá Grunt.

Pojďme si ukázat jak. Předpokládejme nainstalovaný Grunt (o instalaci Gruntu zase někdy jindy).

Vytvoříme soubor package.json:

npm init

Nainstalujeme grunt-version:

npm install grunt-version --save-dev

Vytvoříme soubor Gruntfile.js:

module.exports = function(grunt) {
   grunt.loadNpmTasks('grunt-version');
 
   grunt.initConfig({
      version: {
         php: {
            options: {
               prefix: '\Version:\\s+'
            },
            src: [ 'my-file.php' ]
         }
      }
   });

   // Release task
   grunt.registerTask( 'release', [ 'version' ]); 
};

Příkazem grunt release nastavíme v souboru my-file.php novou verzi uvedenou v package.json:

Version:           0.0.1 se změní na verzi zadanou v souboru package.json

To je solidní základ 🙂 Ale mne to nestačí. Verzi kterou chci vydat musím zapsat do souboru package.json a příliš práce to za mne neudělá…

Dělám různé releasy:

  • patch (1.2.X)
  • minor (1.X.0)
  • major (X.0.0)

Pojďmě si napsat script který bude automaticky zvyšovat verzi v souboru package.json v závislosti na vydávané verzi. Vužijeme modul grunt-update-json

npm install grunt-update-json --save-dev

Použijeme tento kód:

module.exports = function(grunt) {

   grunt.loadNpmTasks('grunt-version');
	
   grunt.initConfig({
      pkg: grunt.file.readJSON( 'package.json' ),
         version: {
            php: {
               options: {
                  prefix: '\Version:\\s+'
               },
                  src: [ 'my-file.php' ]
               }
            }
         });
         // Release task		
         grunt.registerTask('release:patch', 'Release patch version', function( target ) {
            release( "patch" );
         });

         grunt.registerTask('release:minor', 'Release minor version', function( target ) {
            release( "minor" );
         });

         grunt.registerTask('release:major', 'Release major version', function( target ) {
            release( "major" );
         });

         function release( type )
         {
            var version = grunt.config.data.pkg.version;
            var aVersion = version.split(".");
            if( type == "patch" )
            {
               aVersion[ 2 ] = ( 1 * aVersion[ 2 ] ) + 1;
            }
            else if( type == "minor" )
            {
               aVersion[ 2 ] = 0;
               aVersion[ 1 ] = ( 1 * aVersion[ 1 ] ) + 1;
            }
               else if( type == "major" )
            {
               aVersion[ 2 ] = 0;
               aVersion[ 1 ] = 0;
               aVersion[ 0 ] = ( 1 * aVersion[ 0 ] ) + 1;
            }
            var newVersion = aVersion.join(".");
            grunt.log.writeln('Release ' + type + ' version: ' + version + ' => ' + newVersion );

            //save new version to package.json
            grunt.config.data.pkg.version = newVersion;		
            grunt.file.write( './package.json', JSON.stringify( grunt.config.data.pkg, null, '  ') + '\n');
		
            //change version in source code
            grunt.task.run(['version']);
		
            //@TODO commit
            //@TODO create TAG
            //@TODO push
         }
};

To už je lepší. Můžeme použít následující příkaz:

  • grunt release:patch
  • grunt release:minor
  • grunt release:major

K dokonalosti chybí pouze commit, vytvořit tag s novou verzí a push. K tomu využijeme grunt-git

npm install grunt-git --save-dev

Do souboru package.json přidáme následující kód:

gitcommit: {
   version: {
      options: {
         message: 'New version: <%= pkg.version %>'
      },
      files: {
         // Specify the files you want to commit
         src: ['my-file.php', 'package.json']
      }
   }
},
gittag: {
   version: {
      options: {
         tag: '<%= pkg.version %>',
         message: 'Tagging version <%= pkg.version %>'
      }
   }
},
gitpush: {
   version: {},
   tag: {
      options: {
         tags: true
      }
   }
},

Celý kód nakonec vypadá takto:

module.exports = function(grunt) {

   grunt.loadNpmTasks('grunt-version');
   grunt.loadNpmTasks('grunt-git');
	
   grunt.initConfig({
      pkg: grunt.file.readJSON( 'package.json' ),
         version: {
            php: {
               options: {
                  prefix: '\Version:\\s+'
               },
               src: [ 'my-file.php' ]
            }
         },
         gitcommit: {
            version: {
               options: {
                  message: 'New version: <%= pkg.version %>'
               },
               files: {
                  src: ['my-file.php', 'package.json']
               }
            }
         },
         gittag: {
            version: {
               options: {
                  tag: '<%= pkg.version %>',
                  message: 'Tagging version <%= pkg.version %>'
               }
            }
         },
         gitpush: {
            version: {},
            tag: {
               options: {
                  tags: true
               }
            }
         }		
      });

      grunt.registerTask('release:patch', 'Release patch version', function( target ) {
         release( "patch" );
      });

      grunt.registerTask('release:minor', 'Release minor version', function( target ) {
         release( "minor" );
      });

      grunt.registerTask('release:major', 'Release major version', function( target ) {
         release( "major" );
      });

      function release( type )
      {
         var version = grunt.config.data.pkg.version;
         var aVersion = version.split(".");
         if( type == "patch" )
         {
            aVersion[ 2 ] = ( 1 * aVersion[ 2 ] ) + 1;
         }
         else if( type == "minor" )
         {
            aVersion[ 2 ] = 0;
            aVersion[ 1 ] = ( 1 * aVersion[ 1 ] ) + 1;
         }
         else if( type == "major" )
         {
            aVersion[ 2 ] = 0;
            aVersion[ 1 ] = 0;
            aVersion[ 0 ] = ( 1 * aVersion[ 0 ] ) + 1;
         }
         var newVersion = aVersion.join(".");
         grunt.log.writeln('Release ' + type + ' version: ' + version + ' => ' + newVersion );

         //save new version to package.json
         grunt.config.data.pkg.version = newVersion;		
         grunt.file.write( './package.json', JSON.stringify( grunt.config.data.pkg, null, '  ') + '\n');
		
         //change version in source code
         grunt.task.run(['version']);
		
         //git commit
         grunt.task.run(['gitcommit:version']);
         //git create TAG
         grunt.task.run(['gittag:version']);
         //git push
         grunt.task.run(['gitpush']);
      }
};

 

 

Nette + Texy

Pokud si zájemce o web přeje základní formátování textů, vetšinou nejprve sáhnu po Texy. Nemám rád weby které vypadají jako omalovánky (ano, tomu kdo dá tu práci aby každé písmenko ve slově mělo jinou barvu se to moc líbí ale spoustu návštěvníků to odradí). Texy je jednoduchý nástroj kterým můžete bez odborných znalostí psát HTML kód. Příklad si ukážeme na jednoduchém formuláři, jehož odesláním naformátujeme zadaný text přes Texy.

 

Pojďme si ukázat jak zprovoznit  Nette společně s Texy!

Ukázka je na poslední verzi Nette (2.2.6) a poslední verzi Texy (2.6).

Instalace Nette

Stáhneme Nette sandbox (přes composer):

composer create-project nette/sandbox nette-texy

příkazem se vytvoří složka nette-texy do které se nahraje Nette + sandbox. Document root pro virtualhost nasměrujeme do nette-texy/www/

Instalace Texy

nainstalujeme samozřejmě přes composer :

composer require dg/texy:~2.0

 

Zprovoznění Texy

Uděláme si pořádek na hřišti. Nahradíme obsah šablony pro titulní stránku: app/templates/Homepage/default.latte:

{block content}
   {ifset $text}
      {$text|texy|noescape}
   {/ifset}
   {control texyForm}
{/block}

 Vytvoření formuláře

app/presenters/HomepagePresenter.php:

<?php

namespace App\Presenters;

use Nette;

class HomepagePresenter extends BasePresenter
{

    protected function createComponentTexyForm()
    {
        $form = new Nette\Application\UI\Form;
        $form->addTextarea('text', 'Text:')
                ->setAttribute('cols', 80)
                ->setAttribute('rows', 10);
        $form->addSubmit('send', 'Send');

        $form->onSuccess[] = $this->texyFormSucceeded;
        return $form;
    }


    public function texyFormSucceeded($form, $values)
    {
        $this->template->text = $values->text;
    } 
}

Máme formulář který při odeslání odešle do šablony (proměnná $text ) zadaný text.

Zapnutí Texy

Nejprve je nutné přidat Texy do neon.config aby o Nette o Texy vědělo:

app/config/config.neon:

 

services:
	- App\Model\UserManager
	- App\RouterFactory
	router: @App\RouterFactory::createRouter
	texy: Texy

 

V souboru app/presenters/BasePresenter.php je nutné vytvořit helper, který bude fomárovat text přes Texy.

<?php

namespace App\Presenters;

use Nette;
use TexyConfigurator;

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    /** @var \Texy */
    private $texy;

    public function injectTexy( \Texy $texy )
    {
        $this->texy = $texy;

        //$this->texy->allowed['heading/underlined'] = FALSE;// podtržené titulky
        //$this->texy->allowed['heading/surrounded'] = FALSE;// ohraničené titulky

        //TexyConfigurator::safeMode( $this->texy ); //nastaveni pro diskuse, ktere nepovoli vlozeni kompromitovaneho kodu
        //TexyConfigurator::disableLinks( $this->texy );
        //TexyConfigurator::disableImages( $this->texy );
    }

    protected function createTemplate( $class = NULL )
    {
        $template = parent::createTemplate( $class );
        $template->registerHelper( 'texy', callback( $this->texy, 'process' ) );
        return $template;
    }

}

V nové verzi Nette (>=2.4) se helper (filtr) registruje takto, metodu createTemplate můžete úplně vynechat):

    protected function beforeRender()
    {
        parent::beforeRender();

        $this->template->addFilter('texy', function ($text) {
            return $this->texy->process($text);
        });

    }

Nejlepší cesta

Kvůli bezpečnosti, doporučuji tento způsob použití, ve kterém filtr vrací Nette\Utils\Html, který nemusíme nonescapovat. Registrujeme filter klasicky v beforeRender:

    protected function beforeRender()
    {
        parent::beforeRender();

        $this->template->addFilter('texy', function ($text) {
            //return $this->texy->process($text);
            return Nette\Utils\Html::el()->setHtml($texy->process($text));
        });

    }

A pak již pouze v šabloně vypíšeme:

{$text|texy}