PHP Code-Analyse mit Ant

Dieser Beitrag ist Teil 1 von 2 in der Serie Continuous Integration

    Graph LeadIch mag Code. Nicht jeden natürlich, „Douchebag“ Code nervt, Code der aussieht wie ein schreiender Quasimodo oder Code der von einem Dr. Frankenstein in Ausbildung erstellt wurde sind bemitleidenswert und sehr viel Code stinkt. Code, der gefällt (wie beispielsweise der von Frigidor) ist clever ohne zu bluffen, kurz aber nicht kryptisch, tut etwas, ist lesbar und hat einige Wows drin.

    Diese Bewunderung sollte in Zahlen gefasst werden: mit Statistiken, Diagrammen, Balken und Graphen. Dies dient zur Vorbereitung der „Continuous Integration“ mit Jenkins/Hudson, die in dieser Serie behandelt wird. Als Quellen haben vor allem jenkins-php und ein äusserst Lesbarer Artikel im Entwickler Magazin 01/11 gedient.

    Hier wird nun gezeigt, wie mit einem Befehl:

    • Die Unittests durchführt.
    • Qualitative Softwaremetriken mit PHP Depend misst: Hierarchietiefe, Komplexität, …
    • Unschöne Teile mit PHPMD identifiziert.
    • Copy-Paste-Verbrecher aufspührt mit phpcpd.
    • Den Coding-Style prüft mit dem PHP Code Sniffer.
    • Quantitative Softwaremetriken mit phploc misst.
    • Die PHPDoc-Doku erstellt.
    • Die Resultate mit dem PHP Code Browser schön darstellt.

    Weiterlesen

    Automatisches und mehrfaches submitten von Formularen

    Wirre Gedanken

    FormularDieser Beitrag ist dem Titz gewidmet. Dem Titz, der sich aufgeregt hat :pirate-grumble: , obwohl es gar nicht nötig gewesen wäre und der sich nun mit einer gewissen Teilnehmerredundanz anfreunden muss. Hauptsache ist doch, das PHP fliesst und die Variablen bleiben sauber… Und ein Bierchen würde ich auch noch springen lassen :bier: …

    Eine wichtige Frage zu Beginn: Wieso sollten wir denn automatisch und mehrfach Formulare submitten wollen? Hmm, um den Titz zu ärgern? Weil wir es können? Weil man manchmal tun muss, was man tun muss? Weil man seine 84 Kinder an einem elektronischen Fussballturnier anmelden will :laola: (hat eigentlich schon jemand bemerkt, dass ich neue Smilies und unglaublich Freude daran habe?)? Oder weil man über einen Wettbewerb gestolpert ist, der ebendies nicht verbietet (also das Mehrfachsubmitten, nicht das Kinderanmelden oder die neuen Smilies) und der kein Captcha hat (vielleicht, weil es der Titz vergessen hat)?

    Nachtrag 31.07.2008: Schenken mir doch so unglaublich nette Mitarbeiter einer Versicherung heute morgen am Bahnhof einen Müsliriegel. Und auf diesem eher gesunden Teil hat es, ja rate, oh wissbegieriges Volk, einen Wettbewerb. Ob der Titz wohl am Abend für eine andere Firma weitercoded? Ich werde mich auf jeden Fall während der Zugfahrt mal damit befassen :computer: .

    Vorgehen

    Erster Schritt: Selenium IDE

    Man könnte nun wie wild losprogrammieren, oder aber einen einfacheren Weg wählen. Ein guter Startpunkt für automatisiertes Browsen generell ist die Selenium IDE. Dieses geniale Teil für den Firefox zeichnet wie ein Macrorecorder alles auf, was im Browser gemacht wird. Hat man die Teilnahme beim Wettbewerb einmal so aufgezeichnet, so müssen nur noch die click’s, die das Form absenden, durch clickAndWait’s ersetzt werden, damit vor dem Weiterausfüllen (bei mehrseitigen Formularen) auf die neue Seite gewartet wird. Es empfiehlt sich, eine Abschlussüberprüfung als letzten Schritt hinzuzufügen, um um kontrollieren zu können, ob die Kinder erfolgreich angemeldet wurden (markieren des „Dankeblabla“, dann rechte Maustaste und assertTextPresent).

    Ein Wettbewerb über 2 Seiten. Aufgezeichnet und bearbeitet mit der Selenium IDE.

    Ein Wettbewerb über 2 Seiten. Aufgezeichnet und bearbeitet mit der Selenium IDE.

    Diesen Testcase kann man nun abspeichern und eigentlich immer wieder ausführen. Ein Klick reicht und Firefox rasselt alles schön durch. Den Namen leicht verändern kann man durch Editieren des Skripts…

    Für den zweiten Schritt sollte man das Testscript als PHP exportieren.

    Zweiter Schritt: Selenium RC

    Die Selenium RC Komponente kann den Browser fernsteuern und so ferngesteuert am Wettbewerb teilnehmen. Um sie unter Linux Debian zum Laufen zu kriegen, war ein Bisschen Gemurkse notwendig.

    Weiterlesen

    Eine eigene Programmiersprache erschaffen? Lexer und Parser in PHP!

    Ich will Gott sein!

    Paper SnippetsWer träumt nicht davon: Eine eigene Programmiersprache, denn der Syntax und das Wort sind Ausdruck! Einen Schritt weiter gehen und vom Programmiersprachenanwender zum Programmiersprachenerschaffer aufsteigen! Auch dies ist nun möglich in PHP, doch leider nur mit minimaler Dokumentation.

    Ich gehe davon aus, dass die Leserin/Der Leser Grundlagen im Compilerbau besitzt. Ansonsten müsste man sich diese noch aneignen. Compilerbau ist rekursiv und somit göttlich! *schwärm*.

    Das Beispiel

    Ich möchte einen Interpreter bauen für normale, arithmetische Ausdrücke wie 9*(8+6)-2/(-4). Klar könnte man diesen einfach durch eval hauen, aber erstens macht das nicht so viel Spass und zweitens ist dieses Beispiel sowas wie das Hello World des Compilerbaus.

    Als BNF habe ich folgendes vor:

    <Expression> ::= '+' <Expression> // Unäres + (Vorzeichen)
    <Expression> ::= '-' <Expression> // Unäres - (Vorzeichen)
    <Expression> ::= <Term>
    <Expression> ::= <Expression> '+' <Term>
    <Expression> ::= <Expression> '-' <Term>
    <Term> ::=  <Factor>
    <Term> ::= <Term> '*' <Factor>
    <Term> ::= <Term> '/' <Factor>
    <Factor> ::=  NUMBER // Ok, hier hab ich abgekürzt ;) Soll der Lexer erledigen...
    <Factor> ::= '(' <Expression> ')'

    Ein Problem stellen die unären Plus- und Minusoperatoren dar. Sie besitzen das gleiche Symbol, nicht aber die gleiche Priorität wie ihre binären Kollegen. Lemon erlaubt keine änderung der Priorität nach Regeln und so funzen jetzt halt auch Konstrukte wie —+++—+1…

    Die Werkzeuge

    Ich verwende PHP_LexerGenerator, der kompatibel zum re2c Format ist und den PHP_ParserGenerator der in grossen Teilen dem LEMON Parser Generator entspricht. Heruntergeladen werden können sie mittels folgenden Zeilen, die allerdings unter Umständen noch eine Anpassungen der Versionsnummer brauchen:

    pear install channel://pear.php.net/PHP_ParserGenerator-0.1.5
    pear install channel://pear.php.net/PHP_LexerGenerator-0.3.4

    Als einziges, weiteres Beispiel für diese Libs ausserhalb der Doku habe ich nur dieses Drupalmodul gefunden.

    Die Realisierung

    Der Lexer

    Für den Lexer muss eine plex Datei erstellt werden. Sie beinhaltet den Rumpf des Lexerobjekts und die lexikalischen Regeln:

    TermLexer.plex

    <?php
    class TermLexer
    {
        private $data;
        public $counter;
        public $token;
        public $value;
        public $node;
        public $line;
        private $state = 1;

        function __construct($data)
        {
            $this->data = $data;
            $this->counter = 0;
            $this->line = 1;
        }

    /*!lex2php
    %input $this->data
    %counter $this->counter
    %token $this->token
    %value $this->value
    %line $this->line
    number = /[0-9]+(\.[0-9]+)?/
    multiplication = /\*/

    division = @/@
    plus = /\+/
    minus = /\-/
    openP = /\(/
    closeP = /\)/
    other = /./
    */
    /*!lex2php
    %statename START
    multiplication {
      $this->token = TermParser::TP_MULTIPLICATION;
      //echo "multiplication: ".$this->value."\n";
    }
    division {
      $this->token = TermParser::TP_DIVISION;
      //echo "division: ".$this->value."\n";
    }
    plus{
      $this->token = TermParser::TP_PLUS;
      //echo "plus: ".$this->value."\n";
    }
    minus{
      $this->token = TermParser::TP_MINUS;
      //echo "plus: ".$this->value."\n";
    }
    openP {
      $this->token = TermParser::TP_OPENP;
      //echo "openP: ".$this->value."\n";
    }
    closeP {
      $this->token = TermParser::TP_CLOSEP;
      //echo "closeP: ".$this->value."\n";
    }
    number {
      $this->token = TermParser::TP_NUMBER;
      //echo "number: ".$this->value."\n";
    }
    other {
      return false;
    }
    */

    }

    So richtig laufen tuts aber noch nicht, da er Konstanten des Parsers benötigt. Die stehen erst zur Verfügung wenn wir den Parser auch gebaut haben.

    Der Parser

    Der Parser wird vom Lexer mit Tokens gefüttert. Die Notation im Lemon-Style unterscheidet sich von Lex und Yacc, ist aber durchaus logisch. Hier der ganze Code, mal hingeknallt:

    TermParser.y

    /* This is an example for a Parser in PHP */
    %name TP_
    %declare_class {class TermParser}
    %include_class
    {
        // states whether the parse was successful or not
        public $successful = true;
        public $retvalue = 0;
        private $lex;
        private $internalError = false;

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

    %token_prefix TP_

    %parse_accept
    {
        $this->successful = !$this->internalError;
        $this->internalError = false;
        $this->retvalue = $this->_retvalue;
        echo "WORKED!!\n\n";
    }

    %syntax_error
    {
        $this->internalError = true;
        echo "Syntax Error on line " . $this->lex->line . ": token '" .
            $this->lex->value . "' count ".$this->lex->counter." while parsing rule: ";
        foreach ($this->yystack as $entry) {
            echo $this->tokenName($entry->major) . '->';
        }
        foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
            $expect[] = self::$yyTokenName[$token];
        }
        echo "\n"; 
        throw new Exception('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN. '), expected one of: ' . implode(',', $expect));
    }

    %left PLUS MINUS.
    %left MULTIPLICATION DIVISION.

    start(res)       ::= expression(expr). { res = expr; }

    /* Unary minus or plus */
    expression(res)  ::= PLUS expression(e). { res = +e; }
    expression(res)  ::= MINUS expression(e). { res = -e; }

    /* The common stuff */
    expression(res)  ::= term(t). { res = t; }
    expression(res)  ::= expression(e1) PLUS term(t2). { res = e1+t2; }
    expression(res)  ::= expression(e1) MINUS term(t2). { res = e1-t2; }

    term(res)        ::= factor(f). { res = f; }
    term(res)        ::= term(t1) MULTIPLICATION factor(f2). { res = t1*f2; }
    term(res)        ::= term(t1) DIVISION factor(f2). { res = t1/f2; }

    factor(res)      ::= NUMBER(n). { res = n; }
    factor(res)      ::= OPENP expression(e) CLOSEP. { res = e; }

    Generieren der eigentlichen Parser und Lexer

    Damit Parser und Lexer generiert werden und Terme gefüttert werden können, sei hier ein kleines Beispiel gegeben:

    <?php
    ///////
    // Parser and lexer have to be created only once! They create two files you can use later
    //////
    // Create Parser
    passthru('php /usr/share/php/PHP/ParserGenerator/cli.php TermParser.y');

    // Create Lexer
    require_once 'PHP/LexerGenerator.php';
    $lex = new PHP_LexerGenerator('TermLexer.plex');

    # Test
    // These are the created files
    include_once("TermParser.php");
    include_once("TermLexer.php");

    $teststr='9*(8+6)-2/(-4)';

    $lex = new TermLexer($teststr);
    // $lex = new TermLexer(file_get_contents($lexerfile));

    echo "-------------------------------\n";
    echo " Parsing $teststr\n";
    echo "-------------------------------\n";
    $parser = new TermParser($lex);
    while ($lex->yylex())  {
        echo "Parsing  {$lex->token} Token {$lex->value} \n";
        $parser->doParse($lex->token, $lex->value);
    }
    $parser->doParse(0, 0);

    print "Returnvalue: ".$parser->retvalue."\n";
    ?>

    Und nu?

    Nun viel Spass… Ich bin auf der Suche nach der EBNF für RegExps, wenn die jemand hat 🙂 …. Ihr könnt den ganzen Code hier auch downloaden…

    Nachtrag September 2008

    Um es klarer zu beschreiben: PHP_LexerGenerator und PHP_ParserGenerator werden nur zum erzeugen des Lexers und des Parsers gebraucht. Es werden zwei php-Dateien generiert, die ganz normal verwendet werden können.