View Helper, Routennamen und Action in der View

Oft benötigt man in den View – Files die aktuelle Route, den Controllernamen und den Actionnamen. In Zend2 ist es ganz einfach zu lösen. Zuerst brauch man einen View Helper, der einen in den View – Files zur Verfügung steht.


use Zend\View\Helper\AbstractHelper;

class RouteHelper extends AbstractHelper
{
    
    protected $routeMatch;

    public function __construct($routeMatch){
        $this->routeMatch = $routeMatch;
    }

    public function __invoke(){
        return $this;
    }
    
    public function getControllerName(){
        if($this->routeMatch != null){
           return $this->routeMatch->getParam('controller', 'index');  
        }
        return '';
    }
    
    public function getMatchedRoute(){
        if($this->routeMatch != null){
           return $this->routeMatch->getMatchedRouteName(); 
        }
        return '';
    }
    
    public function activeIfMatch($route, $action = null){
        if($this->getMatchedRoute() == $route){
            if($action == null || $this->routeMatch->getParam('action', 'index') == $action){
                return 'active';
            }
        }
        return '';
    }
}


In der Module Klasse


...
public function onBootstrap(MvcEvent $e)
    {
        $e->getApplication()->getServiceManager()->get('translator');
        $eventManager        = $e->getApplication()->getEventManager();
        //view helper for route name
        $e->getApplication()->getServiceManager()->get('viewhelpermanager')->setFactory('routeHelper', function($sm) use ($e) {
            $viewHelper = new \AAUShare\Helper\RouteHelper($e->getRouteMatch());
            return $viewHelper;
        });
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);
        
    }
...

Wichtig ist hier vor allem die Methode activeIfMatch welche eine Route und eine Action erwartet. Wenn diese erfüllt sind so wird active zurückgegeben. Dies habe ich vor allem mit den Gedanken gebaut nur mehr diese Methode in der View ausgeben zu müssen (z.B. um den Menüpunkt als active zu Markieren). In der View dann:


... <li class="<?php echo $this->routeHelper()->activeIfMatch('home'); ?>"><a  ... 

Doctrine2 Subquerys und der IDENTITY Helfer

Vor allem bei Suchquerys kommt man oft nicht an Subquerys vorbei. Konkretes beispielsweiße ist hier die Suche nach Einträgen mit gewissen Tags im AAUShare System. Es gibt hier das Post Entity mit den Mappings zu den Tags, bzw PostTags(Mapping zwischen Tags und Posts). Um hier z.B. alle Posts zu erhalten die mit einen Tag besitzen ist noch recht unkompliziert, möchte man jedoch nur Posts mit z.B. den Tags Informatik und ESOP so wird die Query schon komplizierter. Ich habe dazu folgende Methode implementiert:


public function findByTagTitles(array $tags, $additive){
        $tagtsrings = '';
        $logicalop = 'OR';
        if($additive == true){
            $logicalop = 'AND';
        }
        //create query statement ors and ands
        for($i=0; $i<sizeof($tags); $i++){
            $tagtsrings .= ' p.id IN (SELECT IDENTITY(pt'.$i.'.post) FROM AAUShare\Entity\PostTag pt'.$i.' JOIN pt'.$i.'.tag t'.$i.' WITH t'.$i.'.title LIKE :tag'.$i.')';
            if(($i+1) < sizeof($tags)){ //last and or or
                $tagtsrings .=' '.$logicalop;
            }
        }
        if(sizeof($tags)==0){
            $tagtsrings.='1';
        }
        //
        $querystring = 'SELECT p FROM AAUShare\Entity\Post p WHERE'.$tagtsrings;
        $query = $this->getEntityManager()->createQuery($querystring);
        //bind
        for($i=0; $i<sizeof($tags); $i++){
            $query->setParameter('tag'.$i, $tags[$i]);
        }
        //do query
        return $query->getResult();
    }

Hier wird ein Array mit Tags übergeben, nach welchen gesucht wird, der zweite Parameter bestimmt ob die Tags mit „AND“ oder mit „OR“ verknüpft werden. Interessant sind hierbei vor allem die for-Schleifen. In der ersten werden die Tags in Subquerys gepackt und geprüft ob der Post in der Menge der zurückgelieferten postids vorhanden ist. Wichtig ist hierbuach der IDENTITY Befehl für Doctrine2, damit wird auf den Foreign Key der OneToMany Relation in PostTag zugegriffen. Außerdem werden die Subquerys durchnummeriert und auch damit die Parameter

In der zweiten for-Schleife werden die durchnummerierten Query Parameter noch mit den Werten aus den Tags verknüpft.

Neu anlegen von vorhanden Entities by ManyToOne Relationen, obwohl Entity bereits existiert

Heute bin ich auf ein im ersten Moment doch sehr seltsames Verhalten von Doctrine2 gestoßen. Benutzt man verschiedene Entity Manager innerhalb einer Applikation, so beispielsweise bei einer ManyToOne Relation versucht das hinzugefügte Entity neu anzulegen

  /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="user")
     * @ORM\JoinColumn(name="userid", referencedColumnName="id")
     */
    protected $createdFrom;

Ursache sind wie bereits erwähnt die beiden verschiedenen EntityManager Instanzen. Abhilfe schafft hier logischerweiße das verwenden von der gleichen Instanz, oder aber das Entity aus dem EntityManger neu zu laden mit dem der Speichervorgang durchgeführt werden soll. Wichtig ist jedoch hier zu beachten, das man keine inkonsistenzen schafft wenn man beide EntityManager verwendet!

Anbei noch der Link zu den Stackoverflow Beitrag, der mir hier weitergeholfen hat.

PHP Markdown in Zend2

Oft hat man das Problem das bei von User eingetragenen Daten bzw. Textfeldern zwar Formatierungen erwünscht sind, jedoch auf der Serverseite für viel mehr Aufwand sorgen. Abhilfe schafft hier Markdown, eine eigene Notation für Formatierungen die dann in html umgewandelt wird. Es gibt hier bereits einige fertige Implementierungen für alle möglichen Sprachen, z.B. auch für PHP. (https://github.com/michelf/php-markdown). Hier habe idh die Klassen kopiert und in ein eigenes Modul gepackt. Die Ordnerstruktur sieht wie folgt aus:

Michelf
| Module.php
|__src
__|__Michelf
____|__Markdown.php

Jetzt muss nur noch im application.config.php File das Modul eingebunden werden ..


'modules' => array(
         ...,
        'DoctrineORMModule',
        'Michelf',

    ),

Nun kann der Text leicht in HTML umgewandelt werden


echo \Michelf\Markdown::defaultTransform($text);

AAUShare – Teile Lösungen und Skripten

Ein Studienkollege hat mich auf eine gute Idee für ein neues Wochenendprojekt gebracht. Es handelt sich um eine Online Plattform in welcher Studenten ihre Skripten teilen können. Man sollte sich dort einfach einloggen können und Skripten und Lösungen hochladen können bzw. nach diesen Suchen können.

Ein solches System müsste mit wenig Tabellen auskommen können. Die Datenbank besteht hauptsächlich aus der Tabelle Post – Ein Eintrag mit einen Titel, Text, Files und Tags sowie einen Rating – welcher in mehrere Tabellen aufgespalten worden ist. Es sollen pro Eintrag mehrere Files hochgeladen werden können, sowie ein Rating möglich sein. Um missbrauch zu vermeiden sollte man als User nur einmal pro Beitrag abstimmen können. Um dies zu gewährleisten muss ein Loginbereich geschaffen werden. Dies sollte mit Sozialen Netzwerken schaffbar sein, da heutzutage fast jeder einen Account in einen der Netzwerke besitzt.
Außerdem sollte man den Post auch Tag’s hinzufügen können um leichter gefunden werden zu können, hierbei habe ich an etwas ähnliches wie bei StackOverflow gedacht, da dies dort meiner Meinung nach einfach und schnell funktioniert.
Eine weitere wichtige Funktionalität ist das Kommentieren von Beiträgen, bzw. die Möglichkeit eine Antwort zu erstellen, wobei es wichtig ist zwischen den beiden zu unterscheiden. Eine Antwort kann wieder ein Beitrag mit Files sein, ein Kommentar sollte jedoch lediglich aus Text bestehen und auch in der Anzeige entsprechend gekennzeichnet werden.

Aufgrund der obigen Funktionalitäten habe ich mir folgendes ER-Diagramm überlegt:

ER

Ich habe hier auch mal das Rating aus dem Post herausgezogen, aus der Überlegung heraus, das für das RatingUpdate nicht auf die Post Tabelle zugegriffen werden muss, ich bin mir aber noch nicht sicher ob das sinnvoll ist.

Als Technologien habe ich ZF2, Doctrine2, Bootstrap (+Font Awesome), JQuery (+Plugins) gewählt, da ich mit diesen das meiset abdecken sollte.

Die Grundstruktur ist gleich wie bei meinen letzten Posts zu Zend / Doctrine. Ein Stolperstein bei den Entities waren die Mappings von Post zu File und Post zu PostTag. Es musste z.B. beim Mapping Post – PostTag auf der PostTag Seite folgendes hinzugefügt werden.


/**
* @ORM\ManyToOne(targetEntity="Post", inversedBy="post")
* @ORM\JoinColumn(name="postid", referencedColumnName="id")
*/
protected $post;

Ein einfaches Mapping auf die postid hat nicht funktioniert, warum genau habe ich nicht herausfinden können.

User Auth

Da die OAuth Lösungen auf die schnell nicht funktionierten habe ich mich Entschieden das Auth Beispiel von https://github.com/samsonasik/SanAuth/ zu modifizieren und an meine Bedürfnisse anzupassen. In dieser Auth wird noch nicht auf Doctrine Entities zurückgegriffen, daher habe ich mithilfe von https://github.com/doctrine/DoctrineModule/blob/master/docs/authentication.md dieses angepasst. Die Anpassungen werde ich in einen anderen Beitrag nochmal genauer erklären.

Nächsten Schritte

Aktuell funktioniert das hochladen von Dateien und das Erstellen von Posts, sowie das einloggen dran. Als nächstes kommen die Kommentare und die Antwortfunktion dran. Außerdem müssen sich noch User registrieren können, dies werde ich über ein Aktivierungsmail lösen. Der nächste Blogeintrag kommt also bald.

Scroll to top