Magento 2 Integration Tests: @magentoConfigFixture

Ich konnte keine gute Dokumentation zur @magentoConfigFixture Annotation in Magento 2 Integrationstests finden, also halte ich hier mal meine Zusammenfassung fest, nachdem ich den Core Code inspiziert habe (Magento 2.1.0, Magento\TestFramework\Annotation\ConfigFixture)

Wie man @magentoConfigFixture nutzt

Standardwert 42 für Konfigurationspfad x/y/z:

/**
 * @magentoConfigFixture default/x/y/z 42
 */

Store-spezifischer Wert 42 für Konfigurationspfad x/y/z im Store mit Code store1

/**
 * @magentoConfigFixture store1_store x/y/z 42
 */

Store-spezifischer Wert 42 für Konfigurationspfad x/y/z in aktuellem Store (also Standard-Store)

/**
 * @magentoConfigFixture current_store x/y/z 42
 */

Das sind alle möglichen Formate. Der erste Parameter muss mit _store enden oder weggelassen werden. Wenn er weggelassen wird, muss der Pfad mit default/ beginnen, sonst wird er ignoriert.

Implikationen

  • Konfigurationswerte können nicht auf Website-Ebene gesetzt werden
  • Man sollte nicht “current” als echten Store Code verwenden, sonst kann für diesen Store keine Konfigurations-Fixture genutzt werden

5 Minuten Tipps: Magento Performance Tweaks

Meine “Woche auf StackExchange” Reihe pausiert gerade weil nicht sooo viel bloggenswertes wöchentlich zusammenkommt.

Stattdessen heute mal wieder etwas neues: Tipps zu einem bestimmten Themenbereich, die man sich in maximal 5 Minuten in der Kaffeepause durchsehen kann. Das meiste nicht von mir sondern nur von mir gefunden 🙂

Es soll keine regelmäßige Reihe werden, aber ich denke es kommt das ein oder andere zusammen, was ich bisher lose gesammelt habe. Da nutze ich doch mal wieder das Blog zum Festhalten von nützlichen Dingen und hoffe, es haben noch mehr Leute etwas davon.

Fangen wir an mit Magento Performance Tweaks, alle mit wenig Aufwand, die alle relativ bedenkenlos eingesetzt werden können:

Continue reading “5 Minuten Tipps: Magento Performance Tweaks”

Magento Attribute effizient inkrementieren & dekrementieren

Magento.SE Screenshot

Diese Frage tauchte auf Magento StackExchange auf:

I need to decrement a value with an atomic database operation, is it possible using Magento models?

Es ist tatsächlich möglich, Attribute mit einem Update zu inkrementieren und dekrementieren und zwar mit einer weniger bekannten Technik mittels Zend_Db_Expr. Ich teile es auch hier:

$object->setNumber(new Zend_Db_Expr('number-1'));

Als Referenz:

Die Methode Mage_Core_Model_Resource_Abstract::_prepareDataForSave() enthält folgenden Code:

if ($object->hasData($field)) {
    $fieldValue = $object->getData($field);
    if ($fieldValue instanceof Zend_Db_Expr) {
        $data[$field] = $fieldValue;
    } else {
        ... [normale Verarbeitung folgt]

EAV Models:

Beachte, dass man das Attribut nur mit seinem Namen referenzieren kann (“number” im Beispiel) wenn es eine echte Spalte der Haupt-Tabelle ist, kein EAV-Attribut.

Obwohl die obengenannte Methode nur von Models mit flachen Tabellen benutzt wird, kann Zend_Db_Expr aber auch für EAV Attribute benutzt werden, die Methode die den Parameter verarbeitet, ist Varien_Db_Adapter_Pdo_Mysql::prepareColumnValue().

ABER man muss dann immer den Spaltennamen “value” verwenden:

$product->setNumber(new Zend_Db_Expr('value-1'));

Es ist nicht notwendig, einen Alias für die Tabelle anzugeben, weil jedes Attribue einzeln mit eigenem Query verarbeitet wird, so dass “value” nicht mehrdeutig ist.

Magento: Direktlink auf Tab in Adminhtml Tab Widgets

Für eine Extension an der ich neulich gearbeitet habe, habe ich mich gefragt, ob es eine eingebaute Möglichkeit gibt, direkt auf einen Tab einer Seite im Backend zu linken. Meine Nachforschungen haben nichts ergeben (sprich: Mein Google Foo hat versagt), also habe ich mir den Core Code vorgenommen um einen Ansatz zu finden. Was ich herausgefunden habe:

Das Problem

Im speziellen wollte ich einen Link auf den “Bezeichnungen / Optionen verwalten” Tab der “Produktattribut bearbeiten” Seite setzen:

Screenshot

Die Lösung

Tatsächlich ist es mit einem URL-Parameter möglich: ?active_tab=$id.

Wie finde ich die Tab-ID heraus?

Finde den verantwortlichen Tab Container. Dies ist eine Unterklasse von Mage_Adminhtml_Block_Widget_Tabs, in meinem Fall Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tabs.

Du wirst Aufrufe von $this->addTab() finden, üblicherweise in den Methoden _beforeToHtml(), oder _construct(). Der erste Parameter von addTab() ist die Tab ID:

    $this->addTab('labels', array(
        'label'     => Mage::helper('catalog')->__('Manage Label / Options'),
        'title'     => Mage::helper('catalog')->__('Manage Label / Options'),
        'content'   => $this->getLayout()->createBlock('adminhtml/catalog_product_attribute_edit_tab_options')->toHtml(),
    ));

Die URL ist also /admin/catalog_product_attribute/edit/attribute_id/123/?active_tab=labels, generiert mit diesem Code (innerhalb eines Adminhtml Blocks):

    $this->getUrl('adminhtml/catalog_product_attribute/edit',
        array('attribute_id' => 123, '_query' => array('active_tab' => 'labels'));

Wie es funktioniert

Schauen wir uns den verantwortlichen Code an:
Continue reading “Magento: Direktlink auf Tab in Adminhtml Tab Widgets”

Magento: Secure Base URL für eigene Controller verwenden

Die Frage tauchte auf Magento.SE auf und es sollte bekannter sein, wie kinderleicht dies eigentlich ist.

Schaut man sich core/Mage/Checkout/etc/config.xml an, kann man sehen, wie Magento für den Checkout definiert, dass die “Secure Base URL” genutzt wird, also HTTPS:

<frontend>
    <secure_url>
        <checkout_onepage>/checkout/onepage</checkout_onepage>
        <checkout_multishipping>/checkout/multishipping</checkout_multishipping>
    </secure_url>
</frontend>

Das ist auch schon alles. Auf die selbe Weise können eigene Controller konfiguriert werden, die sichere Basis-URL zu verwenden. Anschließend wird Mage::getUrl() die sichere URL für die konfigurierten Routen zurückgeben und Requests auf die ungesicherten URLs werden weitergeleitet.

SVG Bilder in Magento Uploader erlauben

Wer die erlaubten Bilddatei-Typen für Produktbilder in Magento ändern will, wird feststellen, dass sie hart kodiert sind in Mage/Catalog/Model/Product/Attribute/Backend/Media.php und Mage/Adminhtml/controllers/Catalog/Product/GalleryController.php

Ich habe allerdings einen Observer gefunden, mit dem sie zusammen mit anderen Uploader-Konfigurationen angepasst werden können. Dieser Beitrag beschreibt, wie eine Extension für diesen Zweck erstellt werden kann, Grundlagen der Magento Extension-Programmierung werden vorausgesetzt.

Continue reading “SVG Bilder in Magento Uploader erlauben”

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 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)

PHP Fatal Error Handler

Codeschnipsel inspiriert von diesem Artikel:

fatalerrorhandler.php

<?php
// report all but fatal errors 
error_reporting((E_ALL | E_STRICT) ^ (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR));

// fatal error handler as shutdown function
register_shutdown_function('fatalErrorHandler'); 

function fatalErrorHandler() {
	$error = error_get_last();
	if ($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
		echo '<h1>Fatal Error, shutting down...</h1>';
		echo '<pre>' . var_export($error,true) . '</pre>';
	} else {
		echo 'Regular Shutdown, no fatal errors.';
	}
}

test1.php

<?php
require 'fatalerrorhandler.php';

// Fatal Error (E_ERROR)
unknown_function_call();

test2.php

<?php
require 'fatalerrorhandler.php';

// E_USER_ERROR
trigger_error('...', E_USER_ERROR);

test3.php

<?php
require 'fatalerrorhandler.php';

// Notice (E_NOTICE)
echo $unknown_var;