Magento: Make New Customer Attributes Visible in Backend Forms

If you add a new attribute to the customer entity in a a setup script, using addAttribute(), gets a surprise starting from Magento 1.5. The setup runs and database entries in customer_eav_attribute are created correctly but in the backend there is no new form element.

Which works well with other entities like categories and until Magento 1.4 also with customers, needs an additional setup step now, to add the attribute explicitly to the admin form (and/or other forms, like the registration form).

There is a new table for it, customer_form_attribute, which defines for each attribute, in which forms it can be used:

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)

So much to for a better understanding, of course you don’t need to (and should not!) modify the database manually, it is sufficient to add the following lines after the addAttribute() call in your setup script:

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

with $attributeCode being the unique code of the new attribute and 'adminhtml_customer' the code for the customer management form in the backend. To keep setup scripts more clear if you add multiple attributes, it helps to extend the entity setup class, like this:

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();
    }
}

Now when you use this class for your setup scripts that add customer attributes, it’s as simple as:

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

Of course you can add the attribute to other forms in the same way, for the codes see the list above.

Thank you to the Magento forums for the crucial hint.

Update

Pay attention, on save the field eav_entity_attribute.sort_order gets set, regardless being specified before!

See my comment on StackOverflow.

Furthermore sort_order only applies if user_defined => 0 was specified in the attribute setup because Magento sorts by user_defined first and then by sort_order.

Anonymous function calls in PHP

Anonymous function calls are a well-known pattern in JavaScript but there are also use cases in PHP where they make sense. Of course PHP 5.3 with its Lambda Functions is required!

But let me first introduce the pattern shortly:

In JavaScript you often have code that just has to be executed when loaded but you really don’t want to pollute the global namespace. The solution is to create an anonymous function and call it directly:

(function() {
  var some, local, variables;
  // do something
})();

Why should you want this in PHP?

Imagine an application that is not fully object oriented (yes this is the reality and yes, sometimes that even makes sense) and has some include files which execute code directly. Now if you don’t unset all local variables at the end you leave a big mess in the global namespace. See the analogy?

Unfortunately the following is not posible with PHP Lambda Functions:

(function() {
  $localvar = 'foo';
  // do something
})(); // Parse error: syntax error, unexpected '('

But there is still good old call_user_func(). Since Lambda Functions are objects of the Closure class which again is of the callable “type”, it fits perfectly our needs:

call_user_func(function() {
  $localvar = 'foo';
  // do something
});

Now what if there are variables from the outer scope that you need or want to change? Of course you could use the global keyword but that only works if you are refering to the global scope and there is a better way: The use keyword.

Look at this example:

// test.php
function test() {
  $readMe = 'Hello';
  $writeMe = 'World';
  include 'include.php';
  echo 'index.php: ', $readMe, ' ', $writeMe, "\n";
}
test();

// include.php
call_user_func(function() use ($readMe, &$writeMe) {
  $temp = $readMe . ' ' . $writeMe;
  echo 'include.php: ', $temp, "\n";
  $readMe = 'Good Bye';
  $writeMe = 'Internet';
});

test.php results in the following output:
include.php: Hello World
index.php: Hello Internet

Let’s go through it step-by-step:

The $readMe and $writeMe variables are present in the local scope of test(). Since we also include include.php there, the scope stays the same within this file.

Using the anonymous function call we then open a new scope but take over $readMe and $writeMe with the use statement. Any other local variable from test() will not be present!

It is important to know that variables passed with use work similar to function parameters, so a copy is used by default (call-by-value). You can change this behaviour to call-by-reference exactly like in function declarations with a “&” denoting a reference.

Now our function prints the contents of $readMe and $writeMe to the screen (“Hello World”) and assigns new values to both variables.

Afterwards, back in test() the $writeMe variable that was passed by reference will hold this new value whereas $readMe is still the same because it was passed by value. Therefore the output is “Hello Internet”.

Of course $temp will not be set, it was a local variable in the anonymous function scope and destroyed after its execution.

One step further

To keep code maintainable the inclusion of files should not have side effects on variables which is on one hand assured by the anonymous function call but on the other hand bypassed again with use parameters by reference. To keep this transparent I recommend doing the wrapping around the include statement instead of (or even additional to) inside the included file:

// test.php
// ...
  call_user_func(function() use (&$writeMe) {
    include 'include.php';
  });
// ...

Now regardless what include.php contains it is clear that it will only have effects on $writeMe

Conclusion

If you work with procedural code and include files (and let it be just configuration files) anonymous function calls are a good way to keep the code more maintainable. They constrain unwanted side effects and make wanted side effects more visible without further documentation. So the two extra lines should really be worth their effort!

Update

Someone at reddit pointed out namespaces so let me clarify: Namespaces do not solve this problem!

Only classes, functions and constants are namespaced, a namespace does not have its own scope, so they share any variables within the parent scope!

CSS: 3D Ribbon Generator

Inspired by this article on pvmgarage.com I recently created a generator for 3D CSS ribbons and made it available on css3d.net. It creates CSS and HTML code for a cross-browser compatible ribbon effect (not limited to CSS 3!) based on parameters for sizes and colors. The red headers you see in this screenshot are also generated like that:

Have fun with it and feel free to comment if you have any suggestions.

If you are interested in how it works, visit the aforementioned link or read about the CSS Triangle trick at css-tricks.com.

CSS: Pseudo Bold Fonts

Sometimes you want to emphasize text but the bold version of a font is too “big”. With CSS 3 there is a simple way to thicken the regular font.

.pseudo-bold {
  text-shadow: 1px 0 0;
  padding-right: 1px;
  letter-spacing: 1px;
}

It creates a duplication that is shifted 1 pixel to the right, the padding and letter-spacing properties prevent overlapping. Of course such hacks will never look as nice as a real typographic bold font, so there are always other possibilities to consider. A way to make wide bold fonts more unobstrusive is reducing the letter spacing:

.narrow-bold {
  letter-spacing: -1px;
}

Here is a complete example with the Georgia font-family

<style type="text/css">
	#pseudo-bold-example {
		font-family:Georgia; font-size: 24px;
	}
	.bold {
		font-weight: bold;
	}
	.pseudo-bold {
		text-shadow: 1px 0 0;
		padding-right:1px;
		letter-spacing:1px;
	}
	.narrow-bold {
		font-weight: bold;
		letter-spacing:-1px;
	}
	.narrow-pseudo-bold {
		text-shadow: 1px 0 0;
		padding-right:1px;
	}
</style>
<div id="pseudo-bold-example">
	<p>Lorem Ipsum - normal text<br />
	<span class="bold">
		Lorem Ipsum</span> - bold text<br />
	<span class="pseudo-bold">
		Lorem Ipsum</span> - pseudo-bold text</span><br />
	<span class="narrow-bold">
		Lorem Ipsum</span> - narrow bold text<br />
	<span class="narrow-pseudo-bold">
		Lorem Ipsum</span> - narrow pseudo-bold text</span></p>
</div>

This is how it should look like:

pseudo-bold.png

For my current project I eventually decided to go with the narrow bold version, instead of the pseudo bold one. Bonus points: It is fully compatible with CSS 2.

Propel 1.5.5: propel-gen fails

Recentyl I couldn’t get the Propel generator running anymore. First I suspected conflicts due to different versions that were installed at the same time but an update and forcing usage of the latest version did not help.

Turns out that Propel cannot work with the latest versions of Phing, everyghing starting from Phing 2.4.2. So unfortunately the following was necessary:

pear install -f phing/phing-2.4.2

(-f is the “force” parameter to overwrite newer versions)

Et voilà, no more errors.

PHP: Undefined constant __COMPILER_HALT_OFFSET__

This notice showed up in a file with __halt_compiler() occasionaly. It took me some time to get the problem… the error came only when I refreshed the page within short time, so after a while I suspected the opcode cache to be the problem. Actually it was related to APC and I found the following bug report:
http://pecl.php.net/bugs/bug.php?id=15788&edit=2

Solution: Upgrade APC or just use other methods to store data than at the end of a PHP script 😉

CSS Top margin before out of nowhere

A typical CSS problem but I fall for it again and again. The whole site is shifted down 10-20 pixels in spite of every imaginable variant of margin:0 in the <html> and <body> elements.

So what was it? A <h1> somewhere in the floating layout whose margin-top pushed everything down.

Note to self: Always check for headings!

Useful link: Margin Collapsing

PHP Fatal Error Handler

Code snipped inspired by this article:

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;