Tudor Barbu's blog

Ramblings about software development

I wrote yesterday an article introducing Cosmin’s blog. His most recent article is on multiple inheritance and how easy it would be to implement that into weakly typed like PHP. Just have a look at the article.

Having learned OOP in Java, I am generally against multiple inheritance. I believe that if the answer is multiple inheritance, you’re asking the wrong question. But, for “academic” reasons, I tried to implement multiple inheritance in PHP. And I came up with the following abomination.

First, create a parent class from which all classes in need of multiple inheritance extend. The parent classes will be placed in the $_parents array as strings. When the main object is instantiated, the constructor creates objects from all the parent classes. And by using __call() I’m just going to redirect method calls on the main object to the first suitable parent object. I know it sounds complicated, so long story short, just read the code! I’m a developer, not a writer!

/**
 * Abomination class implementing multiple inheritance via
 * __call magic method
 */
abstract class MultipleInheritance
{
    /**
     * List of parent classes
     *
     * @var array
     * @access protected
     */
    protected $_parents = array();    

    /**
     * List of parent objects - generated automatically
     *
     * @var array
     * @access private
     */
    private $_parentObjects = array();

    /**
     * Constructor (thank you captain Obvious)
     *
     * - init the parent objects
     *
     * @access public
     */
    public function __construct()
    {
        foreach ($this->_parents as $parentClass) {
            $this->_parentObjects []= new $parentClass();
        }
    }

    /**
     * __call magic method
     *
     * @param string $name
     * @param array $arguments
     * @access public
     * @return mixed
     */
    public function __call($name, array $arguments)
    {
        foreach ($this->_parentObjects as $object) {
            if (method_exists($object, $name)) {
                return call_user_func_array(array($object, $name), $arguments);
            }
        }

        throw new Exception('No such method: ' . $name);
    }
}

Now, some very sexist implementations of Mom and Dad classes:

class Dad
{
    /**
     * Can parallel park
     *
     * @access public
     * @return void
     */
    public function park()
    {
        echo 'Look, I can parallel park' . PHP_EOL;
    }    

    /**
     * Can ground the kids
     *
     * @access public
     * @return void
     */
    public function ground()
    {
        echo 'Grounded by DAD' . PHP_EOL;
    }
}

class Mom
{
    protected $_parents = array('GrandMa');

    /**
     * Makes sandwiches
     *
     * @access public
     * @return void
     */
    public function makeSandwich()
    {
        echo 'Just making a sandwich' . PHP_EOL;
    }    

    /**
     * Can grounds the kids
     *
     * @access public
     * @return void
     */
    public function ground()
    {
        echo 'Grounded by MOM' . PHP_EOL;
    }
}

…and finally the child class:

class Child extends MultipleInheritance
{
    protected $_parents = array('Dad', 'Mom');
}

Let’s try it out:

$child = new Child();
$child->park();
$child->makeSandwich();
$child->ground();

And it works. And when two parent classes have the same method – ground() in this case – the method belonging to the first encountered class is executed. And voila, multiple inheritance in PHP. Well, sort of.

Now there’s a small problem with the instanceof operator. And by small problem I mean it doesn’t work and there’s no way to fix it. The following code

if ($child instanceof Mom) {
    echo get_class($child) . ' class inherits from Mom' . PHP_EOL;
} else {
    echo get_class($child) . ' class does not inherit from Mom' . PHP_EOL;
}

will always go on the “else” branch, although in my application’s logic, the Child class extends from the Mom class. And since PHP doesn’t allow overloading the operators, there’s no clean way to do this. Of course, there’s a hacky way around it, by adding an isInstanceOf() method to the MultipleInheritance class:

abstract class MultipleInheritance
{
    // ...

    /**
     * Check inheritance
     *
     * @param string $class
     * @access public
     * @return bool
     */
    public function isInstanceOf($class)
    {
        if (in_array($this->_parents, $class)) {
            return true;
        }

        foreach ($this->_parentObjects as $parent) {
            if ($parent instanceof MultipleInheritance) {
                if ($parent->isInstanceOf($class)) {
                    return true;
                }
            }
        }

        return false;
    }
}

…and using it to check inheritance:

if ($child->isInstanceOf('Mom')) {
    echo get_class($child) . ' class inherits from Mom' . PHP_EOL;
} else {
    echo get_class($child) . ' class does not inherit from Mom' . PHP_EOL;
}

But the abomination is not completed yet. Because I used method_exists() instead of is_callable(), magic methods in the parent class will not be detected. To fix this, just change the code from:

if (method_exists($object, $name)) {
    return call_user_func_array(array($object, $name), $arguments);
}

…to…

if (is_callable($object, $name)) {
    return call_user_func_array(array($object, $name), $arguments);
}

…and it will work. At last, the abomination is ready. Doctor Frankenstein would be proud! Here is a gist with the entire example: https://gist.github.com/1652646.

PS: DO NOT USE MULTIPLE INHERITANCE IN REAL LIFE APPLICATIONS!

20 Jan

New kid on the block

Posted by Tudor. Tags: ,

Meet Cosmin. He’s my dev partner at StoreBeez. He’s a software developer who recently turned to the light side of the Force by making the switch from .NET and other M$ technologies to PHP and open source.

While he still uses Windows – which bears testimony of his former allegiance with the evil empire – he’s moving up the ladder and started a blog on PHP and other FLOSS technologies. Check him out.

As some of you might already know, I’m working on my latest project, StoreBeez, which is a virtual mall where independent businesses and artisans can open an online store fast and easy, without having to pay any upfront costs.

The products are also displayed on our frontpage. And since I don’t want to promote a given store – all out stores are equal – displaying the products in random order seemed like a good idea. Retrieving random results is simple, just use MySQL’s rand() function and make the queries look like:

SELECT * FROM `table` ORDER BY RAND();

Which works. Every time the user reloads the page, different results are shown. The problem arises when I’m trying to paginate. Every request means a new random order and sometimes rows get displayed multiple times – on different pages – or simply get “lost”.

The solution is to keep the same order between two consecutive requests. To do this, just pass a seed to the random number generator:

SELECT * FROM `table` ORDER BY RAND({seed});

…where {seed} is a number kept in session. This way, the order is kept between requests. Given that I use Zend Framework as my “weapon of choice”, I will post a ZF solution below:

In the controller:

$page = intval($this->_getParam('page'));
$page = $page ?: 1;

if ($page == 1) {
    $namespace = new Zend_Session_Namespace('random_key');
    $namespace->key = time();
}

$productsTable = new My_Table_Products();
$products = $productsTable->fetchPaginator();
$products->setItemCountPerPage(10)
         ->setCurrentPageNumber($page);

And in the table:

class My_Table_Products
{
    // ...

    public function fetchPaginator()
    {
        $namespace = new Zend_Session_Namespace('random_key');
        $select = $this->select()
                       ->order('RAND(' . $namespace->random_key . ')');

        $paginator = Zend_Paginator::factory($select);

        return $paginator;
    }
}
10 Nov

Zend Framework 2.0

Posted by Tudor. Tags: ,

Looks promising

Uploadify is an awesome script and it works like a charm. But – there’s always a but – sometimes it throws a mysterious 302 error. This happened to me all day long and it drove me crazy. Well, not really, I was already crazy :) So, what to do when the HTTP 302 error pops? A quick look over HTTP statuses should point the me in the right direction:

The requested resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field.

The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).

If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

from here. In simple English, that means a redirect. So what happens!? For security reasons, I turned on the cookie-httponly setting and the client-side script was unable to access the cookies and pass the session id back to the server-side script, which in term would see this connection as coming from an non-authenticated user and issue a redirect to the login page. Thus the mysterious 302 status.

The problem can be solved really easy, by turning the cookie-httponly setting off for the entire application. If that’s not desirable, there’s a more complicated solution. First, Uploadify must send the session id to the server together with the file:

$('#fileUpload').uploadify({
    'uploader'   : '/uploadify/uploadify.swf',
    'script'     : '/images/upload/',
    'cancelImg'  : '/uploadify/cancel.png',
    'auto'       : true,
    'fileExt'    : '*.jpg;*.gif;*.png',
    'fileDesc'   : 'Image Files',
    'sizeLimit'  : 2097152,
    'scriptData' : {'sid' : '<?=Zend_Session::getId();?>'},
    onComplete   : function(event, id, fileObj, response, data) {
        // bla bla bla
    }
}

…then, turn off the auto-start in the application.ini file:

phpSettings.session.strict	= "On"

…and in the Bootstrap.php file:

protected function _initSession()
{
	if (isset($_POST['sid'])) {
		Zend_Session::setId($_POST['sid']);
	}

	Zend_Session::start();
}

Of course, there are some security issues with both approaches, but nothing serious. Took me about 2 hours to figure it out :(

18 Apr

ZF module for IndexTank

Posted by Tudor. Tags: , , ,

I like the way open source works. For example, my former colleague and manager started his own company that produces customer support and ticketing software called Helpdesk and one of the challenges he had to solve was building a reliable search system that will allow his users to search through thousands of tickets.

And since we all know that providing a reliable search system is not an easy job, a decision was made to outsource the searching features to IndexTank, a company that provides scaled real-time search. Here’s where the open source part comes in: since there wasn’t any suitable Zend Framework component for integration with IndexTank, Alex decided to write his own and open-source it on Github. It’s really cool and well documented. Deutschland uber alles :)

Also check out the post on IndexTank’s blog and Helpdesk‘s website.

25 Jan

Forward POST data

Posted by Tudor. Tags: , ,

I was looking today for a way to gracefully forward a POST request from one page to another in PHP. By “forwarding the request” I mean redirecting the user to another page on a different domain with all the POST data intact. I want to stress on the definition, because most people understand “use cURL to create a POST request from PHP”. Being on a different domain, it kind of rules out cookies and sessions.

The proper way to do it is by issuing a HTTP 307 – Temporary redirect header – which will instruct the browser to resend the data to the new URI, indicated by the Location field of the response.

header('HTTP/1.1 307 Temporary Redirect');
header('Location: new-location.php');

Just for the record, in pre-HTTP 1.1 browsers the POST data gets converted to GET, but if your users don’t use computers from the 90s, this won’t be a problem. And it works like a charm. In Chrome and Internet Explorer.

In Firefox and Opera, the browser will pop-up a dialog, informing the user that the data is being redirected to another page and asking the user’s permission to continue. Which is an extremely stupid standard behavior. Yes, it’s in the standard!

If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

…straight from the HTTP’s status codes bible, RFC 2616. Why do I say it’s a stupid specification? Because it is! It makes no sense to pop-up a dialog that might confuse the user when the data was already sent. For example, if I’m a malevolent webmaster, I already have the user’s data, so at this point displaying the dialog box is useless. I can save the data in my database and have a cronjob connect to the new page or do whatever I please with the POSTed information. However, if the redirect is legitimate, displaying the message box might confuse the user and have him cancel the request, which might break the application’s logic.

Great work guys, I’m moments away from converting the POST data into GET and send it in the URL…

One of the strong assets of Zend Framework is its rich built in package of validators. It has validators for just about anything, from simple email addresses and digits to credit card numbers and database rows. Yet, sometimes things go wrong and they need to be hacked back on track. Today’s problem was with the email address validator that sometimes generates some very user-unfriendly error messages. Like such:

‘bad.email’ is no valid hostname for email address ‘testing@bad.email’
‘bad.email’ does not match the expected structure for a DNS hostname
‘bad.email’ appears to be a local network name but local network names are not allowed

For the average user, these messages can be very confusing and sometimes is much better to just show an easy to understand “Please enter a correct email” message. How hard can that be with Zend Framework!?! Well…let’s say it’s not as simple as it looks.

The obvious thing to do is to add custom error messages (basically adding the same message over and over again). Like such:

$email->setOptions(
    array(
        'label'      => 'Email',
        'required'   => true,
        'filters'    => array(
            'StringTrim',
            'StripTags',
        ),
        'validators' => array(
            array(
                'EmailAddress',
                true,
                array(
                    'allow' => Zend_Validate_Hostname::ALLOW_DNS,
                    'domain' => true,
                    'mx' => true,
                    'deep' => true,
                    'messages' => array(
                        Zend_Validate_EmailAddress::INVALID => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_FORMAT => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_HOSTNAME => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_MX_RECORD => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_SEGMENT => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::DOT_ATOM => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::QUOTED_STRING => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_LOCAL_PART => 'Please enter a correct email'
                    ),
                ),
            ),
        ),
    )
);

But it won’t work. That’s because Zend_Validate_EmailAddress calls Zend_Validate_Hostname internally, so custom error messages should be added also for the Zend_Validate_Hostname validator.

$email->setOptions(
    array(
        'label'      => 'Email',
        'required'   => true,
        'filters'    => array(
            'StringTrim',
            'StripTags',
        ),
        'validators' => array(
            array(
                'EmailAddress',
                true,
                array(
                    'allow' => Zend_Validate_Hostname::ALLOW_DNS,
                    'domain' => true,
                    'mx' => true,
                    'deep' => true,
                    'messages' => array(
                        Zend_Validate_EmailAddress::INVALID => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_FORMAT => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_HOSTNAME => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_MX_RECORD => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_SEGMENT => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::DOT_ATOM => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::QUOTED_STRING => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::INVALID_LOCAL_PART => 'Please enter a correct email',
                        Zend_Validate_EmailAddress::LENGTH_EXCEEDED => 'Please enter a correct email',
                        Zend_Validate_Hostname::CANNOT_DECODE_PUNYCODE => 'Please enter a correct email',
                        Zend_Validate_Hostname::INVALID => 'Please enter a correct email',
                        Zend_Validate_Hostname::INVALID_DASH => 'Please enter a correct email',
                        Zend_Validate_Hostname::INVALID_HOSTNAME => 'Please enter a correct email',
                        Zend_Validate_Hostname::INVALID_HOSTNAME_SCHEMA => 'Please enter a correct email',
                        Zend_Validate_Hostname::INVALID_LOCAL_NAME => 'Please enter a correct email',
                        Zend_Validate_Hostname::INVALID_URI => 'Please enter a correct email',
                        Zend_Validate_Hostname::IP_ADDRESS_NOT_ALLOWED => 'Please enter a correct email',
                        Zend_Validate_Hostname::LOCAL_NAME_NOT_ALLOWED => 'Please enter a correct email',
                        Zend_Validate_Hostname::UNDECIPHERABLE_TLD => 'Please enter a correct email',
                        Zend_Validate_Hostname::UNKNOWN_TLD => 'Please enter a correct email',
                    ),
                ),
            ),
        ),
    )
);

Pretty long. Yet still not working. But it is somewhat better – as no creepy error messages are being displayed – instead the same message appears multiple times. This is not a validator chain, so the programmer can’t force the breaking of the chain after the first failed test :(

In the end, after few hours of digging through the code and the docs, I came up with my own solution, which still harnesses the power of the original Zend_Validate_EmailAddress validator while providing a way to add a simple “Please enter a correct email” message. One time :) . It uses the Zend_Validate_Callback class and PHP 5.3 anonymus functions:

$email->setOptions(
    array(
        'label' => 'Email',
        'required' => TRUE,
        'filters' => array(
            'StringTrim',
            'StripTags',
        ),
        'validators' => array(
            array(
                'Callback',
                true,
                array(
                    'callback' => function($value) {
                        $validator = new Zend_Validate_EmailAddress(
                            array(
                                'allow' => Zend_Validate_Hostname::ALLOW_DNS,
                                'domain' => true,
                                'mx' => true,
                                'deep' => true,
                            )
                        );

                        return $validator->isValid($value);
                    },
                    'messages' => array(
                        Zend_Validate_Callback::INVALID_VALUE => 'Please enter a correct email',
                    ),
                ),
            ),
        ),
    )
);

It might seem a little far fetched, but it works :P

This is a post I’ve been wanting to write for a while but I didn’t quite get the time to do it. It’s about me finally taking the ZF certification exam. I wanted to take this certification for over an year and a half, but first I postponed it because I was learning python and working with Django and I didn’t have the time to actually look over Zend Framework’s manual and read it. Then I moved to Spain, got caught up with other problems and so on, and so on…

Thing is that I bought a voucher for this exam about an year ago, and, as most Zend issued vouchers, it only lasts one year. So, few weeks ago I found myself with an already paid non-refundable voucher bound to expire. Since I had nothing to lose, I decided to take my chances and take the exam. And I did :) Hooray for me!

About the exam

The Zend Framework is much more “to the point” than the PHP 5 and it’s a piece of cake for the seasoned Zend Framework developer. No more “which comes first: the needle or the haystack?” questions. Everything is focused on asserting the fact that the candidate has the knowledge required to be productive using ZF. The topics cover a large part of the ZF manual, with focus on commonly used features. This doesn’t mean that you shouldn’t pay attention to more “exotic” topics like Zend_Memory, Zend_TimeSync or Zend_Wildfire. Although the test refers to version 1.5 of Zend Framework – the current version is 1.11 – there’s nothing specific to that version and the questions refer to those features that are common throughout all the versions (MVC, coding standards, plugins, validator chains, etc).

PS: don’t email me asking for the questions to the test! Go study…

31 Aug

PHP switch

Posted by Tudor. Tags: , ,

Over the years, I’ve encountered a lot of strange things in PHP, but this one is off the scale:

$value = 'zero';

switch ($value) {
    case 0:
        echo 'value is zero';
        break;
    default:
        echo 'value is not zero';
        break;
}
echo PHP_EOL;

What do you think that the code above will print? Well…I’ll spare you the hassle and give you the answer:

% php test.php
value is zero

This weird behavior originates in the fact that PHP uses the equality operator (==) instead of the identity (===) one when evaluating expressions inside a switch statement. It’s basically the same as:

if ('foo' == 0) {
    echo 'bar';
}

…which also yields unexpected results. I hate this type of casting!!!