Category Archives: Magento

EAV Properties for Magento

The only thing I hate more than bad code completion is no code completion.  When working with PHP arrays for configuration there are often options that you need to remember to properly configure the object, factory or whatever, that you are using.

What I really like to do for my own code is that when I do have some kind of array-based configuration I like to have class constants that define what options there are for me to set.  Because they are constants they cut down on fat-finger problems and also take out a lot of the guesswork.

Magento is pretty good about doing this, but one place where I have not found much by way of constants is in the EAV system.  There are a number of EAV properties that you can set.  Here is a table of the ones I found.

Config KeyMerged KeyDefault Value
backendbackend_model
typebackend_typevarchar
tablebackend_table
frontendfrontend_model
inputfrontend_inputtext
labelfrontend_label
frontend_classfrontend_class
sourcesource_model
requiredis_required1
user_definedis_user_defined0
defaultdefault_value
uniqueis_unique
notenote
globalis_global1 (Mage_Catalog_Model_Resource_Eav_Attribute:: SCOPE_GLOBAL)
input_rendererfrontend_input_renderer
visibleis_visible1
searchableis_searchable0
filterableis_filterable0
comparableis_comparable0
visible_on_frontis_visible_on_front0
wysiwyg_enabledis_wysiwyg_enabled0
is_html_allowed_on_frontis_html_allowed_on_front0
visible_in_advanced_searchis_visible_in_advanced_search0
filterable_in_searchis_filterable_in_search0
used_in_product_listingused_in_product_listing0
used_for_sort_byused_for_sort_by0
apply_toapply_to0
positionposition0
is_configurableis_configurable1
used_for_promo_rulesis_used_for_promo_rules0

Kind of a lot.  And as you get more experience you will memorize them.  But what a pain in the butt.

So I created a new GitHub repository that allows you to simply use some class constants instead of hand-typed array keys for EAV configuration.  Most of the code I have seen for EAV configuration uses inputted key values instead of constants and my Google searches have not yielded much.  So my presumption, possibly incorrect, is that this kind of helper class does not exist.

Using the class is very, very simple.  You only need to put it in code from GitHub in code/local/Eschrade/Util.  The autoloader should take care of the rest.  No need to manage a config file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$installer = Mage::getResourceModel('catalog/setup','default_setup');
 
$config = array(
	Eschrade_Util_Statics::EAV_TYPE_ALIAS 	=> Eschrade_Util_Statics::EAV_TYPE_TEXT,
	Eschrade_Util_Statics::EAV_INPUT_ALIAS	=> Eschrade_Util_Statics::EAV_INPUT_BOOLEAN,
	Eschrade_Util_Statics::EAV_LABEL_ALIAS 	=> 'Some Attribute',
);
 
$installer->addAttribute(
	Mage_Catalog_Model_Product::ENTITY,
	'some_attribute',
	$config
);
 
$installer->updateAttribute(
	Mage_Catalog_Model_Product::ENTITY,
	'some_attribute',
	Eschrade_Util_Statics::EAV_LABEL,
	'Some Other Attribute'
);

Note that for the addAttribute() call I use the name  + _ALIAS.  So ‘label’ for adding an attribute is Eschrade_Util_Statics::EAV_LABEL_ALIAS but for updating an attribute it is Eschrade_Util_Statics::EAV_LABEL.

Also note that this class is not complete.  I will make additions to it as I have time and I hope that others who see holes make push requests to add more things.

Please, please fork it, make additions and fix errors you may see.  Personally, I would rather be able to choose from a list of options than having to manually type things in.  It makes things much cleaner and predictable.  So I hope that this will make things a little easier for you.

No-.htaccess httpd.conf file for Magento

A couple of days ago I wrote a blog post on how why you should not use .htaccess files, or AllowOverride != All, on a production web server.  What you should do is place the .htaccess configuration information into your httpd.conf file instead.

So of course I was asked what that would look like.  So here it is.  I took all of the .htaccess settings, stripped some of the superfuous ones and removed the comments ( for clarity :-) ) and here is what you have.  Customize for your own site, of course.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<VirtualHost *:80>
	ServerName magento.loc
	DocumentRoot /var/www/html
	DirectoryIndex index.php
 
	<Directory /var/www/html/var/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/>
		AllowOverride None
		<IfModule mod_php5.c>
 
		    php_value memory_limit 128M
		    php_value max_execution_time 18000
 
		    php_flag magic_quotes_gpc off
		    php_flag session.auto_start off
 
		</IfModule>
 
		<IfModule mod_security.c>
		    SecFilterEngine Off
		    SecFilterScanPOST Off
		</IfModule>
 
		<IfModule mod_ssl.c>
		    SSLOptions StdEnvVars
		</IfModule>
		<IfModule mod_rewrite.c>
 
		    Options +FollowSymLinks
		    RewriteEngine on
 
		    #RewriteBase /magento/
		    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
		    RewriteCond %{REQUEST_URI} !^/(media|skin|js)/
		    RewriteCond %{REQUEST_FILENAME} !-f
		    RewriteCond %{REQUEST_FILENAME} !-d
		    RewriteCond %{REQUEST_FILENAME} !-l
		    RewriteRule .* index.php [L]
 
		</IfModule>
 
		    AddDefaultCharset Off
		    #AddDefaultCharset UTF-8

		<IfModule mod_expires.c>
		    ExpiresDefault "access plus 1 year"
		</IfModule>
	    Order allow,deny
	    Allow from all
	</Directory>
 
	<Directory /var/www/html/includes/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/errors/>
		<FilesMatch "\.(xml|phtml)$">
		    Deny from all
		</FilesMatch>
	</Directory>
 
	<Directory /var/www/html/pkginfo/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/app/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/lib/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/downloader/>
		<IfModule mod_deflate.c>
 
		    RemoveOutputFilter DEFLATE
		    RemoveOutputFilter GZIP
 
		</IfModule>
 
		<Files ~ "\.(cfg|ini|xml)$">
		    order allow,deny
		    deny from all
		</Files>
	</Directory>
 
	<Directory /var/www/html/downloader/template/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/media/>
		Options All -Indexes
		<IfModule mod_php5.c>
			php_flag engine 0
		</IfModule>
 
		AddHandler cgi-script .php .pl .py .jsp .asp .htm .shtml .sh .cgi
		Options -ExecCGI
 
		<IfModule mod_rewrite.c>
		    Options +FollowSymLinks
		    RewriteEngine on
		    RewriteCond %{REQUEST_FILENAME} !-f
		    RewriteRule .* ../get.php [L]
		</IfModule>
	</Directory>
 
	<Directory /var/www/html/media/customer/>
		Order deny,allow
		Deny from all
	</Directory>
 
	<Directory /var/www/html/media/downloadable/>
		Order deny,allow
		Deny from all
	</Directory>
 
</VirtualHost>

Don’t modify index.php in Magento for multi-store configs

unless you really, really have to.  I did some quick Googling on this and found that a number of places recommend making changes to index.php.

In my humble opinion it is better to leave the index.php file alone and configure your stores via SetEnv in your Apache config, as is noted in the Magento wiki.

For example

<VirtualHost *:80>
SetEnv MAGE_RUN_CODE msv
ServerName kps.loc
DocumentRoot /var/www/magento.loc/magento
CustomLog logs/magento.access_log varnishcombined
ErrorLog logs/magento.error_log
</VirtualHost>

<VirtualHost *:80>
SetEnv MAGE_RUN_CODE base
ServerName magento.loc
DocumentRoot /var/www/magento.loc/magento
CustomLog logs/magento.access_log varnishcombined
ErrorLog logs/magento.error_log
</VirtualHost>

No code changes required and upgrades won’t break your modifications.

That is all.

10 “what to do’s when setting up Magento” and file inclusion attacks

Found this list of things “to do” on Twitter this morning.  I went over the list and saw that there was one item that was missing, which I feel is very important to do.  I saw it in another post on Local File Inclusion for which it seems like there was a local file inclusion vulnerability in Joomla (I think.  I didn’t read that far into it).

The thing on the list that was missing was securing your local file system when installing Magento.  The default installation asks for certain directories to be writable.  This is necessary for certain things.  But what we lazy installers sometimes do is just make the whole thing writable to make installation easier.  And while I am not aware of any specific Magento vulnerability like the one noted it is definitely a good practice to deny write access to all but the necessary files.  This is done by changing the permission settings on the files but changing the file ownership so that the web server user is unable to change the permissions to something more permissive.  And for the files that you need write access to you should deny access via either .htaccess or <directory> settings in httpd.conf so they can’t be called remotely.

So, the 11th thing to do is to secure your file system by denying write access to the server user that is running your Magento code.

Starting with Magento on Monday

Having spent several years as a consultant with Zend, working with highly scalable applications, developing many of Zend’s training courses, building mobile applications and doing my best to be a generally good guy I am making the move to Magento.  More specifically, MagentoU.  Magento has, for several years, been a company that I have been interested in.  Their product is technically quite interesting and very powerful, but my interest has been in watching the company’s meteoric rise.

This rise is because they did a lot of things right.  From the start the system was designed to be expandable.  It was designed to be easily built upon without having to rewrite portions of the core code.  This allowed a community of developers and companies to spring up around the software which, in turn, generates a tremendous amount of activity.  So Varien, now Magento, built not just a software package, but provided the groundwork for an ecosystem to sprout and grow.

And grow it did.

But now, a new chapter starts in Magento’s life.

Me.

hehe.  More likely it’s the other way around :-)

I will be a Technical Manager for Education and Consulting.  Sounds boring, right?  Not at all.  It is actually a very wide ranging position.  I will be teaching, developing courseware, driving self-education in the Magento community (blogging, videos, forums, etc.), provide support for training partners, execute consulting services, working with customers and partners and a whole bunch of other things.

This is an opportunity I am quite excited to be part of and I am quite thankful for having it.

… I’m also hoping that now that I have an “8-5″ job, which I am well aware it will not be, that I will be able to carve out some time for writing a couple of tunes again.  When you’re trying to start your own company you tend to force those kinds of things to the backburner.  I’m hoping that I will be able to move it forward a little bit.  (and the world cheers!).

Magento, ESI, Varnish and performance

I have been doing a little playing with Magento over the past couple of days.  I’ve been helping out Ebay/Magento by delivering some of their performance training over the past few months.  I’m by no means the world’s best Magento person at the moment, but I know the architecture pretty well.

One of the things I’ve wanted to do is play more with Varnish.  There’s lots of hype, and a lot of the hype is true.  It really is as fast as they say it is and worth looking at for a full-page caching solution.

But what about when you “can’t” do full page caching?  Enter Magento (or any ecommerce platform).  Most of the time you pages are fully cacheable.  Right up until you click the “Add To Cart” button.  At that point full page caching doesn’t work and so the default behavior for most platforms is to simply not cache output at that point.

But the problem is that you still have 90% of the page (or more) is still cacheable.  So you are doing full execution on a page that has a bare minimum of actual dynamic content.

This is where ESI comes in.  ESI, or Edge Side Includes, allow servers on the edge of a CDN do full page caching but do dynamic callbacks to your website to fill in certain parts of the page.  You lose a lot of the benefit of having a full page cache in that the many order of magnitude performance improvements you gain with full page caching is reduced to how fast you can get the backend dynamic content.

But who says that ESI only needs to be on the edge?  I decided to take a look at how I might implement ESI, with partially dynamic content in Magento, using Varnish as a processor.

My results are in and they look pretty good, though they are preliminary.  As I write this blog post I’m dealing with some wildly erratic response times from Varnish.  Varnish as a full page cache is consistent.  The ESI pages are consistent.  But put them together and I have response times that vary by two orders of magnitude when I do a benchmark that uses concurrent connections.

But if I do the benchmark with sequential HTTP calls you can definitely see an improvement.

First the results without a full page cache.  This is the way you do it by default now once someone adds something to their shopping cart.

Concurrency Level: 1
Time taken for tests: 1.171843 seconds
Complete requests: 10
Failed requests: 0
Write errors: 0
Total transferred: 81030 bytes
HTML transferred: 76210 bytes
Requests per second: 8.53 [#/sec] (mean)
Time per request: 117.184 [ms] (mean)

Now the results when using Varnish with ESI.

Concurrency Level: 1
Time taken for tests: 0.591032 seconds
Complete requests: 10
Failed requests: 0
Write errors: 0
Total transferred: 86500 bytes
HTML transferred: 81000 bytes
Requests per second: 16.92 [#/sec] (mean)
Time per request: 59.103 [ms] (mean)

That is roughly a 50% improvement with no loss of functionality.

In this example I simply removed the call to the Magento sidebar which renders the shopping cart and replaced it with an ESI tag.

<esi:include src=”http://magento.loc/eschrade/esi/sidebar” />

That routes to a controller where I manually spit out the sidebar contents.

Now, you might be thinking “OK, that’s one HTTP call.  What happens if you have 20 ESI calls?  Won’t your server be overloaded?”  Well, perhaps (though not definitely).  But what you need to remember is that performance and scalability are two different things.  Yes, you could overload that ONE server with 20 ESI calls.  But what if you have 20 servers running behind a load balancer?  Yes, you will be using much more by way of server resources.  But if your response time from each of those backend servers is 50 ms you still only have a total response time of 50ms, give or take a few nanoseconds to render the page.  So while CPU time will be greatly increased across your cluster, wall time will be greatly reduced because the processing is being done asynchronously with Varnish aggregating the results.

This, of course, assumes that Varnish does its ESI processing asynchronously, which I will confirm.  (I’d be surprised if it were synchronous)

[UPDATE]

As noted in the first comment before, it does look like Varnish processes ESI synchronously, meaning one at a time.  So these numbers would seem to hold if you only have one ESI call to make, but they drop off significantly as you add ESI calls.  So I will be looking at different options since the whole premise depends on asynchronous ESI.

Crap.

[/UPDATE]

But like I said, these are preliminary results.  I will do a fuller blog post on the subject when I have the inconsistent performance issue worked out.

 

Why is FastCGI /w Nginx so much faster than Apache /w mod_php?

I was originally going to write a blog post about why NginX with FastCGI was faster than Apache with mod_php.  I had heard a while ago that NginX running PHP via FastCGI was faster than Apache with mod_php and have heard people swear up and down that it was true.  I did a quick test on it a while back and found some corresponding evidence.

Today I wanted to examine it more in depth and see if I could get some good numbers on why this was the case.  The problem was that I couldn’t.  IIRC, it was for a Magento installation.

To test I did a simple “hello, world” script.  Why something simple?  Because once you’re in the PHP interpreter there should be no difference in performance.  So why not just do a blank page?  It’s because I wanted to have some kind of bi-directional communication.  The intent was to test the throughput of the web-server, not PHP.  So I wanted to be spending as little time in PHP as possible but still test the data transmission.

The baseline tests show the following.

Apache w/ mod_php

 Total transferred: 3470000 bytes
 HTML transferred: 120000 bytes
 Requests per second: 2395.73 [#/sec] (mean)
 Time per request: 4.174 [ms] (mean)
 Time per request: 0.417 [ms] (mean, across all concurrent requests)
 Transfer rate: 811.67 [Kbytes/sec] received

NginX with PHP-FPM

 Total transferred: 1590000 bytes
 HTML transferred: 120000 bytes
 Requests per second: 5166.39 [#/sec] (mean)
 Time per request: 1.936 [ms] (mean)
 Time per request: 0.194 [ms] (mean, across all concurrent requests)
 Transfer rate: 801.82 [Kbytes/sec] received

Apache was able to dish out 2400 requests per second compared with 5200 requests per second on NginX.  That was more than I had seen before and so  I did an strace -c -f on Apache to see what came up.  -c shows cumulative time on system calls, -f follows forks.  The result for the top 10?

% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
 33.65 0.042063 4 10003 getcwd
 16.10 0.020127 2 10001 writev
 16.00 0.019994 2 10001 shutdown
 10.54 0.013179 0 51836 40118 open
 9.01 0.011263 1 20008 semop
 5.22 0.006522 0 54507 10002 read
 2.53 0.003158 0 10024 write
 1.91 0.002386 0 88260 66483 close
 1.57 0.001959 245 8 clone
 1.16 0.001455 0 54832 384 stat64

getcwd?  Why?  Then I remembered that I had AllowOverride (.htaccess) turned on.  So I re-ran the test with AllowOverride set to None.

Total transferred: 3470000 bytes
HTML transferred: 120000 bytes
Requests per second: 5352.41 [#/sec] (mean)
Time per request: 1.868 [ms] (mean)
Time per request: 0.187 [ms] (mean, across all concurrent requests)
Transfer rate: 1813.40 [Kbytes/sec] received

At 5352 requests per second Apache actually was outperforming NginX.  But what about if more data was transferred?  So I created about 100k of content and tested again.

Apache

Total transferred: 1051720000 bytes
HTML transferred: 1048570000 bytes
Requests per second: 2470.24 [#/sec] (mean)
Time per request: 4.048 [ms] (mean)
Time per request: 0.405 [ms] (mean, across all concurrent requests)
Transfer rate: 253710.79 [Kbytes/sec] received

NginX

Total transferred: 1050040000 bytes
HTML transferred: 1048570000 bytes
Requests per second: 2111.08 [#/sec] (mean)
Time per request: 4.737 [ms] (mean)
Time per request: 0.474 [ms] (mean, across all concurrent requests)
Transfer rate: 216476.53 [Kbytes/sec] received

This time the difference was even greater.  This all makes sense.  mod_php has PHP embedded in Apache and so it should be faster.  If you’re running only PHP on a web server then Apache still seems to be your best bet for performance.  And if you are seeing a significant performance difference then you should check if AllowOverride is turned on.  If it is, try moving that into httpd.conf and try again.

If you are running mixed content, such as adding CSS, JS and images, then NginX will provide better overall performance but it will not run PHP any faster.  It will also respond better to denial of service attacks better, but a CDN is generally better at mitigating that risk.

But if you are running pure PHP content on a given server, Apache seems to still be the best bet for the job.

Connecting PHP to ActionScript and Flex using Stomp

In yesterday’s post I talked a little bit about some of the details on how I used messaging to connect a front end on demand request to a back end scheduled data processing mechanism.  In this post we’re going to talk about how to send data from a web page to a running Flex application without using HTTP.  It is really quite easy.

First up you need a message queue that supports Stomp.  Stomp is a text-based message queue protocol that a good number of message queue applications support.  I chose ActiveMQ, mostly because of authentication issues I was running into with RabbitMQ that I didn’t have time to solve.  Stomp isn’t turned on automatically so you need to change some config settings.  I have details at my first post on how to do that.

In my demo I wanted to have real time notifications of when a sale came in from a Magento application.  So the first thing to do would be to create a queue endpoint in ActiveMQ that you can send the data to.  That’s the easy part.  Next I needed to create an observer that would be triggered when a sale is made.  Magento uses an event based system and so hooking in to that was relatively easy.  The observer looked like this:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use vo\SaleItem;
use vo\Sale;
use log\Logger;
 
class Zend_Dashboard_Model_Observer
{
 
  public function logSale(Varien_Event_Observer $observer)
  {
 
    $session = Mage::getSingleton('checkout/session');
    $orderId = $session->getLastOrderId();
    $order = Mage::getModel('sales/order')->load($orderId);
    $items = $order->getAllItems();
 
    $sale = new Sale();
    $sale->date = time();
    $sale->total = $order->getTotalDue();
    foreach ($items as $item) {
      $sItem = new SaleItem();
      $sItem->name = $item->getData('name');
      $sItem->price = $item->getData('price');
      $sItem->qty	 = $item->getData('qty_ordered');
      $sale->items[] = $sItem;
    }
    $logger = new Logger();
    $logger->registerSale($sale);
  }
}

 

The Logger class that it calls is where the messaging occurs.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
namespace log;
 
use vo\Sale;
 
class Logger
{
 
  private static $queues = array();
 
  /**
  *
  * Retrieves a queue object based on the name
  * @param string $queue
  * @return \Zend_Queue_Adapter_Activemq
  * @throws Exception
  */
 
  public function getQueue($queue)
  {
    if (!isset(self::$queues[$queue])) {
      $config = new \Zend_Config_Ini(__DIR__ . '/../../etc/config.ini');
      if (!isset($config->mq->$queue)) {
        throw new Exception('Undefined queue configuration');
      }
 
      $stomp = new \Zend_Queue_Adapter_Activemq(array());
      $stomp->setQueue(
        new \Zend_Queue(
          array(
            'name'=> $config->mq->$queue->endpoint
          )
        )
      );
 
      self::$queues[$queue] = $stomp;
 
    }
    return self::$queues[$queue];
  }
 
  public function registerSale(Sale $sale)
  {
    $queue = $this->getQueue('sales');
 
    $date = new \Zend_Date();
    $date->setTimestamp($sale->date);
 
    $output = sprintf(
      "%s Total: \$%s Items: %d",
      $date->toString(\Zend_Date::DATETIME_MEDIUM),
      $sale->total,
      count($sale->items)
    );
    // Send notification
    $queue->send($output);
  }
}

 

The workings of the logger and the configuration are described in yesterday’s post.  For this notification all I needed was a simple text message stating that a purchase was made and how much.  I want to figure out how to send an AMF serialized object to a Flash front end but I haven’t had time to do that yet.  So a simple text message will do.

Once the message is in the queue we now need to retrieve it in our Flex application.  I used AS3Stomp for the connection.  What I did was create a VGroup which could pretty much be inserted on any visual component. In there I created a list element that would be populated with the individual messages that were received from the queue.  The data provider for the list is a simple array of the individual messages which had been received.

<?xml version="1.0" encoding="utf-8"?>
<s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    width="100%" height="100%" horizontalAlign="center"
    creationComplete="vgroup1_creationCompleteHandler(event)"
    xmlns:stomp="org.codehaus.stomp.*">
    <fx:Script>
    <![CDATA[
    import flashx.textLayout.elements.ListElement;
    import mx.events.FlexEvent;
    import org.codehaus.stomp.Stomp;
    import org.codehaus.stomp.event.MessageEvent;
    import valueObjects.Sale;
 
    protected function vgroup1_creationCompleteHandler( event : FlexEvent ) : void {
      stomp.connect();
      stomp.subscribe("/queue/sales");
    }
 
    protected function stomp_messageHandler( event : MessageEvent ) : void {
      var str : String = event.message.body.readUTFBytes(event.message.body.length);
      var data : Object = new Object;
      data.label = str;
      salesMessages.addItem(data);
    }
  ]]>
 
  </fx:Script>
  <fx:Declarations>
    <stomp:Stomp id="stomp" message="stomp_messageHandler(event)" />
    <s:ArrayCollection id="salesMessages" />
  </fx:Declarations>
 
  <s:Label text="Sales"/>
  <s:List id="saleList" width="100%" height="100%" color="#000000" dataProvider="{salesMessages}" fontSize="13"></s:List>
 
</s:VGroup>

The stomp object was provided as a declaration with a method associated with it’s “message” event which is thrown every time a new message comes in.  When a new message comes in the event is fired and the messageHandler() method is called where we read the message data into a string and attach it to an object with a label property.  Then we add it to the data provider at which point the list will update itself with the new value.

Easy.

Details on building a Flex-based dashboard with a PHP backend

Yesterday (June 1st) I wrote a blog post on how to set up an example application that I did for a webinar with Adobe and building a Flash based dashboard application (I would suggest watching it) connecting into to PHP.  Today I would like to provide some of the details of how I did all that.  I was a bit pressed for time while building the example app, and I did my standard thing, taking the Mythbusters motto to it’s logical conclusion.  But in the end I think it worked out well.  The example is complicated but a dashboard is intended to aggregate data from multiple different places and so while it was complex I also think that the attendees were able to see how and why data was processed as it was.

The dashboard application was a Flex-based app that could get summary information and batched information from a Magento based e-commerce site.  It would display a traffic summary, slightly delayed sales information (which products were selling best) and realtime notification of sales that occurred.

The way it worked was that all of the information that was going to be displayed in the dashboard was sent into one of three message queues.  That’s right.  The PHP application did not use a database. Well, it did, later on, but the actual registering of data was not done in the standard PHP way of doing a database insert.

Heresy!  No, actually I think there are a lot of problems that PHP developers solve with SQL that should be solved with message queues.  In this case, there were three things that I wanted.  I wanted traffic information, notification of a sale and the details of that sale.  But I really didn’t need to keep that information once it was processed and I didn’t care about ACID compliance or anything like that.  I basically wanted to throw data somewhere where I could get it later.  That’s where the message queue came in.

I chose ActiveMQ as my message queue.  I tried RabbitMQ but I ran into authentication issues that I didn’t have time to solve.  It was quicker to install ActiveMQ.  ActiveMQ has a feature that it shares with RabbitMQ and that is the Stomp interface.  Most message queues have some kind of binary protocol that they use to initiate communication.  But Stomp is a text based protocol.  What that means is that it is a) easy to build adapters for, and b) very easy to hack if you need one built for you.

Zend Framework has a Stomp interface, which requires some messing around with, but it also has an ActiveMQ interface that uses Stomp.  What that means is that it uses the Stomp interface but makes sure that it’s working with ActiveMQ with any “special” things that needs to be done.  ActionScript, the Flash-based programming language, also has a Stomp based interface available via a third party.  What this meant is that I could connect to a message queue and both the front end and the back end were able to interact with it.

What I wanted to do was send data into the queue that I was either going to a) retrieve directly using the dashboard, or b) pass to a PHP job for batch processing.  Messages are simple unstructured text.  A) was simple because I was just passing a notification.  B) was more problematic because I wanted to pass structured data, i.e. a log object.  But wait!  A serialized object is unstructured text!  So passing the object was no problem.  To do this I created a logger class that understood a few different classes.  We’ll start with those.

First the LogItem class

1
2
3
4
5
6
7
8
namespace log;
 
class LogItem
{
public $title;
public $url;
public $timestamp;
}

Now let’s take a look at the Logger class. (I am omitting the sales portion)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
namespace log;
 
class Logger
{
 
private static $queues = array();
 
public function getQueue($queue)
{
    if (!isset(self::$queues[$queue])) {
        $config = new \Zend_Config_Ini(__DIR__ . '/../../etc/config.ini');
        if (!isset($config->mq->$queue)) {
            throw new Exception('Undefined queue configuration');
        }
 
        $stomp = new \Zend_Queue_Adapter_Activemq(array());
        $stomp->setQueue(
            new \Zend_Queue(
                array(
                   'name'=> $config->mq->$queue->endpoint
                )
            )
        );
 
        self::$queues[$queue] = $stomp;
 
    }
    return self::$queues[$queue];
}
 
public function log($queue, LogItem $log)
{
    $this->getQueue($queue)->send(serialize($log));
}
}

 

This class works in conjunction with a config file like this

1
2
3
4
mq.traffic.endpoint = /queue/traffic
mq.traffic.scheme = tcp
mq.traffic.host = localhost
mq.traffic.port = 61613

The Logger object receives a request to log a LogItem object on a specific queue.  It then calls getQueue() which looks to see if a configuration exists for that queue.  If it does it creates a new ActiveMQ adapter object, sets its queue name and returns it.  Then the log() method serializes the $log object and passes it to the send() method of the ActiveMQ.

At this point the data is in the message queue and you’re done, right?

Nope.  Now you need to pull it out of the queue.  On the PHP side it is pretty much the same as queuing it.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$stomp = new \Zend_Queue_Adapter_Activemq(array());
$stomp->setQueue(
  new \Zend_Queue(
    array(
      'name'=> $config->mq->$queue->endpoint
    )
  )
);
 
while (true) {
  $msgs = $stomp->receive();
 
  foreach ($msgs as $msg) {
    /* @var $msg Zend_Queue_Message */
    $logItem = unserialize($msg->body);
    // Do your thing
  }
}

 

In here we create our connection to the queue daemon and state the queue name.  Then, in this loop, we state that we want to receive data from the queue by calling the receive() method.  Generally a message queue will allow a connection to sit idle indefinitely until there is data on the queue.  But for PHP, since most are not long running programs, you will want to have a timeout.  Leaving the first argument null on the receive() call will set the timeout to be the default, but you probably should set it relatively low.  The receive() method will return an object of Zend_Queue_Message_Iterator which implements Iterator which means it can be used in a foreach loop.  Each item in the object returned will be an instance of Zend_Queue_Message which contains your data along with some meta data.  I didn’t care about the meta data so all I did was unserialized the body of the message which then gives the instance of the object that I had passed in to the queue earlier.

That covers about 1/8th of what I still have left to talk about concerning the webinar I did with Adobe.  But one of the more interesting things I did was use a message queue to communicate between a front end PHP application, a back end PHP application and a front end Flex application (which I didn’t get to).  I personally think that PHP developers should spend a little time looking at asynchronous communication and examining whether their applications could use it.  Perhaps this will help you get on your way.