Propel 1.5.5: propel-gen schlägt fehl

Neuerdings bekam ich den Propel-Generator nicht mehr zum laufen, zunächst vermutete ich Konflikte wegen unterschiedlicher Versionen die gleichzeitig installiert waren aber auch ein Update und die Sicherstellung, nur mit der neuesten Version zu arbeiten half nicht.

Stellte sich heraus, dass Propel mit den neuesten Versionen von Phing nicht klar kommt, genau genommen allem nach 2.4.2. Es war also leider folgendes nötig:

pear install -f phing/phing-2.4.2

(-f ist der “force”-Parameter, um neuere Versionen zu überschreiben)

Und voilà, keine Fehlermeldungen mehr!

PHP: Undefined constant __COMPILER_HALT_OFFSET__

Diese Notice bekam ich gelegentlich in einer Datei mit __halt_compiler(). Es hat mich einige Zeit gekostet, bis ich das Problem entdeckt habe… der Fehler kam nur wenn ich die Seite innerhalb kurzer Zeit aktualisiert habe, so dass ich nach einer Weile den Opcode Cache als Ursache vermutete.

Tatsächlich hing es mit APC zusammen, ich habe folgenden Bug Report gefunden: http://pecl.php.net/bugs/bug.php?id=15788&edit=2

Lösung: APC updaten oder einfach andere Methoden nutzen, Daten zu speichern als am Ende eines PHP Skripts 😉

PHP-CLI Default-Werte für Kommandozeilenparameter

Heute mal ein nützliches Code-Snippet um Kommandozeilenparameter auszuwerten. Gegeben seien Standardwerte als Array in $conf['params'], beispielsweise

array(
	'narf' => 'zort',
	'foo' => false,
	'bar' => true,
);

Das Skript wollen wir so aufrufen, dass booleans als einwertige Parameter übergeben werden können:

# Standard:
php -f skript.php

# narf=puit, foo=true
php -f skript.php -- narf=puit foo

# bar=false
php -f skript.php -- bar=0

Und so wirds gemacht:

// CLI args override conf['params']
if (isset($argv)) {
	for($i=1;$i<$argc;++$i) {
		list($param,$value) = explode('=', $argv[$i], 2) + array(1=>true);
		$conf['params'][$param] = $value;
	}
}

Der Clou ist die Array-Vereinigung mittels +, der Index 1 wird hier im Gegensatz zu array_merge() nur gesetzt wenn er noch nicht vorhanden ist, somit wird foo behandelt wie foo=true

PHP Array Path

Hier mal ein kleines Snippet 1 um auf ein bestimmtes Element in verschachtelten Arrays zuzugreifen. Nützlich, wenn ein String der Form "key1.key2.key3" vorliegt, und damit auf $array['key1']['key2']['key3'] zugegriffen werden soll.

Funktionen

<?php
/**
  * @param string $spec Spezifikation in der Form 'item_1.item_2.[...].item_n=wert'
  * @param array $array Ziel-Array
  */
function insert_into_array($spec, &$array)
{
    list($path,$value) = explode('=', $spec, 2);
    $current =& $array;
    // setze Referenz $current Schritt für Schritt auf $array['item_1']['item_2'][...]['item_n']
    foreach(explode('.', $path) as $key) {
        $current =& $current[$key];
    }
    // belege dieses Array-Element mit $value
    $current = $value;
}  
/**
 * @param string $path Pfad in der Form 'item_1.item_2.[...].item_n'
 * @param array $array Ursprungs-Array
 */
function &get_from_array($path, &$array)
{
    $current =& $array;
    foreach(explode('.', $path) as $key) {
        $current =& $current[$key];
    }
    return $current;
}

Beispiel: Nutzung

$array = array();
insert_into_array('item.test.6.12134.12.12.343=4546', $array);
insert_into_array('item.test.23=foo', $array);
var_dump(get_from_array('item.test', $array));

Beispiel: Ausgabe

array(2) {
  [6] =>
  array(1) {
    [12134] =>
    array(1) {
      [12] =>
      array(1) {
        ...
      }
    }
  }
  [23] =>
  string(3) "foo"
}

Notes:

  1. Ausgehend von dieser Forumsdiskussion

PHP Iterator, IteratorAggregate mit current(), next() etc. nutzen

Einige Anmerkungen zu PHP Traversables

Iteration mit Funktionen

Traversables können mit foreach iteriert werden, jedoch nicht im allgemeinen mit Array-Iterations Funktionen wie reset() und next().

Iterator Objekte können auch auch mit folgenden Funktionen verwendet werden:

  • current()
  • next()
  • prev()
  • reset()
  • end()

Achtung: Es ist zwar möglich, allen diesen Funktionen IteratorAggregate Objekte zu übergeben (wie auch jedes andere Objekt), die Iterator-Funktionalität wird dabei aber nicht genutzt, sondern es wird über die Attribute des Objekts iteriert.

Das selbe gilt für Iterator Objekte mit each()! Dieses sollte generell nicht für Objekte benutzt werden, da hilft weder ArrayAccess noch Iterator. Es funktioniert im allgemeinen nie wie erwünscht.

Überprüfung von unbekannter Variable auf Iterierbarkeit mit foreach

$a implements Traversable || is_array($a);
  • Traversable ist vor Iterator zu bevorzugen da auch IteratorAggregate und andere interne Iteratoren damit erkannt werden.
  • Arrays sind keine Traversables, da keine Objekte

Will man allerdings volle Flexibilität genügt:

is_object($a) || is_array($a)
  • PHP kann beliebige Objekte als Iterator benutzen und iteriert dabei über die öffentlichen Attribute!

PHP Typesafe Enum & Propel

Dieser Beitrag beschreibt kurz und knapp, wie meine Typesafe Enum-Klasse zusammen mit dem ORM Propel funktioniert. Mehr über Typesafe Enum und Enumerations in PHP finden Sie hier: Typesafe Enum

Use case:

Typesafe Enum datatypes in propel objects, mapped to SQL ENUM type.

Example in Propel (schema.xml):

<column name="title_casus" sqlType="ENUM('N','M','F')"
    phpType="Casus" required="false" description="Kasus"/>

Example Enum Type:

Enum::define('CasusEnum', 'N','M','F');

Required Data Wrapper:

/**
 * Wrapper class that allows multiple instances, needed by propel
 * 
 * @author Fabian Schmengler <fschmengler@sgh-it.eu>
 * @copyright © 2010 SGH informationstechnologie UG
 */

class Casus
{
	private $casusEnum;

	public function __construct($casusString)
	{
		if (method_exists('CasusEnum', $casusString)) {
			$this->casusEnum = call_user_func(array('CasusEnum', $casusString));
		} else {
			$this->casusEnum = null;
		}
	}
	public function __tostring()
	{
		return (string)$this->casusEnum;
	}
	public static function __set_state(array $properties)
	{
		return (string)$properties['casusEnum'];
	}
}

Note to myself: diese Wrapper-Geschichte könnte noch ausgebaut werden, so etwas wie Enum::getInstance($string) wollte ich eh mal in TypesafeEnum integrieren, warum nicht auch gleich einen mit-generierten Wrapper (der natürlich auch noch eine getEnum() Methode bekommt)

Smarty Method chaining

Ich musste mich mal wieder mit Smarty herumschlagen und habe eine Weile gebraucht um folgendes zu begreifen:

Method chaining in Smarty 2.x ist NICHT MÖGLICH. Genaugenommen bleibt {$object->getFoo()->getBar()} immer leer. Der Smarty-Parser nimmt den Ausdruck komplett auseinander, verarbeitet Objekte jedoch nur eine Ebene tief. In dem Fall ist also ein hässliches {assign var=_foo value=$object->getFoo()}{$_foo->getBar()} nötig. Dies gilt überall, wo das Objekt als Template-Variable genutzt wird.

RESTful Web Applications mit PHP und HTML

Wer sich beim erstellen einer Web-Applikation am REST Prinzip orientieren will und dies nicht nur für Webservices sondern auch fürs User-Interface benutzen will, stößt ganz schnell an die Grenzen von HTML-Formularen, die in den meisten Browsern nämlich nur GET und POST als Methode erlauben – alles andere, also auch PUT und DELETE wird standardmäßig als GET aufgefasst.

Das ist aber kein Grund aufzugeben, Ruby on Rails macht es vor:
How do forms with PUT or DELETE methods work?

CakePHP benutzt den selben Hack (oh Wunder ;-)), wie man hier und hier sieht.

Konkret werden dabei abhängig von $_POST['method'] die Umgebungsvariablen $_SERVER['REQUEST_METHOD'] bzw. $_ENV['REQUEST_METHOD']überschrieben, auf die Daten wird dann mittels $_REQUEST zugegriffen, genau wie bei echten PUT oder DELETE Requests.

Somit können Web-Browser die selbe API wie andere Clients benutzen und es wird ein einheitlicher Standard für URLs unterstützt.