A Case of PHP Annotations

Posted May 7, 2010

I recently had the need for a simple ORM layer for a custom CMS framework I work in. I didn't want the typical huge ORM, such as Doctrine - just something small and simple.

Coming from a J2EE background, I learned to like annotations and wanted my PHP ORM to be similar. After searching (unsuccessfully) to find a good annotation library for PHP, I ended up writing my own, and made my ORM use it. I was happy, it did what I wanted, and all was well in the world.

Then yesterday I was given a webapp to work on, which was written in CakePHP (which I believe uses Doctrine), and so had a chance to look at their models.

Although the data represented is completely different, take a look at the two code snippets below.

My annotation-based model:

/**
 * 
 * @Table("website_content")
 */
class WebsiteContent extends ModelBase {

    /**
     *
     * @var int
     * @Id(autoincrement=true)
     * @Column(name="website_content_id", type=TYPE_INTEGER, nullable=false)
     */
    protected $id;

    /**
     *
     * @var Website
     * @BelongsTo(columnName="website_content_website_id", type=TYPE_INTEGER, parentModel="Website")
     */
    protected $website;

}

CakePHP model:

class User extends AppModel {
    var $name = 'User';
    
    var $virtualFields = array(
            'FullName' => "CONCAT(User.Name, ' ', User.LastName)"
    );
    
    var $displayField = 'FullName';
    var $validate = array(
            'Name' => array(
                            'notempty' => array(
                                            'rule' => array('notempty'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
            ),
            'LastName' => array(
                            'notempty' => array(
                                            'rule' => array('notempty'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
            ),
            'Phone' => array(
                            'notempty' => array(
                                            'rule' => array('notempty'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
            ),
            'username' => array(
                            'alphanumeric' => array(
                                            'rule' => array('alphanumeric'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
                            'notempty' => array(
                                            'rule' => array('notempty'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
                            'minlength' => array(
                                            'rule' => array('minlength', 5),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
                            'unique' => array(
                                            'rule' => 'isUnique',
                                            'message' => 'This username has already been taken.'
                            )
            ),
            'Email' => array(
                            'email' => array(
                                            'rule' => array('email'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
                            'notempty' => array(
                                            'rule' => array('notempty'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            )
            ),
            'password' => array(
                            'notempty' => array(
                                            'rule' => array('notempty'),
                                            'message' => 'Please define a password',
                                            //'allowEmpty' => false,
                                            //'required' => false,
                                            //'last' => false, // Stop validation after this rule
                                            'on' => 'create' // Limit validation to 'create' or 'update' operations
                            ),
                            'alphanumeric' => array(
                                            'rule' => array('alphanumeric'),
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            ),
            ),
            'RepeatPassword' => array(
                            'notempty' => array(
                                            'rule' => array('notempty'),
                                            'message' => 'Please define a password',
                                            //'allowEmpty' => false,
                                            //'required' => false,
                                            //'last' => false, // Stop validation after this rule
                                            'on' => 'create' // Limit validation to 'create' or 'update' operations
                            ),
                            'match' => array(
                                            'rule' => array('CheckPasswordMatch'),
                                            'message' => 'Passwords did not match'
                            //'message' => 'Your custom message here',
                            //'allowEmpty' => false,
                            //'required' => false,
                            //'last' => false, // Stop validation after this rule
                            //'on' => 'create', // Limit validation to 'create' or 'update' operations
                            )
            )
            
    );
    //The Associations below have been created with all possible keys, those that are not needed can be removed
    
    var $hasMany = array(
            'Client' => array(
                            'className' => 'Client',
                            'foreignKey' => 'user_id',
                            'dependent' => false,
                            'conditions' => 'Client.active = 1',
                            'fields' => '',
                            'order' => '',
                            'limit' => '',
                            'offset' => '',
                            'exclusive' => '',
                            'finderQuery' => '',
                            'counterQuery' => ''
            ),
            'Note' => array(
                            'className' => 'Note',
                            'foreignKey' => 'user_id',
                            'dependent' => false,
                            'conditions' => '',
                            'fields' => '',
                            'order' => '',
                            'limit' => '',
                            'offset' => '',
                            'exclusive' => '',
                            'finderQuery' => '',
                            'counterQuery' => ''
            )
    );
    
    var $hasOne = array(
            "Aro" => array(
                            "className" => "Aro",
                            "foreignKey" => "foreign_key",
                            "conditions" => "Aro.model = 'User'"
            ),
            "Aco" => array(
                            "className" => "Aco",
                            "foreignKey" => "foreign_key",
                            "conditions" => "Aco.model = 'User'"
            )
    );
    
    
    function CheckPasswordMatch($data) {
        return $this->data['User']['password'] == Security::hash($this->data['User']['RepeatPassword'], null, true);
    }
}

Okay, now I do know that these are 2 different structures being represented here, and the second one is doing a lot more work, but I think I am on to something here. Using associative arrays within associative arrays to express relatively simple information seems ... bloated.

I'd be willing to admit that perhaps the developer who wrote this particular model wasn't especially gifted at model design, so I'm open to discussion on this, but does anyone else feel like me - that annotations would actually be a welcome addition to PHP?

Comments

Yes, I would be interested in collaborating with you on this.

I would be very interested in seeing how these were parsed!

Displaying all 2 comments

Add comment

Visit my Friends and Family

If you've enjoyed my site, please take a moment to visit my friends and family, many of whom have some interesting insights, and entertaining thoughts and ideas.