Magento: Neue Kundenattribute im Backend-Fomular sichtbar machen

Wer in einem Setup-Skript mittels addAttribute() der Customer-Enitität neue Attribute hinzufügen will, erlebt ab Magento 1.5 eine Überraschung. Das Setup läuft zwar fehlerfrei und die DB-Einträge in customer_eav_attribute sind anschließend vorhanden aber es findet sich im Backend kein neues Formular-Element.

Was mit anderen Entitäten wie z.B. Kategorien problemlos funktioniert und bis Magento 1.4 auch mit Kunden, benötigt hier nun eine zusätzlichen Setup-Schritt mit dem das Attribut explizit dem Admin-Formular hinzugefügt wird.

Dazu existiert eine neue Tabelle customer_form_attribute, die für jedes Attribut festlegt, in welchen Formularen es verwendet wird:

mysql> select * from customer_form_attribute;
+----------------------------+--------------+
| form_code                  | attribute_id |
+----------------------------+--------------+
| adminhtml_customer         |            1 |
| adminhtml_customer         |            3 |
| adminhtml_customer         |            4 |
| checkout_register          |            4 |
| customer_account_create    |            4 |
| customer_account_edit      |            4 |
| adminhtml_customer         |            5 |
| checkout_register          |            5 |
| customer_account_create    |            5 |
| customer_account_edit      |            5 |
| adminhtml_customer         |            6 |
| checkout_register          |            6 |
| customer_account_create    |            6 |
| customer_account_edit      |            6 |
| adminhtml_customer         |            7 |
| checkout_register          |            7 |
| customer_account_create    |            7 |
| customer_account_edit      |            7 |
[...]
+----------------------------+--------------+
88 rows in set (0.00 sec)

Aber dies nur zum Verständnis, natürlich muss man nicht selber an der Datenbank Hand anlegen, es genügen folgende Zeilen nach dem addAttribute()-Aufruf im Setup-Skript:

    Mage::getSingleton('eav/config')
        ->getAttribute('customer', $attributeCode)
        ->setData('used_in_forms', array('adminhtml_customer'))
        ->save();

wobei $attributeCode der eindeutige Code des neuen Attributs ist und 'adminhtml_customer' der Code für das Kundenverwaltungsformular im Backend. Um die Setup-Skripte übersichtlicher zu halten empfiehlt sich eine Erweiterung der Entity Setup-Klasse etwa wie folgt:

class MyNamespace_ExtendableCustomer_Model_Entity_Setup
    extends Mage_Customer_Model_Entity_Setup
{
    public function addAttributeToForm($attributeCode, $formCode)
    {
    	Mage::getSingleton('eav/config')
            ->getAttribute('customer', $attributeCode)
            ->setData('used_in_forms', (array) $formCode)
            ->save();
    }
}

Nutzt man nun diese Klasse für seine Setup-Skripte, die Kundenattribute hinzufügen, ist es ganz einfach:

$this->addAttribute('customer', 'neues_attribut', array( /* ... */);
$this->addAttributeToForm('neues_attribut', 'adminhtml_customer');

Analog kann das Attribut auch zu anderen Formularen hinzugefügt werden (Codes siehe oben).

Danke ans Magento-Forum für den entscheidenden Hinweis!

Update

Achtung, beim Speichern wird zusätzlich das Feld eav_entity_attribute.sort_order gesetzt, unabhängig davon ob dieses schon vorher spezifiziert wurde!

Siehe dazu mein Kommentar auf StackOverflow.

Weiterhin wirkt sich sort_order nur aus wenn beim Anlegen des Attributs user_defined => 0 gesetzt wird, da vor sort_order nach user_defined sortiert wird!

Anonyme Funktionsaufrufe in PHP

Dieser Beitrag ist nur auf Englisch erschienen.

CSS: 3D Ribbon Generator

Inspiriert von diesem Artikel auf pvmgarage.com habe ich kürzlich einen Generator für 3D CSS Ribbons geschrieben und ihn auf css3d.net veröffentlicht. Er erstellt CSS und HTML code für einen cross-browser kompatiblen Ribbon Effekt (nicht beschränkt auf CSS 3!), basierend auf Parametern für Größen und Farben. Die roten Überschriften im untenstehenden Screenshot sind auch so generiert:

Viel Spaß damit!

Wenn Du interessiert daran bist, wie es funktioniert, besuche den obigen Link oder lies über den CSS Dreieck Trick auf css-tricks.com.

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 😉

Zend Studio Code Completion kaputt

Mir hat folgender Tipp geholfen, die Code Completion in Zend Studio (Eclipse) wieder zum Laufen zu bringen: http://forums.zend.com/viewtopic.php?f=59&t=5585&start=20#p19107

  1. Gehe zu /Zend/workspaces//.metadata/.plugins/org.eclipse.core.runtime/.settings
  2. Lösche Datei org.eclipse.dltk.ui.prefs

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

CSS Top margin vor scheinbar aus dem Nichts

Ein typisches CSS problem aber ich stolpere immer wieder darüber: Die ganze Seite ist 10-20 pixel nach unten verschoben, trotz aller vorstellbaren Varianten von margin:0 in den <html> und <body> Elementen.

Was war es? Ein <h1> irgendwo im floating layout, dessen margin-top alles nach unten verschob.

Notiz an mich selbst: Immer auf die Überschriften achten!

Nützlicher Link: Margin Collapsing

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!