Mot-clé - nette

Fil des billets

mardi, 24 novembre 2015

Nette framework : utilisation

Ici, nous voyons les interactions entre une présentation et ses vues, comment fonctionnent les formulaires, ainsi que l'ajout et l'édition de données dans la base de données.

Avant de commencer, admettons que vous ayez connecté votre base de données (cf. article précédent) en prenant soin de privilégier InnoDB comme moteur de stockage, et que vous ayez créé deux tables : posts et comments.

Presenter / Présentation

app/presenters/PostPresenter.php


namespace App\Presenters;

use Nette,
    Nette\Application\UI\Form;

class PostPresenter extends BasePresenter
{
/** @var Nette\Database\Context */
private $database;

public function __construct(Nette\Database\Context $database)
{
    // connexion à la base de données
    $this->database = $database;
}

public function renderDefault() ➊
// génère une vue default.latte
{
   // récupère les données et
   // les envoie dans la vue sous la variable $posts
   $this->template->posts = $this->database->table('posts')
      ->order('created_at DESC')
      ->limit(5);
}

public function renderShow($postId) ➋
// génère une vue show.latte
{
   // récupération du $postId,
   // s'il n'existe pas, afficher une erreur,
   // sinon envoyer le résultat dans la vue
   $post = $this->database->table('posts')->get($postId);
   if (!$post) {
       $this->error('Post not found');
   }

   $this->template->post = $post;
   $this->template->comments = $post->related('comments')
       ->order('created_at');
}

protected function createComponentCommentForm() ➌
// sera récupéré dans la vue sous "CommentForm"
{
    // création d'un formulaire
    $form = new Form;

    $form->addText('name', 'Your name:') // ('name', 'label')
        ->setRequired() // required
        ->setAttribute('class', 'toto')
        ->setAttribute('placeholder', 'Please type your name')
        ->setAttribute('onchange', 'mafonction()');

    $form->addText('email', 'Email:')
        ->setType('email');

    $sex = array(
        'm' => 'male',
        'f' => 'female',
    );
    $form->addRadioList('gender', 'Gender:', $sex);

    $form->addCheckbox('Human', 'Are you a human ?');

    $countries = array(
        'Europe' => array(
            'CZ' => 'Czech republic',
            'SK' => 'Slovakia',
            'GB' => 'United Kingdom',
        ),
        'CA' => 'Canada',
        'US' => 'USA',
        '?'  => 'other',
    );
    $form->addSelect('country', 'Country:', $countries)
        ->setPrompt('Pick a country');

    $form->addTextArea('content', 'Comment:')
       ->setDisabled(); // disabled

    $form->addUpload('thumbnail', 'Thumbnail:')
        ->addRule(Form::IMAGE, 'Thumbnail must be JPEG, PNG or GIF');

    $form->addSubmit('send', 'Publish comment');

    $form->onSuccess[] = array($this, 'commentFormSucceeded'); ➍

    return $form;
}

➍ public function commentFormSucceeded($form, $values) {
$postId = $this->getParameter('postId');

$this->database->table('comments')->insert(array(
     'post_id' => $postId,
     'name' => $values->name,
     'email' => $values->email,
     'content' => $values->content,
));

$this->flashMessage('Thank you for your comment', 'success'); ➎
$this->redirect('this');
// alternative :
// $this->redirect('show', $post->id);
}

public function actionEdit($postId) ➒
// actionEdit et non renderEdit :
// il ne s'agit pas que d'un travail de rendu dans ce cas-ci.
{
    $post = $this->database->table('posts')->get($postId);
    if (!$post) {
        $this->error('Post not found');
    }
    $this['postForm']->setDefaults($post->toArray()); ➏
}

protected function createComponentPostForm() ➏
{
$form = new Form;
$form->addText('title', 'Title:')
     ->setRequired();
$form->addTextArea('content', 'Content:')
     ->setRequired();

$form->addSubmit('send', 'Save and publish');

$form->onSuccess[] = array($this, 'postFormSucceeded'); ➐

return $form;
}

➐ public function postFormSucceeded($form, $values) {
$postId = $this->getParameter('postId');

if ($postId) {
   $post = $this->database->table('posts')->get($postId);
   $post->update($values);
} else {
   $post = $this->database->table('posts')->insert($values);
}

$this->flashMessage('Post was published', 'success'); ➎
$this->redirect('show', $post->id);
}

}

View / Vue

app/templates/Post/default.latte


{block content} ➑
    <h1 n:block="title">Blog</h1>

    {foreach $posts as $post}
    <div class="post">
        <div class="date">{$post->created_at|date:'F j, Y'}</div>

        <h2>{$post->title}</h2>

        <div>{$post->content}</div>
    </div>
    {/foreach}

    <a n:href="Post:create">Write new post</a>

{/block}

➋ app/templates/Post/show.latte


{block content} ➑
<p><a n:href="Homepage:default">← back to posts list</a></p>
<div class="date">{$post->created_at|date:'F j, Y'}</div>
<h1 n:block="title">{$post->title}</h1>
<div class="post">{$post->content}</div>
➒ <a n:href="edit $post->id">Edit this post</a>

<h2>Post new comment</h2>
➌ {control commentForm}

<h2>Comments</h2>
<div class="comments">
    {foreach $comments as $comment}
        <p><b><a href="mailto:{$comment->email}"
         n:tag-if="$comment->email">
         {$comment->name}</a></b> said:</p>
        <div>{$comment->content}</div>
    {/foreach}
</div>

app/templates/Post/edit.latte


{block content} ➑
<h1>Edit post</h1>

➏ {control postForm}

app/templates/@layout.latte (gabarit inclusif)


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>{ifset title}{include title|striptags} | {/ifset}Nette Web</title>
    <!-- embarque/agrège le title spécifié dans la vue -->

    <link rel="shortcut icon" href="{$basePath}/favicon.ico">
</head>

<body>
     ➎ <div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
            {$flash->message}
        </div>
    <!-- affiche les messages d erreur liés
    à la complétion du formulaire (champs requis et autres règles)-->

     ➑ {include content}
        <!-- embarque/agrège le contenu des vues : {block content} -->

    {block scripts}
    <script src="//nette.github.io/resources/js/netteForms.min.js"></script>
    {/block}
</body>
</html>

Formulaires : validation et récupération des données


$form->addPassword('password', 'Password:')
    // if password is not longer than 5 characters ...
    ->addCondition(Form::MAX_LENGTH, 5)
        // ... then it must contain a number
        ->addRule(Form::PATTERN, 'Must contain number', '.*[0-9].*')
        ->addRule(Form::RANGE, 'You must be older 18 years and be under 120.', array(18, 120));

// récupération des valeurs après soumission :
$values = $form->getHttpData($form::DATA_TEXT, 'sel[]');
$values = $form->getHttpData($form::DATA_TEXT | $form::DATA_KEYS, 'sel[]');

Parmi les règles et conditions, voici la liste des fonctions possibles ((Liste non exhaustive.)) :

  • Form::MIN_LENGTH : minimum de caractères ou de fichiers accepté
  • Form::MAX_LENGTH : maximum de caractères ou de fichiers accepté
  • Form::LENGTH : nombre strict de caractères ou de fichiers accepté
  • Form::EMAIL : impose un format email
  • Form::URL : impose un format url
  • Form::INTEGER : impose un nombre entier
  • Form::FLOAT : impose un nombre décimal
  • Form::RANGE : impose une fourchette entre X et Y
  • Form::PATTERN : expression régulière
  • Form::BLANK : le champ doit rester vide
  • Form::MAX_FILE_SIZE : impose le poids d'un fichier
  • Form::MIME_TYPE : vérifie si le type MIME est valide
  • Form::IMAGE : impose un fichier au format .gif, .png ou .jpg
  • Form::FILLED : vérifie si le champ est rempli
  • Form::EQUAL : vérifie que la valeur est égale à celle prescrite
  • Form::NOT_EQUAL : vérifie que la valeur est différente de celle prescrite
  • Form::IS_IN : vérifie si la valeur fait partie d'un tableau
  • Form::VALID : vérifie si le champ est acceptable pour la validation

Sources

lundi, 23 novembre 2015

Nette framework : présentation et installation

nette.pngNette est un framework MVP* qui a le vent en poupe en République tchèque (d’où il vient) et en Slovaquie. Il existe depuis 2009 et Sitepoint le place dans le trio de tête des frameworks PHP en 2015, malgré quelques carences au niveau des performances, comme pour Zend et Laravel :

 

sp-compare.gif

Bien que la Belgique et la France ignorent pour le moment ce framework, je l’ai testé pour comparer son fonctionnement à ceux de Laravel, CodeIgniter et CakePHP.

Nette comporte pléthore de qualités : parmi elles, sa prise en main d’une simplicité déconcertante, sa sécurité manifeste, ses outils de débogage (Tracy), sa documentation complète et accessible (qui commence au b.a.-ba, contrairement à certaines…) et sa communauté anglophone plutôt dynamique.

A l’inverse de certains frameworks, il fonctionne selon une architecture Modèle-Vue-Présentation, version évoluée de l’architecture Modèle-Vue-Contrôleur.

mvp.png

Le MVP garde le même principe que le MVC mais élimine l’interaction entre la vue et le modèle. Le relais est assuré par la présentation qui organise les données à afficher dans la vue. Plus d’informations.

Dans cet article et ceux qui suivront, vous trouverez des mémos d’utilisation du framework, comme je l’ai fait en son temps pour CakePHP.


Installer Nette Framework

  • Installer Composercomposer.png
  • Installer Nette : depuis l’invite de commandes, accéder au dossier www/htdocs de votre wamp/xampp et exécuter la commande suivante :
    composer create-project nette/sandbox [app_name]
  • Mettre à jour Nette : la dernière version stable du framework sera installée en exécutant à partir du dossier générique de l’appli la commande suivante :
    composer update
  • Installer les composants nécessaires au bon fonctionnement du framework :
    composer require latte/latte
  • Établir la connexion à la base de données en INNODB : adapter les paramètres sous app > config > config(.local)*.neon (* Si vous testez l'appli en local ou en production). Attention à l'indentation au sein du fichier .neon !

Prérequis

⚠ Mémo :

nette-structure.png

  • Il y a autant de vues qu’il y a d’actions/méthodes dans le presenter.
  • Il y a autant de dossiers de vue (sous Templates) qu’il y a de presenters.

Standards de programmation

  • L'indentation est importante : elle se fait par tabulations et non par espaces.
  • Les lignes de codes sont limitées à 120 caractères (idéalement, 80 ou moins).
  • Les constantes TRUE, FALSE, NULL sont toujours en capitales.
  • Nette recommande l'utilisation de l'UTF-8 et de l'ASCII en guise d'encodage de caractères.
  • L'utilisation de <?php est la seule manière valable d'amorcer du code en php (oubliez les '<?= ?>').
  • La fermeture ?> doit être omise dans les fichiers qui contiennent du PHP exclusivement.
  • Limitez-vous à une déclaration par ligne.
  • Aucune espace ne doit traîner à la fin des lignes de code.
  • Les noms de fichiers et noms de classes doivent être identiques.
  • La déclaration de variables, fonctions ou constantes globales est fortement déconseillée.
  • @, l'opérateur de contrôle d'erreur (ou de mise en veilleuse) doit être commenté comme suit :
@mkdir($dir); // @ - directory may exist

Conventions de nommage

  • Les classes doivent être déclarées en UpperCamelCase.
  • Les propriétés et méthodes doivent être déclarées en lowerCamelCase.
  • Les opérateurs de comparaison forts (===/!==) sont préférés aux autres (==/!=).
  • Les structures conditionnelles utilisent elseif et non else if.
  • Se limiter à des caractères alphanumériques dans le choix des noms.
  • Les variables doivent être explicites : $i, $n ne sont admises que pour faire office de compteur dans les boucles.
  • La concaténation demande un espace avant et après le "."
  • Les tableaux doivent être déclarés avec la déclaration entre [], avec des espaces après chaque virgule pour augmenter la lisibilité du code : [$un, $deux, $trois]
  • Le nom des interfaces commence toujours par un "I". Exemple : IPresenter.
  • Les mots-clés "extends" et "implements" doivent être déclarés sur la même ligne que le nom de la classe.

Sources