Confluence and REST


Introduction

This tutorial aims at integrating REST functionality into a Confluence plugin. Its purpose is to provide a straightforward step-by-step documentation that is suited even for users who are just making their first experiences with the Confluence framework. Since the tutorial got quite comprehensive and contains many Confluence procedures of more general nature (e.g. creating macros, including web resources), it can also be considered a rather broad introduction to Confluence itself. Weiterlesen

Veröffentlicht unter Tech-Blog | Tags , , , , , | Hinterlasse einen Kommentar

Symfony 2 Custom Validation

General
The built-in validations of Symfony 2 are sufficient for the daily usage and validation of basic forms. If you try to build an extended application, you’ll end up needing a more sophisticated validation to check and validate forms.

Building a custom validation in Symfony2 is very easy. All you need are two basic types: The constraint and the validator. Weiterlesen

Veröffentlicht unter Tech-Blog | Tags , , , | Hinterlasse einen Kommentar

Symfony2 – Formulardaten manipulieren

In Symfony2 können zur Manipulation von Formulardaten Data Transformer genutzt werden. Dazu bietet der Data Transformer die Methoden transform und  reverseTransform an, mit denen beim Laden oder beim Absenden des Formulars Daten verändert werden können.

Unser Data Transformer entfernt nach dem Absenden des Formulars aus einem RTE Textfeld von uns festgelegte HTML Tags. Dazu implementieren wir zwei neue Klassen. Den Data Transformer RteTextAreaTransformer und einen neuen Custom Form Type RteTextAreaType. Dieser Form Type ist eine einfache Erweiterung des Standardfelds TextArea und fügt dem FormBuilder mittels appendClientTransformer unseren RteTextAreaTransformer hinzu.

Erstellen des Data Transformer

namespace Scandio\DemoBundle\Form\DataTransformer;

use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\DataTransformerInterface;

class RteTextAreaTransformer implements DataTransformerInterface
{
    public function __construct()
    {
    }

    /**
     * @param  mixed $value
     * @return mixed
     */
    public function transform($value)
    {
       return $value;
    }

    /**
     * Transforms the value from the request and validates the fields according to the api
     *
     * @param  array $array
     * @return array
     */
    public function reverseTransform($value)
    {
       if (!empty($value)) {
            $value = strip_tags($value, "<p><br><ul><li>");
       }
       return $value;
    }
}

Erstellen des Custom Form Types

namespace Scandio\DemoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Scandio\DemoBundle\Form\DataTransformer\RteTextAreaTransformer;

class RteTextAreaType extends AbstractType
{

    public function __construct()
    {
    }

    public function buildForm(FormBuilder $builder, array $options)
    {
        $transformer = new RteTextAreaTransformer();
        $builder->appendClientTransformer($transformer);
    }

    public function getParent(array $options)
    {
        return 'textarea';
    }

    public function getName()
    {
        return 'textarea_rte';
    }
}

Registrierung des Form Types als Service

Den Custom Form Type in der config.yml als Service registrieren

services:
    scandio.type.textarea_rte:
        class: Scandio\DemoBundle\Form\RteTextAreaType
        tags:
            - { name: form.type, alias: textarea_rte }

Verwendung des Transformers mit oder ohne Custom Form Type

Beim Testen eines Data Transformers fiel auf, dass ein grundlegender Unterschied zwischen der Anwendung auf ein bestehendes Feld und der Anbindung innerhalb eines Custom Field Types vorliegt.

Wird in einem bestehenden Formular der Data Transformer mittels appendClientTransformer hinzugefügt wird der Transformer auf das komplette Formular angewendet. Bei einem Custom Form Type bezieht er sich nur auf das Custom Form.

Veröffentlicht unter Tech-Blog | Tags , | Hinterlasse einen Kommentar

Symfony2 – Custom Annotations

Allgemeines

Symfony2 macht sehr viel Gebrauch von Annotations. Das vereinfacht den Programmcode und steigert den Wiederverwendungswert von Applikationen bzw. Programmteilen. Bspw. können so ganze Bibliotheken für mehrere Projekte aufgebaut werden.

Ein Beispiel für Annotations findet man im Controller, wenn man ein Standard Projekt über die Symfony Console generiert:

<?php
namespace Scandio\PlaygroundBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class DefaultController extends Controller
{
    /**
     * @Route("/hello/{name}")
     * @Template("ScandioPlaygroundBundle:Default:index")
     */
    public function indexAction($name)
    {
        return array('name' => $name);
    }

    //...
}

@Route ist hierbei die für die Action verwendete URL (näheres hier). @Template(“ScandioPlaygroundBundle:Default:index”) ist die Annotation zum Template, welches zur Ansicht verwendet wird (mehr dazu hier).

Annotations vermüllen den Programmcode nicht und können wunderbar ausgelagert werden. In Symfony2 werden Annotations über Event Listener bzw. Subscriber im System registriert.

Eigene Annotations erstellen

Eigene Annotations erstellen zu können, ermöglichen dem Programmierer modularen und wiederverwendbaren Code zu schreiben. In Symfony2 ist dies sehr einfach.

Best Practice ist hier eine Trennung von Symfony2 abhängigem Code (z.B: Controller-Listener Aufrufe oder Symfony 2 Response Objekte, etc…) und global wiederverwendbarem Code (bsp. String Manuplation, Feld-Validierungen, etc). Symfony2 abhängiger Code sollte dabei in /vendor/bundles/Scandio/[bundle-name] erstellt werden. Unabhängiger Code kann unter /vendor/scandio/lib/… gespeichert werden. Dadurch ist es möglich, den Annotations Code in Symfony2-fernen Projekten wieder zu verwenden.

@CreatedAt Annotation als Beispiel

Im Folgenden wird davon ausgegangen, dass der Entwickler eine lauffähige Symfony2 (stable) Version auf dem Webservice besitzt.

Programmierer sind darauf bedacht möglichst wenig Code doppelt zu schreiben bzw. zu kopieren. Das trifft besonders bei einfachen Funktionen zu. Um z.B. den Zeitpunkt der ersten Speicherung in der Datenbank zu protokollieren kann der Entwickler entweder bei jedem Speichern eines Objekts folgenden Code schreiben:

$article = new Article();

// do something with the article

$article->setCreatedAt(new \DateTime());
$em = $this->getDoctrine()->getEntityManager();

$em->persist($article);
$em->flush();

Im Eifer des Gefechts kann das Setzen des Datums eines Artikels auch einfach mal vergessen werden. Das produziert unnötige Fehler. Besser ist es, gleich in der Definition des Artikels eine Annotation anzufügen, die automatisch beim Erstellen eines Artikels das Datum einträgt. Wir nennen diese Annotation der Einfachheit halber “@CreatedAt”. Eine Ausarbeitung zu “@Timestamp” oder dergleichen ist dem Entwickler überlassen.

Am Ende des Tutorials sieht die Article.php Entität wie folgt aus:

<?php

namespace Scandio\PlaygroundBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Scandio\Annotations as ScandioAnnotation;

/**
* Scandio\PlaygroundBundle\Entity\Article
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Scandio\PlaygroundBundle\Entity\ArticleRepository")
*/
class Article
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $title
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var string $description
     *
     * @ORM\Column(name="description", type="string", length=255)
     */
    private $description;

    /**
     * @var datetime $createdAt
     *
     * @ORM\Column(name="createdAt", type="datetime", nullable=true)
     * @ScandioAnnotation\CreatedAt()
     */
    private $createdAt;

    // ...
}

@ScandioAnnotation\CreatedAt() zeigt an, dass das Feld $createdAt beim Erstellen befüllt werden soll. Hierbei ist der Name des Feldes unwichtig.

1. Schritt: Annotations

Zuerst müssen wir die Annotation-Klasse CreatedAt erstellen. Diese kann man entweder im Bundle oder im Vendor Verzeichnis speichern. Wie oben bereits erwähnte ist eine Trennung zwischen Symfony2 relevantem und unabhängigem Code wünschenswert. Möchte man seine Annotations-Klasse noch um Properties erweitern (bsp: durch type=”date”) definiert man einfach nur ein Attribut mit dem gleichen Namen in der Annotations-KLasse.

<?php
// .../vendor/scandio/lib/Scandio/Annotations/CreatedAt.php

namespace Scandio\Annotations;

use Doctrine\Common\Annotations\Annotation;

/**
 * Annotation
 */
class CreatedAt extends Annotation
{
    $type = 'datetime';
}

2. Schritt: AnnotationsDriver

Damit die Annotations auch einen Effekt haben, muss etwas mit ihnen an einem Zeitpunkt während der Action geschehen. Bei CreatedAt ist dies der Zeitpunkt kurz vor der Speicherung der Daten nach allen Änderungen des Users im Controller. Dadurch sind bereits alle Speicherungsaktivitäten des Users beendet und man muss keine Nebeneffekte fürchten (ausser von weiteren Listeners. Mehr zu Listeners im nächsten Schritt).

<?php
// .../vendor/scandio/lib/Scandio/Annotations/Driver/CreatedAt.php
namespace Scandio\Annotations\Driver;

use Scandio\Annotations\CreatedAt;

use Doctrine\Common\Annotations\Reader;
use Doctrine\ORM\Event\LifecycleEventArgs;

class CreatedAtDriver
{
    private $reader;

    public function __construct(Reader $reader)
    {
        // initialise Doctrine Reader
        $this->reader = $reader;//get annotations reader
    }

    public function prePersist(LifecycleEventArgs $event)
    {
        // get article
        $entity = $event->getEntity();

        // get Reflection of Class
        $reflectionProperties = new \ReflectionClass($entity);
        // get all properties with their reflections
        $properties = $reflectionProperties->getProperties();

        $annotation = false;
        $property = false;
        // iterate over all properties to check for the @CreatedAt-Annotation
        foreach ($properties as $prop) {
            $annotation = $this->reader->getPropertyAnnotation(
                $prop,
                'Scandio\Annotations\CreatedAt'
            );
            // get the name of the property/field
            if (!empty($annotation)) {
                $property = $prop->getName();
            }
        }

        if (!empty($property)) {
            // getCreatedAt and setCreatedAt
            $setMethod = 'set'.ucFirst($property);
            $getMethod = 'get'.ucFirst($property);
            if (method_exists($entity, $setMethod) && method_exists($entity, $getMethod)) {
                // add the current date if not available
                $date = $entity->{$getMethod}();
                if (empty($date)) {
                    $entity->{$setMethod}(new \DateTime());
                }
            }
        }
    }
}

3. Schritt: Listener

Damit wir auch den Driver in die aktuelle Symfony2 Anwendung “einklinken” können, müssen wir einen Listener in Symfony registrieren, der beim Aufruf des prePersist-Doctrine-Events abgefeuert wird. Dies schaffen wir durch Einbindung der folgenden config (entweder in /app/config.yml oder im Bundle):

services:
    created_at_listener:
        class: Scandio\Annotations\Driver\CreatedAtDriver
        tags:
          - {name: doctrine.event_listener, event: prePersist}
        arguments: [@annotation_reader]

@annotation_reader liefert uns den Doctrine Annotation Reader per DependencyInjection, der die Annotation interpretiert. “doctrine.event_listener” ist das Doctrine-Event und prePersist ist die Event Methode in die wir uns einklinken wollen.

Fazit

Nach diesen Änderungen kann nun die @CreatedAt Annotation in Doctrine Entitäten verwendet werden. Man ist aber nicht nur auf Doctrine Entitäten beschränkt, sondern kann dies auf fast alle Listeners oder Projekte erweitern.

Annotations liefern dem Entwickler ein weiteres Werkzeug um Programmcode einfach, sauber und wiederverwendbar zu halten.

Veröffentlicht unter Tech-Blog | Tags , , , , , | Hinterlasse einen Kommentar

Symfony2 Web Applikationen erstellen: Teil 4 – Update auf 2.0.9 und Views/Twig

Nach einer (zu) langen Pause, geht es heute weiter mit dem 4. Teil des Symfony2 Tutorials.

Update
Seit dem letzten Artikel vergangenen Jahres hat sich bei Symfony 2 einiges getan und wurde mittlerweile in einer stabilen Version veröffentlicht. Das wollen wir hier nicht ignorieren und haben das Taskmanager Projekt auf die Symfony Version 2.0.9 geupdated.

Am einfachsten ist es die aktuelle Version des Frameworks von symfony.com herunterzuladen und in einen seperaten Ordner zu entpacken. Aus unserem “alten” Projekt entnehmen wir den /src Ordner und kopieren ihn in das neue Verzeichnis.
Da wir wenige Änderungen am Symfony-Grundsystem vorgenommen haben, müssen wir so nur die Config-Einstellungen ändern und eine Kleinigkeiten in den Models nachziehen, um zu einer lauffähigen Version zu kommen.

Die alten Config Dateien können wir größtenteils ignorieren und diese im neuen Projektordner verwenden.

1. /app/config/routing.yml: Hier übernehmen wir die Routing-Informationen aus dem alten Projekt und kopieren sie in die routing.yml im neuen Verzeichnis. Dadurch haben wir alle Routen eingeschlossen, die wir für unseren Taskmanager brauchen.
2. /app/config/parameters.ini: Die parameters.ini kann nahezu 1:1 kopiert werden, da es sich hier nur um den DB Zugang handelt
3. /app/config/config.yml: Die config.yml kann nahezu unverändert übernommen werden. Wichtig ist, dass wir hier den translator service anschalten (# entfernen). Wer auch PHP als Templating Engine verwenden möchte, kann dies in den templating Einstellungen eintragen (mehr dazu später).

Im Task-Model müssen wir die Reflections über den Attributen ändern (…/src/Scandio/DaylogBundle/Entity/Task.php).
Die ORM-Reflections von Doctrine müssen von @orm:id in @ORM\id geändert werden. Zusätzlich muss noch das Mapping am Anfang der Datei hinzugefügt werden:

<?php
namespace Scandio\DaylogBundle\Entity;

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

/**
 * Task Model
 * @ORM\Entity(repositoryClass="Scandio\DaylogBundle\Entity\TaskRepository")
 */
class Task {
	// ...
}

Nach den Änderungen sollte das System wieder lauffähig sein. Wichtig ist auch den Inhalt des /app/cache Ordners zu löschen, um alten Code zu vermeiden.

Allgemeines zu Views

Views werden in Symfony standardmäßig mit Hilfe der Twig Template Engine ausgeliefert. Twig ist wie Symfony ebenfalls von Fabien Potencier kreiert worden und lässt sich ganz im Stile von Symfony2 auch ohne das Framework betreiben.
Dadurch erhält man eine leistungsstarke Template-Engine, die schnell und einfach erlern- und einsatzbar ist.

Wie wir bereits in den vorherigen Teilen gesehen haben, liefert Symfony2 als Return-Werte einer Controller Action idealerweise ein Response-Objekt zurück.
Da wir nun Twig für das Darstellen unserer Views ausgewählt haben, können wir die Controller Funktion $this->render(…) verwenden.

// src/Scandio/DaylogBundle/Controller/TaskController.php
class TaskController extends Controller {

    /**
     * Index Function to show all tasks
     */
	public function indexAction($sort = 'priority')
    {
        // get Doctrine ORM Entity Manager
        $em = $this->getDoctrine()->getEntityManager();
        // get the Repository
        // location \Scandio\Entity\TaskRepository --> look at the Header Annotation of the Task Entity
        $tasks = $em->getRepository('ScandioDaylogBundle:Task')
                    // custom find Function to take advantage of ordering!
                    ->getAll($sort);

        // defining the view vars
        $viewVars = array(
            'tasks' => $tasks
        );
        return $this->render('ScandioDaylogBundle:Task:index.html.twig', $viewVars);
    }

	//...
}

Diese erstellt für uns mit Hilfe des Twig System (besser gesagt, das Templating System, aber dazu später mehr) ein Response Objekt, welches die gerenderte View beinhält.

Standardmäßig werden views im Bundle-Ordner …/Resources/views erstellt. Wenn wir im Controller folgende View spezifizieren:

$this->render('ScandioDaylogBundle:Task:index.html.twig', $viewVars);

wird dies in die Ordnerstruktur

/Scandio/DaylogBundle/Resources/views/Task/index.html.twig

“übersetzt”. Möchte man z.B. eine Controller unabhängige View erstellen, lässt man einfach das “Task” im render-Funktionsaufruf weg: $this->render(‘ScandioDaylogBundle::index.html.twig’, $viewVars); Dies ist z.B. für eine layout.html.twig sehr sinnvoll, die das Gerüst der Seite beinhält.

Das Twig System hat noch einige Kniffe parat: Möchte man eine View in einem Bundle überschreiben, fügt man eine View mit derselben Ordnerstruktur im /app/Resources/ Ordner hinzu.
Bsp. macht das Sinn, wenn man ein externes Bundle wie FOSUser einbindet. Mehr über Twig-Namespaces findet man hier.

View Vererbung

In Twig ist es möglich, views zu vererben. In der Eltern View werden Blöcke definiert, die durch die Kind Views ersetzt bzw. erweitert werden (beides ist möglich!). Um dies zu verdeutlichen hier das Beispiel unserer Tasks index Seite:

…/Resources/views/base.html.twig

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>{%block title%}Scandio Daylog Project!{%endblock%}</title>
        <!--[if lt IE 9]>
            <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
        <link rel="stylesheet" type="text/css" media="all" href="{{ asset('bundles/scandiodaylog/css/reset.css') }}" />
        <link rel="stylesheet" type="text/css" media="all" href="{{ asset('bundles/scandiodaylog/css/text.css') }}" />
        <link rel="stylesheet" type="text/css" media="all" href="{{ asset('bundles/scandiodaylog/css/960.css') }}" />
        <link href="{{ asset('bundles/scandiodaylog/css/general.css') }}" rel="stylesheet" type="text/css" media="screen, projection" />
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <!-- Adding "maximum-scale=1" fixes the Mobile Safari auto-zoom bug: http://filamentgroup.com/examples/iosScaleBug/ -->
    </head>

    <body lang="en">
        <div id="wrapper" class="container_12">
            {% block body %}{%endblock%}
        </div>
    </body>
</html>

…/Resources/views/Task/index.twig

{% extends 'ScandioDaylogBundle::base.html.twig' %}

{% block body %}
    <div class="grid_12">
        <h1>
            Daylog <a href="http://www.scandio.de">Scandio</a> Symfony2 Tutorial
            <a href="{{ path('scandio_daylog_add_prepare') }}">+</a>
        </h1>
    </div>

    <div class="clear"></div>

    {% for num, task in tasks %}
        <div class="grid_1 {{ num%2==0 ? 'even' : 'odd' }}">
            [<a href="{{ path('scandio_daylog_delete', {'taskId': task.id} ) }}" title="delete">x</a>]
            [<a href="{{ path('scandio_daylog_edit_prepare', {'taskId': task.id} ) }}" title="edit">e</a>]
            {% if task.isDone %}
                [<a href="{{ path('scandio_daylog_markAsUndone', {'taskId': task.id} ) }}" title="mark as undone">u</a>]
            {% else %}
                [<a href="{{ path('scandio_daylog_markAsDone', {'taskId': task.id} ) }}" title="mark as done">d</a>]
            {% endif %}
        </div>

        <div class="grid_1 {{ num%2==0 ? 'even' : 'odd' }}">
            {% if task.isDone %}
                <strike>{{ task.getPriorityAsString() }}</strike>
            {% else %}
                {{ task.getPriorityAsString() }}
            {% endif %}
        </div>

        <div class="grid_10 {{ num%2==0 ? 'even' : 'odd' }}">
            {% if task.isDone %}
                <strike><b>{{ task.title }}</b></strike>
            {% else %}
                <b>{{ task.title }}</b>
            {% endif %}
        </div>

    {% else %}
        <div class="grid_12">
           No Tasks found!
        </div>
    {% endfor %}

    <div class="clear"></div>

    <div class="grid_12">
        sort by:
        <a href="{{ path('scandio_daylog_index_sort', {'sort': 'priority'} ) }}" title="order by priority">prio</a> |
        <a href="{{ path('scandio_daylog_index_sort', {'sort': 'done'} ) }}" title="order by mark-as-done">done</a>
    </div>
{% endblock %}

In der Kind View (index.html.twig) wird zuerst die Eltern View über das keyword “extends” definiert. Als nächstes wird der Block “content” durch dem Block “content” aus der Kind View erstetzt.
Die restlichen Blöcke und View Inhalte aus der Eltern View bleiben erhalten.

Views mit Daten befüllen

Um unserer Anwendung ein wenig Leben einzuhauchen müssen wir die Views mit dynamischen Daten befüllen. Twig nimmt uns hier ebenfalls viel Arbeit ab.

Im allgemeinen werden Daten mit {{ task.name }} ausgegeben. “{{” und “}}” signalisieren twig ein “echo” um Daten auszugeben.
Mit Hilfe des Punktes in “task.name” weißt man Twig an, im Task Objekt (Arrays gleichermaßen) nach der Funktion isName(), getName() oder name() zu suchen und diese auszugeben.
Ist diese nicht vorhanden oder ist der assoziative Array Key nicht definiert, wird ein Fehler geworfen.

Anweisungen werden durch {% if … %} {% else %} {% endif %} definiert. Diese sind nahezu identisch zu den PHP-Äquivalenten. Mehr dazu findet man hier.
Um über Listen zu iterieren, verwendet man eine for-Schleife: {% for num, task in tasks %} {% endfor %} wobei num hier der Iterator-Zähler ist und Task das jeweilge Objekt.

Damit wäre auch das allgemeine Rüstzeug erleutert und man kann nun loslegen und Views erstellen.

Alternativen zu Twig
Wie bereits oben angesprochen lässt sich Symfony2 auch ganz ohne Twig verwenden. Wem die Template-Enginge zu umständlich ist kann auch ganz normales PHP verwenden.
Hierzu muss nur in der config unter “templating: { engines: ['twig'] }” php hinzugefügt werden: “templating: { engines: ['twig', 'php'] }”.
Nun kann man auch views mit der Endung .html.php rendern. Dies ist besonders interessant wenn man verschiedene Formate für die gleiche Action anbieten möchte. Näheres dazu in der Dokumentation.

Fazit
Twig ist ein sehr mächtiges Werkzeug für professionelle Webseitengestaltung. Die Trennung von Programmcode und Views wird durch Twig sehr stark unterstützt.
Man hat größere Schwierigkeiten Programmcode in Twig zu integrieren, als diesen in die passende Models oder Controller auszulagern. Das erhöht die Wiederverwendbarkeit und Lesbarkeit von Code.

Veröffentlicht unter Softwareentwicklung, Tech-Blog | Tags , , , , , | Hinterlasse einen Kommentar