Timeout Problems: Web Server + PHP

What?

First there is an HTTP request and that will hit your Web server, then it will pass the request via TCP- or UNIT-Socket via FastCGI to your PHP-FPM Daemon, here we will start a new PHP process and in this process we will connect e.g. to the database and run some queries.

PHP-Request

The Problem!

There are different timeout problems here because we connect different pieces together and this parts need to communicate. But what if one of the pieces does not respond in a given time or, even more bad, if one process is running forever like a bad SQL-query.

Understand your Timeouts.

Timeouts are a way to limit the time that a request can run, and otherwise an attacker could simply run a denial-of-service with a simple request. But there are many configurations in several layers: Web server, PHP, application, database, curl, …

– Web server

Mostly you will use Apache or Nginx as Web server and in the end it makes not really a difference, there are different timeout settings, but the idea is almost the same: The Web server will stop the execution and kills the PHP process, now you got a 504 HTTP error (Gateway Timeout) and you will lose your stack trace and error-tracking because we killed our application in the middle of nothing. So, we should keep the Web server running as long as needed.

“`grep -Ri timeout /etc/apache2/“`

/etc/apache2/conf-enabled/timeout.conf:Timeout 60

/etc/apache2/mods-available/reqtimeout.conf:<IfModule reqtimeout_module>

/etc/apache2/mods-available/reqtimeout.conf: # mod_reqtimeout limits the time waiting on the client to prevent an

/etc/apache2/mods-available/reqtimeout.conf: # configuration, but it may be necessary to tune the timeout values to

/etc/apache2/mods-available/reqtimeout.conf: # mod_reqtimeout per virtual host.

/etc/apache2/mods-available/reqtimeout.conf: # Note: Lower timeouts may make sense on non-ssl virtual hosts but can

/etc/apache2/mods-available/reqtimeout.conf: # cause problem with ssl enabled virtual hosts: This timeout includes

/etc/apache2/mods-available/reqtimeout.conf: RequestReadTimeout header=20-40,minrate=500

/etc/apache2/mods-available/reqtimeout.conf: RequestReadTimeout body=10,minrate=500

/etc/apache2/mods-available/reqtimeout.load:LoadModule reqtimeout_module /usr/lib/apache2/modules/mod_reqtimeout.so

/etc/apache2/mods-available/ssl.conf: # to use and second the expiring timeout (in seconds).

/etc/apache2/mods-available/ssl.conf: SSLSessionCacheTimeout 300

/etc/apache2/conf-available/timeout.conf:Timeout 60

/etc/apache2/apache2.conf:# Timeout: The number of seconds before receives and sends time out.

/etc/apache2/apache2.conf:Timeout 60

/etc/apache2/apache2.conf:# KeepAliveTimeout: Number of seconds to wait for the next request from the

/etc/apache2/apache2.conf:KeepAliveTimeout 5

/etc/apache2/mods-enabled/reqtimeout.conf:<IfModule reqtimeout_module>

/etc/apache2/mods-enabled/reqtimeout.conf: # mod_reqtimeout limits the time waiting on the client to prevent an

/etc/apache2/mods-enabled/reqtimeout.conf: # configuration, but it may be necessary to tune the timeout values to

/etc/apache2/mods-enabled/reqtimeout.conf: # mod_reqtimeout per virtual host.

/etc/apache2/mods-enabled/reqtimeout.conf: # Note: Lower timeouts may make sense on non-ssl virtual hosts but can

/etc/apache2/mods-enabled/reqtimeout.conf: # cause problem with ssl enabled virtual hosts: This timeout includes

/etc/apache2/mods-enabled/reqtimeout.conf: RequestReadTimeout header=20-40,minrate=500

/etc/apache2/mods-enabled/reqtimeout.conf: RequestReadTimeout body=10,minrate=500

/etc/apache2/mods-enabled/reqtimeout.load:LoadModule reqtimeout_module /usr/lib/apache2/modules/mod_reqtimeout.so

/etc/apache2/mods-enabled/ssl.conf: # to use and second the expiring timeout (in seconds).

/etc/apache2/mods-enabled/ssl.conf: SSLSessionCacheTimeout 300

Here you can see all configurations for Apache2 timeouts, but we only need to change etc/apache2/conf-enabled/timeout.conf`` because it will overwrite `/etc/apache2/apache2.conf` anyway.

PS: Remember to reload / restart your Web server after you change the configurations.

If we want to show the user at least a custom error page, we could add something like:

ErrorDocument503 /error.php?errorcode=503
ErrorDocument 504 /error.php?errorcode=504

… into our Apache configuration or in a .htaccess file, so that we can still use PHP to show an error page, also if the requested PHP call was killed. The problem here is that we will lose the error message / stack trace / request etc. from the error, and we can’t send e.g. an error into our error logging system. (take a look at sentry, it’s really helpful)

– PHP-FPM

Our PHP-FPM (FastCGI Process Manager) pool can be configured with a timeout (request-terminate-timeout), but just like the Web server setting, this will kill the PHP worker in the middle of the process, and we can’t handle the error in PHP itself. There is also a setting (process_control_timeout) that tells the child processes to wait for this much time before executing the signal received from the parent process, but I am uncertain if this is somehow helpfully here? So, our error handling in PHP can’t catch / log / show the error, and we will get a 503 HTTP error (Service Unavailable) in case of a timeout.

Shutdown functions will not be executed if the process is killed with a SIGTERM or SIGKILL signal. :-/

Source: register_shutdown_function

PS: Remember to reload / restart your PHP-FPM Daemon after you change the configurations.

– PHP

The first idea from most of us would be maybe to limit the PHP execution time itself, and we are done, but that sounds easier than it is because `max_execution_time` ignores time spent on I/O (system commands e.g. `sleep()`, database queries (SELECT SLEEP(100)). But these are the bottlenecks of nearly all PHP applications, PHP itself is fast but the external called stuff isn’t.

Theset_time_limit()function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real.

Source: set_time_limit

– Database (MySQLi)

Many PHP applications spend most of their time waiting for some bad SQL queries, where the developer missed adding the correct indexes and because we learned that the PHP max execution time did not work for database queries, we need one more timeout setting here.

There is the MYSQLI_OPT_CONNECT_TIMEOUT and MYSQLI_OPT_READ_TIMEOUT (Command execution result timeout in seconds. Available as of PHP 7.2.0. – mysqli.options) setting, and we can use that to limit the time for our queries.

In the end you will see a “Errno: 2006 | Error: MySQL server has gone away” error in your PHP application, but this error can be caught / reported, and the SQL query can be fixed, otherwise the Apache or PHP-FPM would kill the process, and we do not see the error because our error handler can’t handle it anyway.

Summary:

It’s complicated. PHP is not designed for long execution and that is good as it is, but if you need to increase the timeout it will be more complicated than I first thought. You need for example different “timeout”-code for testing different settings:

// DEBUG: long-running sql-call
// Query(‘SELECT SLEEP(600);’);

// DEBUG: long-running system-call
// sleep(600);

// DEBUG: long-running php-call
// while (1) { } // infinite loop

Solution:

We can combine different timeout, but the timeout from the called commands e.g. database, curl, etc. will be combined with the timeout from PHP (max_execution_time) itself. The timeout from the Web server (e.g. Apache2: Timeout) and from PHP-FPM (request_terminate_timeout) need to be longer than the combined timeout from the application so that we still can use our PHP error handler.

e.g.: ~ 5 min. timeout

  1. MySQL read timeout: 240s ⇾ 4 min.
    link->options(MYSQLI_OPT_READ_TIMEOUT, 240);
  2. PHP timeout: 300s ⇾ 5 min.
    max_execution_time = 300
  3. Apache timeout: 360s ⇾ 6 min.
    Timeout 360
  4. PHP-FPM: 420s ⇾ 7 min.
    request_terminate_timeout = 420

 

Links:

Prepare your PHP Code for Static Analysis

Three years ago I got a new job as PHP developer, before that I called myself web developer because I build ReactJS, jQuery, CSS, HTML, … and PHP  stuff for a web agency. So now I am a full-time PHP developer and I converted a non typed  (no PHPDoc + no native types) project with ~ 10.000 classes into a project with ~ 90% type coverage. Here is what I have learned.

1. Write code with IDE autocompletion support.

If you have autocompletion in the IDE most likely the Static Analysis can understand the code as well. 

Example:

bad:

->get('DB_Connection', true, false);

still bad:

->get(DB_Connection::class);

good:

getDbConnection(): DB_Connection

2. Magic in Code is bad for the long run!

Magic methods (__get, __set, …) for example can help to implement new stuff very fast, but the problem is nobody will understand it, you will have no autocompletion, no refactoring options, other developers will need more time to read and navigate in the code and in the end it will cost you much more time than you can save with it.

3. Break the spell on Magic Code …

… by explaining to everyone (Devs > IDE > Tools) what it does.

Example 1:

We use a simple Active Record Pattern, but we put all SQL stuff into the Factory classes, so that the Active Record class can be simple. (Example) But because of missing support for Generics we had no autocompletion without adding many dummy methods into the classes. So one of my first steps was to introduce a “PhpCsFixer” that automatically adds the missing methods of the parent class with the correct types via “@method”-comments into these classes. 

Example 2:

Sometimes you can use more modern PHPDocs to explain the function. Take a look at the “array_first” function in the linked Post.

Example 3:

/**
* Return an array which has the Property-Values of the given Objects as Values.
*
* @param object[] $ObjArray
* @param string $PropertyName
* @param null|string $KeyPropertyName if given uses this Property as key for the returned Array otherwise the keys from the
* given array are used
*
* @throws Exception if no property with the given name was found
*
* @return array
*/
function propertyArray($ObjArray, $PropertyName, $KeyPropertyName = null): array {
// init
$PropertyArray = [];

foreach ($ObjArray as $key => $Obj) {
if (!\property_exists($Obj, $PropertyName)) {
throw new Exception('No Property with Name ' . $PropertyName . ' in Object Found - Value');
}

$usedKey = $key;
if ($KeyPropertyName) {
if (!\property_exists($Obj, $KeyPropertyName)) {
throw new Exception('No Property with Name ' . $PropertyName . ' in Object Found - Key');
}
$usedKey = $Obj->{$KeyPropertyName};
}

$PropertyArray[$usedKey] = $Obj->{$PropertyName};
}

return $PropertyArray;
}

Sometimes it’s hard to describe the specific output types, so here you need to extend the  functions of your Static Code Analyze Tool, so that it knows what you are doing. ⇾ for example here you can find a solution for PHPStan ⇽  but there is still no support for the IDE and so maybe it’s not the best idea to use magic like that at all. And I am sure it’s more simple to use specific and simple methods instead:  e.g. BillCollection->getBillDates()

4. Try to not use strings for the code.

Strings are simple and flexible, but they are also bad for the long run. Mostly strings are used because it looks like a simple solution, but often they are redundant, you will have typos everywhere and the IDE and/or Static Analysis can’t analyze them because it’s just text.

Example:

bad: 

AjaxEditDate::generator($bill->bill_id, $bill->date, 'Bill', 'date');
  • “Bill” ⇾ is not needed here, we can call e.g. get_class($bill) in the “generator” method
  • “date” ⇾ is not needed here, we can fetch the property name from the class
  • “$bill->bill_id” ⇾ is not needed here, we can get the primary id value from the class

good:

AjaxEditDate::generator($bill, $bill->m()->date);

5. Automate stuff via git hook and check it via CI server.

Fixing bad code is only done if you disallow the same bad code for the future. With a pre-commit hook for git it’s simple to run these checks, but you need to check it again in the CI server because the developers can simply skip these checks.

Example:

I introduced a check for disallowing global variables (global $foo && $GLOBALS[‘foo’]) via “PHP_CodeSniffer”. 

Links:

6. Use array shapes (or collections) if you need to return an array, please.

Array shapes are like arrays but with fixed keys, so that you can define the types of each key in the PHPDocs.

You will have autocompletion for the IDE + all other devs can see what the method will return + you will notice if you’re better retuning an object because it will be very hard to describe the output for complex data structures and Static Analysis can use and check the types. 

Example:

/**
* @return array{
* missing: array<ShoppingCartLib::TAB_*,string>,
* disabled: array<ShoppingCartLib::TAB_*,string>
* }
*/
private static function getShoppingCartTabStatus(): array {
...
}

Generics in PHP via PHPDocs

If you did not know that you can use Generics in PHP or you do not exactly know how to use it or why you should use it, then the next examples are for you.

Type variables via @template

The @template tag allows classes and functions to declare a generic type parameter. The next examples starts with simple functions, so that we understand how it works, and then we will see the power of this in classes.


A dummy function that will return the input.

https://phpstan.org/r/1922279b-9786-4523-939d-dddcfd4ebb86

    <?php    

    /**
     * @param \Exception $param
     * @return \Exception
     *
     * @template T of \Exception
     * @psalm-param T $param
     * @psalm-return T
     */
    function foo($param) { ... }

    foo(new \InvalidArgumentException()); // The static-analysis-tool knows that 
                                          // the type is still "\InvalidArgumentException" 
                                          // because of the type variable.

@template T of \Exception // here we create a new type variable, and we force that it must be an instance of \Exception

@phpstan-param T $param // here we say that the static-analysis-tool need to remember the type that this variable had before (you can use @psalm-* or @phpstan-* both works with both tools)

@phpstan-return T // and that the return type is the same as the input type 


A simple function that gets the first element of an array or a fallback. 

In the @param PHPDocs we write “mixed” because this function can handle different types. But this information is not very helpful if you want to understand programmatically what the code does, so we need to give the static-analysis-tools some more information. 

https://phpstan.org/r/1900a2af-f5c1-4942-939c-409928a5ac4a

    <?php
     
    /**
     * @param array<mixed> $array
     * @param mixed        $fallback <p>This fallback will be used, if the array is empty.</p>
     *
     * @return mixed|null
     *
     * @template TFirst
     * @template TFirstFallback
     * @psalm-param TFirst[] $array
     * @psalm-param TFirstFallback $fallback
     * @psalm-return TFirst|TFirstFallback
     */
    function array_first(array $array, $fallback)
    {
        $key_first = array_key_first($array);
        if ($key_first === null) {
            return $fallback;
        }

        return $array[$key_first];
    }

    array_first([1, 2, 3], null); 

    if ($a === 'foo') { // The static-analysis-tool knows that 
                        // === between int|null and 'foo' will always evaluate to false.
	    // ...
    }

@template TFirst // we again define your typed variables

@template TFirstFallback // and one more because we have two inputs where we want to keep track of the types

@psalm-param TFirst[] $array // here we define that $array is an array of TFirst types

@psalm-param TFirstFallback $fallback // and that $fallback is some other type that comes into this function

@psalm-return TFirst|TFirstFallback // now we define the return type as an element of  the $array or the $fallback type 


 Very basic Active Record + Generics

The IDE support for generics is currently not there, :-/ so that we still need some hacks (see @method) for e.g. PhpStorm to have autocompletion.

https://phpstan.org/r/f88f5cd4-1bb9-4a09-baae-069fddb10b12

https://github.com/voku/phpstorm_issue_53352/tree/master/src/Framework/ActiveRecord

<?php

class ActiveRow
{
    /**
     * @var ManagedFactory<static>
     */
    public $factory;

    /**
     * @param Factory<ActiveRow>|ManagedFactory<static> $factory
     * @param null|array                                $row
     */
    public function __construct(Factory $factory, array $row = null) {
        $this->factory = &$factory;
    }
}

/**
 * @template T
 */
abstract class Factory
{
    /**
     * @var string
     *
     * @internal
     *
     * @psalm-var class-string<T>
     */
    protected $classname;

    /**
     * @return static
     */
    public static function create() {
        return new static();
    }
}

/**
 * @template  T
 * @extends   Factory<T>
 */
class ManagedFactory extends Factory
{
    /**
     * @param string $classname
     *
     * @return void
     *
     * @psalm-param class-string<T> $classname
     */
    protected function setClass(string $classname): void
    {
        if (\class_exists($classname) === false) {
            /** @noinspection ThrowRawExceptionInspection */
            throw new Exception('TODO');
        }

        if (\is_subclass_of($classname, ActiveRow::class) === false) {
            /** @noinspection ThrowRawExceptionInspection */
            throw new Exception('TODO');
        }

        $this->classname = $classname;
    }

    // ...
}

final class Foo extends ActiveRow {

    public int $foo_id;

    public int $user_id;

    // --------------------------------------
    // add more logic here ...
    // --------------------------------------
}

/**
 * @method Foo[] fetchAll(...)
 *
 * @see Foo
 *
 * // warning -> do not edit this comment by hand, it's auto-generated and the @method phpdocs are for IDE support       
 * //         -> https://gist.github.com/voku/3aba12eb898dfa209a787c398a331f9c
 *
 * @extends ManagedFactory<Foo>
 */
final class FooFactory extends ManagedFactory
{
    // -----------------------------------------------
    // add sql stuff here ...
    // -----------------------------------------------
}

A more complex collection example.

In the end we can extend the “AbstractCollection” and the static-analysis-tools knows the types of all the methods. 

https://github.com/voku/Arrayy/tree/master/src/Type

/**
 * @template TKey of array-key
 * @template T
 * @template-extends \ArrayObject<TKey,T>
 * @template-implements \IteratorAggregate<TKey,T>
 * @template-implements \ArrayAccess<TKey|null,T>
 */
class Arrayy extends \ArrayObject implements \IteratorAggregate, \ArrayAccess, \Serializable, \JsonSerializable, \Countable
{ ... }

/**
 * @template TKey of array-key
 * @template T
 * @template-extends \IteratorAggregate<TKey,T>
 * @template-extends \ArrayAccess<TKey|null,T>
 */
interface CollectionInterface extends \IteratorAggregate, \ArrayAccess, \Serializable, \JsonSerializable, \Countable
{ ... }

/**
 * @template   TKey of array-key
 * @template   T
 * @extends    Arrayy<TKey,T>
 * @implements CollectionInterface<TKey,T>
 */
abstract class AbstractCollection extends Arrayy implements CollectionInterface
{ ... }

/**
 * @template TKey of array-key
 * @template T
 * @extends  AbstractCollection<TKey,T>
 */
class Collection extends AbstractCollection
{ ... }

Links:

https://phpstan.org/blog/generics-in-php-using-phpdocs

https://psalm.dev/docs/annotating_code/templated_annotations/

https://stitcher.io/blog/php-generics-and-why-we-need-them

❤️ Simple PHP Code Parser

It based on code from “JetBrains/phpstorm-stubs” but instead of Php-Reflection we now use nikic/PHP-Parser, BetterReflection, phpDocumentor and PHPStan/phpdoc-parser internally. So, you can get even more information about the code. For example, psalm- / phpstan-phpdoc annotations or inheritdoc from methods.

Install:

composer require voku/simple-php-code-parser

Link:

voku/Simple-PHP-Code-Parser

More:


Example: get value from define in “\foo\bar” namespace

$code = '
  <?php
  namespace foo\bar;
  define("FOO_BAR", "Lall");
';

$phpCode = PhpCodeParser::getFromString($code);
$phpConstants = $phpCode->getConstants();

$phpConstants['\foo\bar\FOO_BAR']->value; // 'Lall'

Example: get information about @property phpdoc from a class

$code = '
  <?php
  /** 
   * @property int[] $foo 
   */
  abstract class Foo { }
';

$phpCode = PhpCodeParser::getFromString($code);
$phpClass = $phpCode->getClass('Foo');

$phpClass->properties['foo']->typeFromPhpDoc); // int

Example: get classes from a string (or from a class-name or from a file or from a directory)

$code = '
<?php
namespace voku\tests;
class SimpleClass {}
$obja = new class() {};
$objb = new class {};
class AnotherClass {}
';

$phpCode = \voku\SimplePhpParser\Parsers\PhpCodeParser::getFromString($code);
$phpClasses = $phpCode->getClasses();

var_dump($phpClasses['voku\tests\SimpleClass']); // "PHPClass"-object

Arrayy: A Quick Overview of map(), filter(), and reduce()

Image
Arrayy: A PHP array manipulation library. Compatible with PHP 7+

The next examples are using the php array manipulation library “Arrayy” which is using generators internally for many operations.

https://github.com/voku/Arrayy

StringCollection::create(['Array', 'Array'])->unique()->append('y')->implode(); // Arrayy


map: transform all values in the collection

StringCollection::create(['foo', 'Foo'])->map('mb_strtoupper'); 

// StringCollection['FOO', 'FOO']

filter: pass all values to the truth test

$closure = function ($value) {
    return $value % 2 !== 0;
}
IntCollection::create([1, 2, 3, 4])->filter($closure); 

// IntCollection[0 => 1, 2 => 3]

reduce: transform all values into a new result

IntCollection::create([1, 2, 3, 4])->reduce(
    function ($carry, $item) {
        return $carry * $item;
    },
    1
); 

// IntCollection[24]

Blackfire PHP-Profiler: Fire up the Performance

Am Wochenende habe ich mal wieder ein neues Tool ausprobiert: “Blackfire” ist ebenso wie “Insight” von SensioLabs entwickelt und kann die Performance einer PHP-Anwendung “on the fly” analysieren und visualisieren.

Die Installation besteht aus folgenden Komponenten:

  • Probe: dies ist die PHP-Extension welche wir auf dem Server installieren
  • Agent: Dies ist ein Programm, welches ebenfalls auf dem Server installiert wird und die Performance-Daten zu blackfire.io weiterleitet, wo diese dann visualisiert werden
  • Client (optional): ein CLI-Tool um PHP-Profiling auf der Kommandozeile auszuführen
  • Companion: dies ist eine Chrome-Extension, welche auf Knopfdruck das PHP-Profiling ausführt
  • Website: die blackfire.io Webseite zur Darstellung der Ergebnisse

Die Installationsanleitung ist innerhalb von zirka 5 Minuten abgearbeitet und schon kann man schnell und bequem die Leistung / Performance von Änderungen testen und die entsprechenden Ergebnisse miteinander vergleichen.

blackfire.io
blackfire.io: Beispiel-Profile von “http://moelleken.org

 

PS: wer noch mehr Information benötigt sollte sich Xdebug lokal installieren bzw. aktivieren und die Profile-Daten anschließend via WinCacheGrind (Windows) oder direkt via KCachegrind (Linux) auswerten und visualisieren.

KCachegrind
KCachegrind

 

Web Development mit Linux (Video)

Im folgenden Video zeige ich kurz (~ 5 min.) wie meine Toolchain im Live-Betrieb aussieht, dabei nutze ich unter anderem Sublime Text, PHP, Twig, Grunt, Sass, Compass, LiveReload und ein paar Funktionen aus den “dofiles” (z.B. phpserver).

 

Wer den Beispiel-Code aus dem Video selber ausprobieren möchte, hier die Zusammenfassung:

sudo apt-get install git

cd ~
git clone https://github.com/voku/dotfiles/
cd dotfiles/
./firstInstall.sh
./bootstrap.sh
cd ~
vim ~/.extra #1

cd ~
mkdir Projects/
git clone https://github.com/voku/twig-wrapper-example/
cd twig-wrapper-example/
npm install
grunt watch

cd ~/Projects/twig-wrapper-example/
phpserver

#1 füge deine persönlichen Git-Einstellungen hier hinzu

 

Wer mehr Infos oder Erklärungen zu den hier verwendeten Tools haben möchte, sollte sich einmal folgende Videos anschauen: suckup.de/howto/html/moderner-build-development-workflow-via-bower-grunt-yeoman/

 

Twig – PHP

Als erstes möchte ich hier klären was “Twig” ist und wie man diese PHP-Bibliothek verwendet.

Was ist Twig?

Twig ist eine Open Source Template-Engine für PHP und wird z.B. von Symfony verwendet. Damit ist es möglich die Programmierung vom Layout zu trennen und Projekte somit übersichtlicher und sicherer zu erstellen. Zudem kann man Twig relativ einfach erweitern und neue Funktionen oder Filter hinzufügen. Außerdem ist die Dokumentation wirklich gut und die Einarbeitungszeit ist sehr kurz.

 

Schnelle Installation

Damit man einen schnellen Einstig in Twig bekommt, habe ich eine kleine Wrapper-Klasse geschrieben und in einem zweiten Projekt “Twitter Bootstrap” als Twig-Beispiel -Template integriert.

git clone https://github.com/voku/twig-wrapper-example/

Das Projekte ist mit Gunt + SASS + Bower + Composer etc. aufgesetzt, jedoch habe ich alle Vendor-Dateien mit in dem Repository eingecheckt, so dass man keine komplette Toolchain benötigt um die folgenden Beispiele auszuprobieren.

PS: um das Projekt zu updaten bzw. weiterzuentwickeln kann man folgende Befehle ausführen:

cd twig-wrapper-example/
rm -rf vendor/
rm composer.lock
npm install
bower install
composer install
grunt build
grunt watch

 

Syntax

Die Allgemeine-Syntax lässt sich in “output”, “function” und “comment” unterteilen.

output” ->  {{ … }}  -> um Variablen in das Template einzufügen

function” -> {% … %} -> um Funktionen auszuführen

comment” -> {# … #} -> um einen Kommentar einzufügen, welche nur im Twig sichbar ist (nicht im späterem “html”-output)

Variablen

Man kann sowohl Arrays, einfache Variablen oder ganze Objekte an Twig übergeben.

z.B.:

$object = new Object();
$twig->assign('object', $object);
$array = array(1,2,3,4,5);
$twig->assign('array', $array);
$string = 'test';
$twig->assign('string', $string);

Zudem kann man auch Variablen in einem Twig-Template definieren:

{% set totalimages = 4 %}

Filter

Auf der Kommandozeile ist man bereits an Pipes ( “|” ) gewöhnt und kann dies nun auch bei der Entwicklung nutzen. :)

{% set totalimages = object.images | length %}
{{ array[1] | striptags | raw }}

Kontrollstrukturen

“if”-Bedingungen werden wie folgt geschrieben:

{% if (name == "foo") or (name == "bar") %}
    {{ name }}!!!
{% elseif name == "test" %}
    {{ name }} -> was a test
{% else %}
    {{ name }}
{% endif %}

“loops” in Twig: for

{% for asset in headerObject %}
  <img src="{{ asset.url }}">
  <p>{{ asset.caption }}</p>
  {% if loop.index0 is divisibleby(2) %}
    <hr />
  {% endif %}
{% endfor %}

PS: außerdem kann man wie im vorherigen Beispiel – “loop” nutzen, um den Anzahl von aktuellen Durchläufe zu analysieren

 

extends

Eine Hauptfunktionalität ist die Twig-Funktion “extends“. Mit dieser Funktion kann man sein Template erweitern und zuvor definierte Bereiche (block – endblock) wiederum ersetzten.

{%extends “base.twig” %}

base.twig -> index.twig

Der vorherige Befehl bewirkt in der “index.twig” Datei, dass das angegebene Template erweitert wird. Dabei ist es sehr hilfreich möglichst viel HTML-Code in einem Basis-Twig unterzubringen. Anders wie bei vielen anderen PHP-Projekten sollte man nicht so oft via “include” inkludieren, sondern bestimmte Bereiche (blocks) im “base.twig” festlegen und diese mit Inhalten befüllen, erweitern oder leeren.

 

include

Die Funktionalität “include” lässt sich einsetzten um Template-Komponenten (z.B.: einen Slider) an mehreren Stellen einzufügen. Man hat die Möglichkeit beim “include”-Aufruf Variablen zu übergeben, jedoch kann man keine “blocks” von bereits inkludierten Templates mehr ändern.

{% include “inc_carousel.twig” %}

base.twig -> inc_carousel.twig

 

embed

Beim “embed” handelt es sich um eine Mischung aus extending & including, so kann man innerhalb des embed-Aufrufes darin befindliche “blocks” ersetzten.

{% embed “content_sidebar.twig” %}

index.twig -> content_sidebar.twig

 

Twig_Extension

Wem ein bestimmter Filter oder Funktion fehlt, kann dies sehr einfach in Twig  integrieren z.B. -> PluginHtml.php

$twig->addExtension(new PluginHtml());

 

Links

http://knpuniversity.com/screencast/twig/basics | Basics – Video
http://twig.sensiolabs.org/ | Dokumentation

Einführung: PHP-Variablen und der Garbage Collector

In diesem Blog-Post möchte ich erklären, was technisch hinter einer Variable in PHP steckt und wann PHP den Speicher dieser Variablen wieder freigibt.

Der PHP Quellcode?

Im Alltag schaut man doch ehr selten (nie) in den eigentlichen Quellcode von PHP. Dies sollten wir an dieser Stelle nachholen. -> https://github.com/php/php-src/tree/PHP-5.5

Info: Auf der folgenden Seite kann man den PHP Quelltext einfach nach bestimmten z.B.: Funktionen durchsuchen: http://lxr.php.net

Ich habe mich hier für den Quelltext von PHP 5.5 entschieden, da diese Version auf meinen lokalen PC läuft und ich die folgenden Tests mit dieser Version durchführen werde.

PHP Quellcode – Struktur: Hier gibt es zwei Hautbestandteile von PHP, mit welchen wir uns weiter beschädigen werden. Aufgeteilt sind diese in den folgende zwei Verzeichnissen.

– Zend: (www.php.net/manual/en/langref.php) Die Zend Engine, stellt die Laufzeitumgebung für PHP zur Verfügung. In diesem Verzeichnis werden somit alle “language level”-Funktionen von PHP bereitgestellt wie z.B. Variablen, Syntax-Analyse, Code-Ausführung und Fehlerbehandlung.

– ext: (www.php.net/manual/en/funcref.php) Extensions, stellt alle einzel Funktionen von PHP bereit. (z.B. strpos, substr, PDO, SplFixedArray etc.)

Intern stellt PHP jeder Variable als sogenannte “ZVALs” da. Dies ist unter anderen nötig, da der PHP-Kern in C geschrieben ist, C jedoch keine dynamische Typisierung wie in PHP zulässt. Somit bedient man sich einer Art Wrapper, welcher verschiedene Werte beinhalten kann.

struct _zval_struct {
  /* Variable information */
  zvalue_value value;             /* value */
  zend_uint refcount__gc;
  zend_uchar type;                /* active type */
  zend_uchar is_ref__gc;
};

Was steck in einer zval-Struktur?

value: http://lxr.php.net/xref/PHP_5_5/Zend/zend.h#321

typedef union _zvalue_value {
    long lval;                   /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

Wir sehen, dass der “value” in einer PHP-Variable (zval) mehrere Variablentpyen (Zustände) beinhaltet. Daher kann eine PHP-Variable sowohl einen int-Wert, als auch ein Array beinhalten.

z.B.:
$foo = (int) 3;
$foo = array(3);

C

PHP

long

int, boolean, resource

double

float

str

string

hashtable

array

zend_object_value

object

type: http://lxr.php.net/xref/PHP_5_5/Zend/zend.h#578

/* data types */
/* All data types <= IS_BOOL have their constructor/destructors skipped */
#define IS_NULL     0
#define IS_LONG     1
#define IS_DOUBLE   2
#define IS_BOOL     3
#define IS_ARRAY    4
#define IS_OBJECT   5
#define IS_STRING   6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY   9
#define IS_CALLABLE 10

/* Ugly hack to support constants as static array indices */
#define IS_CONSTANT_TYPE_MASK       0x00f
#define IS_CONSTANT_UNQUALIFIED     0x010
#define IS_CONSTANT_INDEX           0x080
#define IS_LEXICAL_VAR              0x020
#define IS_LEXICAL_REF              0x040
#define IS_CONSTANT_IN_NAMESPACE    0x100

Da wir sovor gesehen haben, dass das “value”-Feld verschiedene Typen von Variablen beinhalten kann, wird hier festgelegt um welchen Type es sich hier handelt. z.B.: zval.type = IS_LONG

is_ref: 0 || 1 http://www.phpinsider.com/download/PHP5RefsExplained.pdf
Dieses Feld wird auf “1” gesetzt falls es sich bei der Variable um eine Referenz (z.B.: &$foo) handelt.

refcount: http://www.php.net/manual/en/features.gc.refcounting-basics.php
Dieses Feld ist ein Zähler für die Anzahl von PHP-Variablen, welche zu dem internen zval referenzieren, so kann der PHP Garbage Collector entscheiden, wann die entsprechende Variable nicht mehr benötigt wird.

z.B.:
$a= new A;    // 1 Referenz
$b = $a;         // 2 Referenzen
unset( $a );   // 1 Referenz
unset( $b);    // 0 Referenzen

Größe von “zval”?

Auf folgender Webseite wurden bereits mit PHP 5.3 getestet, wie viel Speicher für 100000 Integer-Werten in einem Array benötigt werden. -> http://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html

                             |  64 bit   | 32 bit

zval                         |  24 bytes | 16 bytes
+ cyclic GC info             |   8 bytes |  4 bytes
+ allocation header          |  16 bytes |  8 bytes
===================================================
zval (value) total           |  48 bytes | 28 bytes
===================================================
bucket                       |  72 bytes | 36 bytes
+ allocation header          |  16 bytes |  8 bytes
+ pointer                    |   8 bytes |  4 bytes
===================================================
bucket (array element) total |  96 bytes | 48 bytes
===================================================
total total                  | 144 bytes | 76 bytes

144 bytes * 100000 = 13.73 MB

Test mit PHP 5.5 (32-Bit)

$int = 0;
Speichergröße: 176 bytes

(refcount=1, is_ref=0),int 0

$null = null;
Speichergröße: 184 bytes

(refcount=1, is_ref=0),null

Da es für “null” keinen Eintrag im zvalue_value gibt, wird dieser Wert seperat gespeichert und benötigt somit inital mehr Speicher als 0.

$string = ”;
Speichergröße: 200 bytes

(refcount=1, is_ref=0),string '' (length=0)

$string = ‘1234567’;
Speichergröße: 200 bytes

(refcount=1, is_ref=0),string '1234567' (length=7)

$string = ‘12345678’;
Speichergröße: 208 bytes

(refcount=1, is_ref=0),string '1234567' (length=8)

$string = ‘0123456789’;
Speichergröße: 208 bytes

(refcount=1, is_ref=0),string '1234567' (length=10)

Wir sehen, dass PHP jeweills 1 byte (8 bit) benötigt, um ein Zeichen für einen String zu Speichern und diese zu jeweills 8 bytes zusammenfasst. So bleiden im letzten Beispiel 192 (208 – 16) bytes übrig, welche PHP benötigt um die entsprechende Variable anzulegen.

$array = range(1, 100000);
Speichergröße: 8524576 bytes => 8.12966919 megabytes

(refcount=1, is_ref=0), array (size=100000)
  0 => (refcount=1, is_ref=0),int 1
  1 => (refcount=1, is_ref=0),int 2
  2 => (refcount=1, is_ref=0),int 3
  3 => (refcount=1, is_ref=0),int 4
  [...]

 

PHPs Garbage Collector

Quellcode für den Test: test4.php

start:                                88 bytes 
array:                     8524592 bytes 
function1-begin:     8524608 bytes 
function1-end:      17049016 bytes 
array-test4:             8524536 bytes 
function2-begin:      8524552 bytes 
function2-end:         8524616 bytes 
array-test4_ref:       8524552 bytes 
end:                         8524552 bytes 

Wir sehen, dass PHP bei der ersten Funktion mit Parameterübergabe (ohne Referenz) das entsprechende Array kopiert, da es sich hier um eine neue Variable handelt, welche in der Funktion verarbeite wird und zurückgegeben wird. Zusätzlich können wir hier beobachten wann PHP die Variable kopiert und zwar erst wenn diese Verändert wird (copy-on-write).

 

Zusammenfassung:

Ich glaube es ist Zeit für eine erste Zusammenfassung von dem was wir bisher aus den PHP Quelltext und den ersten kleinen Tests sehen konnten.

1.) PHP behandelt Variablen intern als Struktur von Variablen (zval_struct -> zval).

2.) Diese Struktur beinhaltet nicht nur den aktuellem Wert und den Type, sondern auch die Anzahl wie oft diese Variable verwendet wird und ob es sich dabei um eine Referenz (&) handelt. 

3.) PHPs Garbage Collector kann somit nicht mehr benötigte Variablen selbständig aus dem Speicher löschen.

 

Quellen:
http://www.php.net/manual/en/internals2.variables.intro.php
http://www.php.net/manual/en/features.gc.refcounting-basics.php
http://www.php.net/manual/en/function.debug-zval-dump.php

http://code.stephenmorley.org/php/references-tutorial/
http://www.developerknowhow.com/inside-the-php-engine-integers-and-their-zval/
http://webandphp.com/how-php-manages-variables
http://wiki.selfhtml.org/wiki/Artikel:PHP/
http://www.sitepoint.com/better-understanding-phps-garbage-collection/

Weniger schlecht PHP programmieren

Wir können die Aspekte von guten Code mit folgenden drei Leitlinien zusammenfassen: Lesbarkeit, Erweiterbarkeit, Effizienz. Dabei hängt es ganz von dem Projekt / Projektphase ab, in welcher Reihenfolge diese Leitlinien stehen. In diesem Artikel werde ich mich mit der „Lesbarkeit“ (Syntax, Stil, Standards) von PHP-Code, Kommentaren und git-commits beschäftigen.

Übersicht:
1. Namensgebung
2. Kommentare
3. Code-Standards
4. Refactoring
5. Debug
6. Versionskontrollsystem (git)
7. CI – Server
8. Empfehlung

1.) Namensgebung

Zur Lesbarkeit von Quellcode gehört insbesondere die Benennung von Variablen, Funktionen, Methoden, Klassen, Konstanten usw. Ich versuche dies an ein paar simplen Beispielen zu erklären:

FALSCH RICHTIG Erklärung
str2clean string die lokalen Variablen wurde vereinfacht, so dass man den Quelltext besser lesen kann
margin2Set margin
naviPageEdit_innavi[$value] naviPageEdit[‘inNavi’][$pageID] ‘value’ wurde durch ‘pageID’ ersetzt
naviPageEdit_offline[$value] naviPageEdit[‘offline’][$pageID] ‘_offine’ wurde durch eine weitere Array-Stufe ersetzt
startTimerGlobal GLOBALS[‘startTimerGlobal’] die globale Variable ist nun auch als solche erkennbar
dontRewriteFileName = false rewriteFileName = true keine Verneinung in Variablennamen verwenden!!!
pageContentPriorities[$i],
pageContentPriorityArray[$i],
page->contentPriorities[$i]
page->contentPriority[$i] wenn möglich kein Plural für Variablen verwenden ; keine Angabe vom Variablentype im Variablennamen
length,delay length_in_mm,delay_seconds wenn eine bestimmte Information sehr Wichtig für eine Variable ist, dann sollte man diese Information auch mit im Namen aufnehmen

 

Gute Namen beschreiben was der Sinn dieser Variable ist und Funktionen sollten beschreiben was diese tun und nicht wie dies programmiert sind.

FALSCH RICHTIG Erklärung
left_column, right_column navi_column, content_column Der Sinn der Variable ist es, die Navigation oder den Inhalt zu beinhalten.
setHeadlineFontColorToRed() setHeadlineFontColor(‘red’) Wir beschreiben im Funktionsnamen nicht wie wir die Überschrift hervorheben, sondern dass wir dies überhaupt machen.
renderBackgroundBlack() renderBackgroundColor(‘black’)

Es folgenden einfache, aussagekräftige Beispiele für Variablennamen: carColor, imageWidth, pageTemplate, daysDateRange, errorCounter, startDateTime, …

 

1.1) Allgemein

Allgemein sollten / dürfen Variablen keine Sonderzeichen (außer dem Unterstrich) enthalten und dürfen in den meisten Programmiersprachen nicht mit Zahlen oder Unterstrich anfangen. Zudem sollte diese in englisch ausgeschrieben werden. Einige Namen sollte man einfach aus seinem Wortschatz streichen: value, key, equals, data, lall, foo, bar, temp, tmp, x, xx, xxx, variable, var, arr, thing, stuff, bla, this, that, something, whatever, dummy, one, two, tree … Wenn man länger über den Namen für z.B. eine Methode nachdenken muss, ist dies ein Indiz dafür, dass man den Zweck dieser Methode noch nicht korrekt durchdacht hat und dass man diese Methode ggf. noch weiter aufteilen kann / sollte.

Beispiel für Wörter, welch man nicht verwenden sollte:

FALSCH RICHTIG Erklärung
foreach ($pages as $key => $value) foreach ($pages as $pageID => $page) Variablen müssen immer Aussagekräftig sein!

Bei Programmen mit Datenspeicherung sieht man im Quelltext häufig folgende Namensgebung: “fetch”, “get”, “retrieve”, “search”, “pull”, “pop”, “read”, usw. um Daten zu erhalten und so etwas wie “put”, “persist”, “set”, “push”, “add”, “insert”, “update”, “write”, usw. um Daten abzuspeichern. Einige Projekte halten sich an der Namensgebung „CRUD“ (Create, Read, Update, Delete) dabei ergibt sich aus dem Zusammenhang, was abgespeichert oder geändert wird, so dass man den entsprechenden Methodennamen auf das wesentliche beschränken und entsprechende Interface definieren kann: z.B.: $user->create(), $car->delete(), $page->update()

Ausnahmen bei gängigen Abkürzungen:

Abkürzung Ausgeschrieben
num numberOf
pos position
len length
max maximum
min minimum
temp || tmp temporary
val value
ret return
fp filePointer

 

1.2) Länge

Man sollte lieber dreimal über einen Namen nachdenken als einmal zu wenig. Und falls einem gerade kein passender Name einfällt, dann sollte man besser einen Aussagekräftigen, wenn auch längeren Namen verwenden. z.B.: cleanFilenameAfterFileupload()

1 Zeichen für Schleifenzähler ist gebräuchlich (z.B.: $i, $x, $y)

1 Wort für Zustands- / Schleifenvariablen (z.B.: $active, $hidden, $flag)

1-3 Wörter für lokale Variablen in Methoden / Funktionen (z.B.: $string, $linkText, $teaserTextType)

1-2 Wörter für Methoden (z.B.: isActive(), isHidden(), isTrue(), send())

1-2 Wörter für Klassen [wenn Namespaces verwendet werden] (Cache, CacheChain, AdaperApc)

1-2 Wörter für Interface [wenn Namespaces verwendet werden] (iCache, iAdapter)

 2-4 Wörter für Globales [sollte man am besten gar nicht verwenden, dazu gleich mehr]

 

1.3) Style

Halte dich an den Code-Style in einem Projekt, auch wenn dieser dir nicht zusagt. Wenn zum Beispiel einmal festgelegt wurde, dass Datenfelder aus der Datenbank in einer Klasse genauso heißen wie das entsprechende Datenbankfeld, dann muss man dies in dem Projekt auch konsequent durchziehen, ansonsten ist das Refactoring wirklich anstrengend. Man sollte sich für alle Projekte in einem Team / Firma auf einen Code-Style festlegen und diesen auch irgendwo niederschreiben (z.B. in einem Wiki), so dass neue Teammitglieder / Mitarbeiter wissen nach welchem Schema man den Quellcode lesen & schreiben sollte.

Es folgt eine kleine Auflistung von Variablen um zu verdeutlichen, welche Möglichkeiten man hat und dass es im Verlauf eines Projektes nicht mehr ganz so einfach ist diese mithilfe von Suchen- / Ersetzen-Tools in Einklang zu bringen:

userName, UserName, USER_NAME, s_userName, user_name, username, name, user->name

Allgemeine-Regel:

Camel Case (Upper Camel Case) für Klassen → z.B.: AdaperApc

Lower Case für namespaces / packages → z.B.: voku\cms\cache

Mixed Case (Lower Camel Case) für Variablen → z.B.: cacheIsReady

Upper Case für Konstanten → z.B.: SECONDS_IN_A_HOUR

– Unterstriche sollten nur für Konstanten verwendet werden

– SQL-Befehle sollten große geschrieben werden → SELECT id, username FROM user

Konsequente Temporäre Variablennamen:

Variable Kontext
$i Schleifenzähler
$j verschachtelte Schleifenzähler
$k weiter verschachtelte Schleifenzähler
$return Rückgabewert
$query SQL-Query String
$result SQL-Query Ergebnis
$fp File-Pointer

 

1.4) Reddick-Namenskonvention

Auch wenn ich dies irgendwann mal in der Schule gelernt habe, solle man Namen nicht mit Tags / Suffixes versehen.

FALSCH RICHTIG Erklärung
gintKundenID $customer->getID() global Integer “KundenID”
$this->mintBenutzerName $this->getName() privat (modular) Integer “BenutzerName”

Zum einen unterstützen uns heute moderne IDEs (z.B.: PhpStorm, Netbeans, Eclipse), so dass man selbst bei schwach typisierten Programmiersprachen wie PHP viele Informationen zu seinen Variablen / Funktionen / Klassen erhält, zum anderen sollte man anstatt eine globale ID für einen Kunden besser eine „Customer“-Klasse anlegen, welche eine Methode mit dem Namen „getID()“ beinhalten könnte.

 

1.5) doppelte Namensvergabe

Klassen: Man sollte vermeiden denselben Namen innerhalb von einer Klasse in unterschiedlichen Zusammenhängen (z.B.: in einer Methode, Konstruktor oder Attribut) zu verwenden, um die Verständlichkeit und Wartbarkeit zu erhöhen.

Methoden / Funktionen: Zudem sollte man nicht dieselbe Variable für verschiedene Zwecke innerhalb einer Funktion verwenden. Man sollte besser eine neue Variable anlegen anstatt eine alte Variabel zu „recyclen“. Ansonsten erschwert dies ebenfalls die Wartbarkeit und Lesbarkeit des Quellcodes.

 

1.6) globale Variablen

Zuerst sollte geklärt werden, was Global heißt. Innerhalb von PHP kann jede Variable welche nicht innerhalb einer Funktion oder Klasse ausgelagert ist „global“ gesetzt / genutzt werden. Benötigt man diese Variablen in einem anderen Kontext (z.B. innerhalb einer Methode) kann man (sollte man jedoch nicht machen) dies mit dem Befehl „global $lall“ zugänglich machen. In den meisten Fällen tut man sich damit jedoch keinen Gefallen, da jemand anderes (oder man selbst) ggf. nicht weiß dass die Variable „$lall“ nun nicht mehr verwendet werden darf, da man diese nun in irgendeiner Methode global setzt hat. Zudem sieht man im Quellcode einer Variable nicht an, dass diese global ist und dass man diese nicht einfach verwenden / verändern darf.

Superglobale Variablen in PHP:

$GLOBALS alle Variablen im globalen Gültigkeitsbereich
$_POST alle Variablen welche via HTTP-POST empfangen wurden
$_GET alle Variabel welche via HTTP-GET empfangen wurden
$_REQUEST alle Variablen welche via HTTP-POST / GET oder via COOKIE empfangen wurden
$_SESSION alle Session-Variablen (wir per User auf den Server gespeichert)
$_COOKIE alle Cookie-Variablen (werden per User auf dem PC des Users gespeichert)
$_FILES alle Datei-Uploads welche via HTTP-POST empfangen wurden
$_SERVER alle Variablen vom Server / Ausführungsumgebung
$_ENV alle Umgebungsvariablen welche z.B. im Betriebssystem gesetzt wurden

 

PHP-Test mit globalen Variablen:

$thisIsGlobal= 1;
echo $GLOBALS['thisIsGlobal'];

$GLOBALS['thisIsGlobal'] = 2;
echo $GLOBALS['thisIsGlobal'];

globalTestNr1();
echo $GLOBALS['thisIsGlobal'];

globalTestNr2();
echo $GLOBALS['thisIsGlobal'];

globalTestNr3($thisIsGlobal);
echo $GLOBALS['thisIsGlobal'];

function globalTestNr1() {
  global $thisIsGlobal;

  $thisIsGlobal= 3;
}

function globalTestNr2() {
  $GLOBALS['thisIsGlobal'] = 4;
}

function globalTestNr3(&$input) {
  $input = 5;
}

// output: 12345

Man sieht bereits bei wenigen Zeilen Code, dass dies nicht zur Wartbarkeit oder Lesbarkeit beiträgt. Zudem habe ich bisher keine Anforderung gesehen, welche man nicht ohne Globale-Variablen lösen könnte: z.B. via Übergabeparameter, Vererbung von Klassen, Singleton-Klassen, ausgelagerte „Konfiguration“ in einer separaten Datei und vieles mehr.

 

2.) Kommentare

Kommentare sollten, wie die bisher erwähnten allgemeine Namensgebung in englisch geschrieben werden, da der Quelltext einerseits länger in Gebrauch sein kann als man denkt, andere (externe) Mitarbeiter daran irgendwann weiterarbeiten und ggf. auch den Weg in andere Projekte findet. Wenn man einmal den Fall hatte, dass man z.B.: eine „SHA-256“ Implementation für ältere PHP-Installationen benötigt und diese Bibliothek nur in Französisch („mais si un char necessite 16bits, on passe tout sur 16 …“ → aha?!) auf „github.com“ zu finden ist, dann weiß man warum man in der Programmierung Konsequent arbeiten sollte / muss.

 

2.1) Was sollte wo Kommentiert werden?

In PHP sollte man zu jeder Methode / Funktion / Klasse einen Kommentar schreiben. Zudem sollte man wichtigen Attributen von Klassen mit einem Kommentar versehen, dabei sollten Mehrzeilige Kommentare vor dem zu beschreibenden Codeabschnitt stehen. Inline-Kommentare sollten nicht das wiedergeben, was man im Quelltext sowieso schon lesen kann, sondern einen Mehrwert bieten. Ein zusätzliches Problem welches entsteht, wenn man in Kommentaren beschreibt, dass der Code exakt macht und nicht was es allgemein machen sollte ist, dass man den Kommentar bei jeder Anpassung des Quellcodes theoretisch auch den Kommentar anpassen müsste, was jedoch nur sehr selten geschieht und somit ist der Kommentar nun ggf. Falsch!!! Mehrzeilige Kommentare sollten einen größeren Code-Abschnitt zusammenfassend beschreiben, dies deutet jedoch wiederum an, dass dieses Code-Abschnitt ggf. auch in eine neue Methode ausgelagert werden kann.

Beispiel:

FALSCH RICHTIG Erklärung
// salutation can be Herr/Frau or 1/2 // set the salutation (ggf. kann man diesen Kommentar auch ganz entfernen)
// get country out of DB depending on PLZ/City // get the country from db Wenn man hier irgendwann auch „Monsieur“ oder „Madame“ übergeben kann, ist dieser Kommentar Fehlerhaft und kann zu echten Problemen führen.
if ($winner) { // JUST GET NEW WINNERS IF A NUMBER IS PASSED // get the new winner from db
if ($winner) {
Der Kommentar sollte immer über einem Codeabschnitt stehen und nicht beschreiben, was sowieso im Code steht.

 

2.2) TODO Kommentar

„TODO“-Kommentare sollten immer mit „TODO“ anfangen und vor dem Nächten Release beseitigt werden. Moderne IDEs zeigen diese Kommentare in einer extra Übersicht / Fenster an, so dass jeder im Projekt weiß, dass hier noch etwas zu tun ist. Es kann zudem sehr hilfreich sein, wenn man Namenskürzel zu den TODOs hinzufügt, so dass man die Zuständigkeit für den Quellcode und für die Lösung des Problems direkt festhält.

2.3) Quellcode Auskommentieren

Quellcode sollte nicht einfach nur auskommentiert werden, wenn dieser nicht mehr benötigt wird, sondern direkt gelöscht werden, da das Versionskontrollsystem sowieso jeder Version des Projektes beinhaltet und der Quellcode somit nicht verloren gehen kann.

2.4) PHPDoc

Moderne IDEs bieten die Möglichkeit Kommentare automatisch zu erstellen, indem man z.B. vor der entsprechenden Funktion „/**“ + [Enter] eingibt. PhpStorm erkennt in vielen Fällen bereits welchen Type die Übergabeparameter und der „Return“-Wert haben, sodass man diese Information nicht in Inline-Kommentaren oder gar in der Variable speichern muss. Zudem kann man z.B. via phpDocumentor eine entsprechende Code-Dokumentation aus diesen Angaben und der Struktur des Projektes erstellen lassen.

Zusätzliche zu den bereits erwähnten PHPDoc (@param, @return) gibt es noch weitere sehr hilfreiche Information welche man zumindest für Klassen / Funktionen / Methoden hinzufügen sollte: @param, @return, @since, @package

Tag

Inhalt

Beschreibung

@abstract Dokumentiert Abstrakte Klasse / Methoden
@access public, private oder protected Beschreibt die Sichtbarkeit von Variablen / Methoden
@author Autor <name@email> Dokumentiert den Autor der z.B.: Klasse
@copyright Name / Datum Dokumentiert Copyright Informationen
@deprecated version Dokumentiert seit wann z.B. eine Funktion als veraltet gilt
@deprec @deprecated
@example /path/to/example Dokumentiert den Pfad zu einem Beispiel
@exception Dokumentiert eine „Exception“ welche von einer Methode geworfen wird → @throws.
@global type $globalvarname Dokumentiert eine globale Variable
@ignore Das Element wird in der Dokumentiert ignoriert
@internal private Information für andere Entwickler
@link URL Eine URL zur eigentlichen Dokumentation
@name global variable name Alias-Name für eine Variablen z.B.: $GLOBALS[‘myvariable’] → $myvariable
@package Namespaces Dokumentiert den entsprechenden Namespace
@param type [$varname] description Dokumentiert Funktions-Parameter
@return type description Dokumentiert den Rückgabewert, wenn die Funktion einen Rückgabewert beinhaltet, falls dies nicht der Fall ist sollte man diese Angabe in PHP weglassen.
@see element Dokumentiert die Verbindung zu anderen Elementen (global variable, include, page, class, function, define, method, variable).
@since version Dokumentiert seit welcher Version der Software die entsprechende z.B.: Klasse / Methode hinzufügt wurde
@static Dokumentiert statische Klassen oder Methoden
@staticvar type Dokumentiert statische Variablen in einer Funktion oder Klasse
@subpackage Spezifiziert sub-package von Gruppen aus Klassen und Funktionen. | benötigt das Tag @package
@throws Dokumentiert Exceptions von Methoden
@todo Dokumentiert was an dieser Stelle noch gemacht werden muss
@var type Datentype für Klassenattribute
@version Dokumentiert die Version einer Klasse, Funktion

Info: Zudem kann man HTML in PHPDoc schreiben, um z.B. bestimmte <strong>Dinge</strong> hervorzuheben!

Beispiel:

cleanFileName_old
Code ohne PHPDoc

cleanFileName
Code mit PHPDoc

2.5) Zusammenfassung

Besser als jeder Kommentar bleiben jedoch eindeutige Namen und die Aufteilung des Quelltextes in kleine einzelnen Modulen (Klassen + Namespaces), sodass man sich mehr mit dem Programmieren und nicht dem Verstehen des Codes und dem lesen von Kommentaren beschäftigen muss.

Zum Schluss noch ein Link mit vielen Beispielen wie man es nicht machen sollte: ;)

http://stackoverflow.com/questions/184618/what-is-the-best-comment-in-source-code-you-have-ever-encountered

 

3.) Code-Standards

Man sollte nicht unterschätzen, wie sich die Quellcodeformatierung & Code-Standards auf die Programmierung im Team auswirkt. Einerseits hat jeder seine Vorlieben, andererseits muss man sich auf einen Standard einigen, da man ansonsten z.B. ständig Merge-Konflikte hervorruft und das Refactoring unmöglich gemacht wird. Nachdem man sich auf einen Standard geeinigt hat sollte man die Änderungen der Formatierung automatisch per IDE ausführen lassen. Zudem sollte man die automatische Codeformatierung in einem eigenen „commit“ in das Versionskontrollsystem einchecken. Ich empfehle an dieser Stelle einfach mal bereits bestehende PHP-Standards → PHP Framework Interoperability Group

Tipp: Minimale IDE übergreifende Programmierstandards kann man relativ einfach pro Projekt via „editorconfig.org“ festlegen.

 

3.1) PSR-0 – Autoloader Standard

github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md

– Ein komplett ausgeschriebener Namespace mit Klasse muss die folgende Struktur einhalten \<Anbieter Name>\(<Namespace>\)*<Name der Klasse>

– Jeder Namespace muss den übergeordneten Namespace (“Anbieter Namen”) besitzen.

– Jeder Namespace kann beliebig viele Unter-Namespaces besitzen.

– Jeder Trenner (“\”) für Namespaces wird beim Laden vom Dateisystem zu einem DIRECTORY_SEPARATOR konvertiert.

– Jedes “_” Zeichen im KLASSENNAMEN wird zu einem DIRECTORY_SEPARATOR konvertiert. Das Zeichen _ hat keine besondere Bedeutung in einem Namespace.

– Der komplette Namespace wird mit dem Namen der Klasse und dem Suffix .php kombiniert, wenn dieser vom Dateisystem geladen wird.

– Alphabetische Zeichen in Anbieternamen, Namespaces und Klassennamen können in beliebiger Kombination aus Groß- und Kleinschreibung bestehen.

Zusammenfassung:
Jede Klasse muss (seit PHP 5.3) einen eigenen Namespace (mit Angabe des Anbieters) enthalten, so dass man sich sowohl bei Klassennamen also auch bei Namspaces nicht in die Quere kommt.

Beispiele:

Namespace + Klasse Dateipfad
\Doctrine\Common\IsolatedClassLoader /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
\Symfony\Core\Request /path/to/project/lib/vendor/Symfony/Core/Request.php
\Zend\Acl /path/to/project/lib/vendor/Zend/Acl.php
Zend\Mail\Message /path/to/project/lib/vendor/Zend/Mail/Message.php

 

Beispiele: Unterstriche in Namespaces & Klassennamen

Namespace + Klasse Dateipfad
\namespace\package\Class_Name /path/to/project/lib/vendor/namespace/package/Class/Name.php
\namespace\package_name\Class_Name /path/to/project/lib/vendor/namespace/package_name/Class/Name.php

 

Der Standard, welcher hier gesetzt wird, repräsentiert die minimale Anforderung, um eine Kompatibilität hinsichtlich Autoloader zu gewährleisten. Mit der Nutzung der Beispielimplementation des SplClassLoaders [https://gist.github.com/jwage/221634] (verfügbar ab PHP 5.3) kann man somit dynamisch und ohne weiteren Aufwand z.B. externe Bibliotheken nutzen.

 

3.2) PSR-1 – Basic Coding Standard

github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md

– PHP-Dateien dürfen nur <?php und <?= Tags verwenden.

– PHP-Dateien dürfen nur UTF-8 ohne BOM verwenden.

– PHP-Dateien sollten entweder Klassen, Funktionen, Konstanten, etc. enthalten oder diese ausführen. Somit sollten die HTML-Navi z.B. in der Klasse „Navi“ entstehen jedoch nicht von dieser ausgegeben werden. Oder sollte die Klasse „Logger“ zwar die Funktionalität zum Loggen bereitstellen, diese jedoch nicht selber triggern.

– Namensräume und-Klassen müssen PSR-0 folgen.

– Klassennamen müssen in StudlyCaps deklariert werden.

– Klassen Konstanten müssen in Großbuchstaben geschrieben und ggf. mit dem Unterstrich getrennt)

– Methodennamen müssen in camelCase geschrieben werden.

 

3.3) PSR-2 – Coding Style Guide

github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md

– Code muss dem “coding style guide” PSR [PSR-1] befolgen.

– PHP-Dateien müssen als Zeilenende Unix LF [\n] haben, Windows CRLF [\r\n]

– PHP-Dateien müssen mit einer leeren Zeile enden

– PHP-Dateien haben kein ?> tag am Ende der Datei

– Code muss 4 Leerzeilen pro Einzug verwenden. Tabs werden nicht verwendet.

– Es gibt keine Längenbegrenzung pro Codezeile; jedoch sollten die 120 Zeichen nicht überschritten werden (80 Zeichen oder weniger seien noch besser)

– PHP-Keywörter (z.B.: include, require, use etc.) müssen immer klein geschrieben werden

– PHP-Konstanten (z.B.: true, false, null) müssen ebenfalls klein geschrieben werden

– Es muss eine Leerzeile nach der Deklaration des Namespaces und ebenso nach den Nutzungserklärungen geben.

– Klammern für Klassen & Methoden müssen in der nächsten Zeile, nach dem Klassennamen geöffnet und dürfen erst eine Zeile unter der geöffneten Klammer geschlossen werden.

– Die Sichtbarkeit (public, private, protected) muss zu allen Eigenschaften und Methoden deklariert werden ; „abstract“ und „final“ muss vor der endgültigen Sichtbarkeit deklariert werden ; „static“ muss nach der Sichtbarkeit deklariert werden.

– Kontrollstrukturen (if, elseif, else, switch …) müssen nachfolgend ein Leerzeichen haben (z.B.: if ($lall === true)) ; Methoden und Funktionen benötigen kein extra Leerzeichen (z.B.: echo(‘lall’);)

– Geöffnete Klammern von Kontrollstrukturen müssen auf der gleichen Linienebene geschlossen werden ; Öffnende und Schließende Klammerpaare dürfen nicht in einer Zeile stehen.

– Geöffnete Klammern von Kontrollstrukturen dürfen nachfolgend keine Leerzeichen besitzen und schließenden Klammern dürfen zuvor keine unnötigen Leerzeichen haben.

Am besten schaut man sich mal ein paar Beispiele auf der angegebenen Webseite an, da ich hier nicht alle Regeln notiert habe. Dies sollte nur eine kleine Übersicht von Überlegungen sein, welche bereits andere Leute für uns übernommen haben, sodass wir und auf wichtigere Dinge wie z.B. dem Implementieren neuer Features konzentrieren können.

 

4.) Refactoring

Vorweg: Ohne moderne IDE mit „Refoctoring“-Funktion (nicht Suchen- & Ersetzten) oder ohne Versionskontrollsystem (z.B. git) sollte man gar nicht mit dem Refactoring anfangen. Zudem sollten für Klassen, welche geändert werden sollen vor und nach den Änderungen entsprechende PHPUnit-Tests durchgeführt bzw. erstellt werden. Es folgt ein etwas längeres Video über das Refactoring via PhpStorm.

5.) Debug

Wie beim Refactoring ist es auch hier sehr Sinnvoll, dass man ein Versionskontrollsystem nutzt, um einerseits zu einem früheren Programmzustand zurückzukehren und andererseits, um seine Änderungen im Nachhinein nachvollziehen zu können.

Bei komplexeren Problemen ist es immer hilfreich, dass Problem im kleinen Nachzustellen. Ggf. kann man die Funktion / Klasse, welche das Problem verursacht außerhalb des gesamten Projektes betrachten und testen.

 

5.1) Fehler-Reporting

Wer einmal an einem größerem Open-Source-Projekt mitgearbeitet hat, wo User Bugs melden, der weiß wie wichtig die Beschreibung von Fehlermeldungen ist. Ich habe dazu extra eine Webseite eingerichtet, nachdem wir viele Meldung bekommen haben, dass etwas nicht funktioniert. → dorimanx.suckup.de/dorimanx-kernel-for-sg2-i9100/howto-report-bugs/

Zusammenfassung:
Minimaler Bug-Report: Was hast du getan (mit welcher Version der Software), was hast du erwartet was passieren sollte und was passiert momentan.

 

5.2) Xdebug

Xdebug ist ein Debugger und Profiler für PHP. Dabei ist der Profiler besonders hilfreich, wenn eine Anwendung Performance Probleme hat, welche man nur schwer lokalisieren kann. Zudem ersetzt Xdebug die eigentlichen Debug-Funktionen von PHP und reichert diese z.B. mit einem Trace des Problems an. Zusätzlich bietet die Erweiterung neue Funktionen z.B.: xdebug_debug_zval(), xdebug_memory_usage(), xdebug_get_function_stack(),

Installation: suckup.de/allgemein/toolchain-fuer-webentwickler

HowTo: code.tutsplus.com/tutorials/xdebug-professional-php-debugging–net-34396

 

5.3) Fehlermeldungen & Logfiles

Gerade bei PHP sollte man die Warnungen während der Programmierung aktivieren und beseitigen, so dass man Programmierfehler bereits bei der Entwicklung sieht und nicht dem Nutzer später auffallen. Ähnlich wie die Ölstand Warnanzeige im Auto sich zu einem wirklichen Problem entwickeln kann, wenn man sich nicht um dessen Behebung kümmert. Außerdem sollte es in jedem PHP-Projekt eine Logging-Klasse (z.B.: monolog, log4php) geben, welche bestimmte definierte Ereignisse aufzeichnet.

 

6.) Versionskontrollsystem (git)

An dieser Stelle möchte ich noch ein paar Worte zu „git“ verlieren. Erstens gibt es bereits viele praktische Programme und Integrationen in allen modernen IDEs, zweitens benötigt man im Standardfall nur wenige einfach Befehle auf der Kommandozeile, um git zu bedienen:

git Task

Notiz

Kommando

Erstellen eines neuen lokalen Repositories
git init
Klone ein existierendes Repository Erstelle eine Kopie eines lokalen Repositories
git clone /path/to/repository
Erstelle eine Kopie eines externen Repositories
git clone username@host:/path/to/repository
Datei hinzufügen Füge eine oder mehrere Dateien zum index
git add <Dateiname|*.php>

git add --all
Einchecken [commit] (wird noch nicht zum Server hochgeladen) Änderungen lokal einchecken
git commit -m "Nachricht"
Neue Dateien (git add) und Änderungen lokal einchecken
git commit -am „Nachricht“
Hochladen [push] von commits Sendet Änderungen zum „master“-Zweig vom Server
git push origin master
Status Zeigt eine Liste von geänderten, neuen und eingecheckten Daten an
git status
Verbindung zu einem externen Server hinzufügen Lokales Repository mit einem Server-Repository verbinden (um anschließend Commit zum Server senen zu können)
git remote add origin <server>
Zeige alle momentan Verbundenen externen Server an
git remote -v

Entwicklungszweig [branche]

Erstelle einen neuen lokalen Entwicklungszweig
git checkout -b <branchname>
Wechseln zwischen lokalen Entwicklungszweigen
git checkout <branchname>
Zeigt alle Entwicklungszweige an
git branch
Löscht ein lokalen Entwicklungszweig
git branch -d <branchname>
Sendet den aktuellen lokalen Entwicklungszweig zum Server
git push origin <branchname>
Sendet alle aktuellen Entwicklungszweige an den Server
git push --all origin
Löscht einen Entwicklungszweig auf dem Server
git push origin :<branchname>
[git push origin --delete <branchname>]
Updates vom externen (Server) Repository Ziehe und vereinige (merge) alle Änderungen vom externen Repository in dein lokales Repository
git pull
Merge einen lokalen Entwicklungszeig in den aktuellen lokalen Entwicklungszeig
git merge <branchname>
Zeige alle Änderungen an:Zeigt alle Änderungen (bis auf Zeilenumbrüche an ; funktioniert auch beim normalen „diff“)Zeigt Unterschiede von verschiedenen Branches an: git diff

git diff -u --ignore-all-space

git diff <sourcebranch> <targetbranch>

Tags Man kann Tags für Release-Versionen der Software vergeben
git tag 1.0.0 <commitID>
CommitId ist eine eindeutige ID zu jedem Commit und kann z.B. folgendermaßen angeigt werden …
git log
Sende alle Tags zum Server
git push --tags origin
lokale Änderungen Rückgängig machen Um eine Datei auf den Stand vom letzten Commit zurück zu drehen:
git checkout -- <filename>
WARNUNG: hier werden auch lokale Commits gelöscht und sowohl die Dateien als auch der Index geändert
git fetch origin

git reset --hard <commitID>
Suchen Sucht im aktuellen Verzeichnis git grep “foo()”

 

6.1) GIT – Commit Message Conventions

Commit-Style Erklärung Beispiel
[+] Ein neues Feature wurde hinzugefügt (auch kleine neue Funktionen oder Tests). [+]: added new SwiftmailerWrapper Class
[-] Feature wurde entfernt z.B. ein „revert“ eines [+] – Commits. [-]: revert some changes from <commitID>
[~] Ein Refactoring Änderung, wobei weder Fehler behebt noch Funktion hinzugefügt wurden [~]: refactored SwiftmailerWrapper Class
[!] Ein Fix für einen Bug. [fixes <issueID>] [!]: fixed embedding images in the SwiftmailerWrapper Class
[!#] Visualisiert, dass eine Änderung ein Sicherheitsproblem behebt. [!#]: fixed send() methode in the SwiftmailerWrapper Class
[!!!] Eine Änderung, welche Grundlegende Änderungen mit sich bringt und sich auch auf die Programmierung anderer Programmierung auswirkt. [!!!]: changed SwiftmailerWrapper constructor-parameter
[*] Alles was nicht zu den bisher definierten Fällen passt, z.B. Code-Style Änderungen, hinzufügen von Dokumentation oder Ändern von Build-Skripten [*]: fixed code-style from the SwiftmailerWrapper Class

 

6.2) GIT – Training

Interaktive Tutorials, welche man einmal in ein paar freien Minuten durchführen sollte.

try.github.io (englisch)

pcottle.github.io/learnGitBranching (deutsch)

 

6.3) git – Server

Wer „git“ im privaten Umfeld einsetzten möchte, seine Projekte jedoch nicht öffentlich auf github.com stellen möchte, der kann z.B. www.gitlab.com/gitlab-com nutzen und für Firmen empfiehlt es sich einen eignen „gitlab“-Server (github.com/gitlabhq/gitlabhq) zu installieren.

 

7.) CI – Server

Wer prüfen möchte, ob man sich in einem Projekt auch an gewisse PHP-Standards hält, der kann z.B. seinen CI-Server (Jenkins) für PHP optimieren und einige Tests automatisch ausführen / auswerten lassen:

– PHP_CodeSniffer → github.com/squizlabs/PHP_CodeSniffer

– PHPUnit → phpunit.de/manual/current/en/index.html

– phpCPD → github.com/EHER/phpunit-all-in-one/tree/master/src/phpcpd

– PHP_Depends → pdepend.org

– phpLOC → github.com/sebastianbergmann/phploc

– phpMD → phpmd.org

– phpdox → phpdox.de

Die entsprechenden Code-Standards kann man anschließend in einer „xml“-Datei (phpcs.xml) im Projekt hinterlegen und auch für andere Projekte verwenden.

JenkinsScreen

8.) Empfehlung

Zum Schluss möchte ich zu diesem Thema noch ein gutes Buch empfehlen → „Weniger schlecht programmieren“ ← . Auch wenn sich das Buch an vielen Stellen direkt an Programmieranfänger richtet, findend man doch einige Beispiele welche man direkt in der täglichen Arbeit umsetzten kann. PS: Am besten kauft man solche Bücher direkt beim Verlag und nicht bei z.B. Amazon. Für dich ist es der selben Preis, der Autor jedoch verdient dabei mehr Geld, da nicht noch ein Händler daran mit verdient.

 

Quellen:

– „Weniger schlecht programmieren“ ISBN: 978-3-89721-567-2 → http://www.oreilly.de/catalog/wenschleprogger/

– PHP: Was ist guter Code? → http://www.sitepoint.com/practical-refactoring-1/

– Lesbaren Quellcode schreiben → http://code.tutsplus.com/tutorials/top-15-best-practices-for-writing-super-readable-code–net-8118

– Test Code Coverage → http://code.tutsplus.com/articles/test-code-coverage-from-myth-to-reality–cms-20442

– PSR? → http://code.tutsplus.com/tutorials/psr-huh–net-29314

– PHP Standards Recommendation → https://github.com/php-fig/fig-standards

– Reddick-Namenskonvention: http://de.wikipedia.org/wiki/Reddick-Namenskonvention#Beispiele

– Refactoring PHP-Code + Beispiel → http://code.tutsplus.com/tutorials/refactoring-legacy-code-part-1-the-golden-master–cms-20331

– Refactoring mit PHPStorm → http://code.tutsplus.com/tutorials/phpstorm-when-the-ide-really-matters–cms-20787

– PHPDocs → http://phpdoc.org/

– PHPDocs – Wiki → http://en.wikipedia.org/wiki/PHPDoc

– Professional PHP Debugging → http://code.tutsplus.com/tutorials/xdebug-professional-php-debugging–net-34396

– xDebug → http://xdebug.org/docs/

– git Befehle → https://confluence.atlassian.com/display/STASH/Basic+Git+commands

– Commit Message rules for TYPO3 Flow → http://docs.typo3.org/flow/TYPO3FlowDocumentation/stable/TheDefinitiveGuide/PartV/CodingGuideLines/PHP.html#commit-messages

– Commit Message Format from AngularJS → https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit

– Template for Jenkins Jobs for PHP Projects → http://jenkins-php.org/installation.html

– HowTo für PHP + Jenkins → http://systemsarchitect.net/continuous-integration-for-php-with-jenkins/

– HowTo (Video) für PHP + Jenkins → https://www.youtube.com/watch?v=PklYO2vYIfc