Type Hinting in PHP: Ein Experiment.

Tags:
Seite:
1
2

Vorne weg...

Auf dieser Artikelseite steht Geschwafel über Type Hinting, Method Overloading und DocBlocks. Das Resultat namens TypeHintMe findest du auf der zweiten Seite.

Type Hinting

Type Hinting nennt sich das Vorgeben des Datentypes eines Funktionsparameters. In vielen Programmiersprachen (C, C++, Java) ist dies Pflicht, in den meisten Scriptsprachen schlicht inexistent. PHP bietet eine Mischform: So gibt es zwar Type Hinting für Objekte (über deren Klassennamen) und Arrays, aber nicht für skalare Datentypen (Strings, Integers, etc.).

// OK
function printName(User $user)
{}

// Syntax Error
function printName(string $forename, string $surname)
{}

Bei PHP gibt's übrigens noch den Sonderfall, dass Parameter mit einem Type Hint für ein Objekt den Standardwert null haben dürfen. Das kann ziemlich nützlich sein.

function printName(User $user, Language $language = null)
{
    if (!$language) {
        $language = Language::getCurrent();
    }

    // ...
}

Aber genug mit dem Exkurs über Type Hinting. Lösen wir das Problem des fehlendes Type Hintings für skalare Parameter mit den vorhandenen Mitteln.

Method Overloading

Das erste Puzzleteil ist Method Overloading.

Das Überladen von Methoden ist in PHP wiederum etwas anderes als beispielsweise in C++. Damit ist nämlich nicht gemeint, dass eine Methode mehrmals mit unterschiedlichen Parameter-Signaturen deklariert werden kann...

void printUser(User user)
{}

void printUser(string firstname, string lastname)
{}

...sondern, dass eine Methode dynamisch zur Laufzeit "erzeugt" werden kann. Dies geschieht aber nur, wenn die Methode nicht deklariert wurde oder im aktuellen Sichtbarkeitsbereich (Scope) nicht, ähm, sichtbar ist.

class User
{
    protected $forename;
    protected $surname;

    public function __construct($forename, $surname)
    {
        $this->forename = $forename;
        $this->surname  = $surname;
    }

    public function __call($method, array $params)
    {
        switch ($method)
        {
            case 'getName':
                $format = isset($params[0])
                        ? $params[0]
                        : '%s %s';
                return sprintf($format, $this->forename, $this->surname);

            default:
                throw new BadMethodCallException("Method '$method' was not declared.");
        }
    } 
}

$user = new User('Tom', 'Jones');
$user->getName();         // prints 'Tom Jones'
$user->getName('%s, %s'); // prints 'Tom, Jones'

DocBlocks

Als Nächstes müssen wir einen Weg finden, um den Datentyp eines Parameters irgendwie zu definieren. Da wir die Syntax von PHP nicht verändern können (würde ja zu einem Syntaxfehler führen) und als professionelle Programmierer unseren Code eh schon mit dem de-facto Standard phpDocumentor dokumentieren, können wir doch auch gleich dessen Kommentare nutzen, oder? Sorry, das war ein langer Satz.

Ein typischer phpDocumentor DocBlock einer Methode schaut etwa so aus:

class Auth
{
    /**
     * Try to authenticate a user by his credentials
     * 
     * @param string $username
     * @param string $password
     * @param bool $regenerateSessionId
     * @return bool
     */
    public function authenticate($username, $password, $regenerateSessionId)
    {
        return false; // Hihi
    }
}

Und da sind sie auch schon, unsere Metadaten. Die @param-Tags sagen uns nun, dass die ersten beiden Parameter vom Typ string und der dritte Parameter vom Typ boolean sein sollte. Sehr gut.

Ein Wrap mit allem, bitte.

Ein Problem haben wir aber noch. Wie geschrieben funktioniert die Überladung von Methoden in PHP nur, wenn diese nicht deklariert wurden. Um unser Type Hinting zu implementieren, müssen wir uns aber bei jedem Methodenaufruf einklinken können - auch bei deklarierten Methoden.

Um dieses Problem zu lösen können wir das Proxy-Pattern nutzen. D.h. wir umwickeln ein Objekt mit unserem eigenen und fangen mittels __call jeden Methodenaufruf ab, um ihn zu überprüfen (und weiterzuleiten).

class Wrapper
{
    protected $_object;

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

    public function __call($method, array $params)
    {
        // DocBlock von $this->_object->$method auslesen
        // und die @param-Tags mit den Werten in $params
        // überprüfen...

        return call_user_func_array(array($this->_object, $method), $params);
    }
}

$auth = new Auth();
$auth = new Wrapper($auth);
$auth->authenticate('ch.blocher', 'IchMagKätzchenH1h1h1');

Das schaut doch schon mal ganz schick aus. Auf der nächsten Seite stell ich euch nun TypeHintMe vor. :-)

Seite:
1
2

Ähnliche Artikel

Kommentare