Solar Models vs. Zend Framework Models

Today, Michelangelo van Dam posted this narrative relating his experience with Zend Framework models:

http://www.dragonbe.com/2010/01/zend-framework-data-models.html

I read the article, and wondered how hard it would be to replicate his narrative using the Solar Framework model system. Turns out it's pretty easy: there's a lot of work that Solar does for you.

  1. Download a Solar system, move it to your localhost directory and make sure that the tmp and sqlite directories are accessible to the web server.

    $ wget http://svn.solarphp.com/system/download/solar-system-1.0.0beta2.tgz
    $ tar -zxf solar-system-1.0.0beta2.tgz
    $ cp -r solar/* /var/www/htdocs # or your localhost directory
    $ cd /var/www/htdocs         # or your localhost directory
    $ chmod -R 777 tmp sqlite
    
  2. Create tables and data in sqlite/example.sq3. There are two changes from Michelangelo's code: the table names are plural, and rename the column type_id to address_type_id. (These are easy enough to change in the model class definitions, but I'd like to minimize the amount of customization for this example.)

    CREATE TABLE "users" (
        "id" integer primary key autoincrement,
        "username" varchar(255) not null,
        "password" varchar(255) not null
    );
    
    
    CREATE TABLE "contacts" (
        "id" integer primary key autoincrement,
        "user_id" integer not null,
        "email" varchar(255) not null,
        "phone" varchar(255) null,
        "fax" varchar(255) null
    );
    
    
    CREATE TABLE "addresses" (
        "id" integer primary key autoincrement,
        "address_type_id" integer not null default 1,
        "user_id" integer not null,
        "address1" varchar(255) not null,
        "address2" varchar(255) null,
        "city" varchar(255) not null,
        "state" varchar(255) null,
        "zip" varchar(255) not null,
        "country" varchar(255) not null
    );
    
    
    CREATE TABLE "address_types" (
        "id" integer primary key autoincrement,
        "type" varchar(255) not null
    );
    
    
    -- Data for users table --
    INSERT INTO "users"
        VALUES (1, 'testuser1', 'test123');
    INSERT INTO "users"
        VALUES (2, 'testuser2', 'test234');
    
    
    -- Data for contacts table --
    INSERT INTO "contacts"
        VALUES (1, 1, 'test1@example.com', '1-800-555-1234', '1-800-555-1230');
    INSERT INTO "contacts"
        VALUES (2, 2, 'test2@example.com', '1-800-555-2234', '1-800-555-2230');
    
    
    -- Data for addresses table --
    INSERT INTO "addresses"
        VALUES (1, 1, 1, '1 Test Home', '', 'Testtown', 'ZF', '1234', 'PHP');
    INSERT INTO "addresses"
        VALUES (2, 1, 2, '2 Test Home', '', 'Testtown', 'ZF', '1234', 'PHP');
    INSERT INTO "addresses"
        VALUES (3, 2, 2, 'Test Corp, LTD', '4 Test Ave', 'Testtown', 'ZF', '1234', 'PHP');
    
    
    -- Data for address_types table --
    INSERT INTO "address_types"
        VALUES (1, 'Home address');
    INSERT INTO "address_types"
        VALUES (2, 'Billing address');
    
  3. Make sure the SQLite database is accessible to the web server.

    $ chmod 777 sqlite/example.sq3
    
  4. Make a vendor space for the controllers, models, suppory libraries, etc. For this example the vendor will be named Example.

    $ ./script/solar make-vendor Example
    
  5. Edit the system configuration in config.php. Change the front-controller class prefixes and model catalog class prefixes, and tell the SQLite adapter which file name to use.

    // front controller
    $config['Solar_Controller_Front'] = array(
        'classes' = array('Example_App'),
        // ... the rest of the front-controller array ...
    );
    
    
    // model catalog
    $config['Solar_Sql_Model_Catalog']['classes'] = 'Example_Model';
    
    
    // add sqlite config
    $config['Solar_Sql_Adapter_Sqlite'] = array(
        'name' => "$system/sqlite/example.sq3",
    );
    
  6. Make the model classes. Note that this avoids a ton of effort that Zend Framework requires: Michelangelo had to hand-create table, model, and mapper classes for each of the following single-line commands.

    $ ./script/solar make-model Example_Model_Users
    $ ./script/solar make-model Example_Model_Contacts
    $ ./script/solar make-model Example_Model_Addresses
    $ ./script/solar make-model Example_Model_AddressTypes
    
  7. Edit the model classes to express what the related models are:

    /** source/example/Example/Model/Users.php */
    protected function _setup()
    {
        // chain to parent
        parent::_setup();
    
    
        // relateds
        $this->_hasOne('contact');
        $this->_hasMany('addresses');
    }
    
    
    /** source/example/Example/Model/Contacts.php */
    protected function _setup()
    {
        // chain to parent
        parent::_setup();
    
    
        // relateds
        $this->_belongsTo('user');
    }
    
    
    /** source/example/Example/Model/AddressTypes.php */
    protected function _setup()
    {
        // chain to parent
        parent::_setup();
    
    
        // relateds
        $this->_hasMany('addresses');
    }
    
    
    /** source/example/Example/Model/Addresses.php */
    protected function _setup()
    {
        // chain to parent
        parent::_setup();
    
    
        // relateds
        $this->_belongsTo('address_type');
        $this->_belongsTo('user');
    }
    
    
  8. Make a skeleton app (i.e., a controller with its associated actions and views):

    $ ./script/solar make-app Example_App_Accounts
    
  9. Edit the controller action to fetch user account data:

    /** source/example/Example/App/Accounts.php */
    public $list;
    public function actionIndex()
    {
        // get a model catalog
        $model = Solar_Registry::get('model_catalog');
    
    
        // populate $this->list with all users.
        // eager-fetch the contact record and addresses collection.
        $this->list = $model->users->fetchAll(array(
            'eager' => array(
                'contact',
                'addresses',
            ),
        ));
    }
    
  10. Edit the view for the index action. This could be a view and a partial, but let's keep it straightforward for now.

    /** source/example/Example/App/Accounts/View/index.php */
    <table>
        <tr>
            <th>Username</th>
            <th>E-Mail</th>
            <th>Phone</th>
            <th>Fax</th>
            <th># addresses</th>
        </tr>
    
    
        <?php foreach ($this->list as $user): ?>
    
    
        <tr>
            <td><?php echo $this->escape($user->username); ?></td>
            <td><?php echo $this->escape($user->contact->email); ?></td>
            <td><?php echo $this->escape($user->contact->phone); ?></td>
            <td><?php echo $this->escape($user->contact->fax); ?></td>
            <td><?php echo count($user->addresses); ?></td>
        </tr>
    
    
        <?php endforeach; ?>
    
    
    </table>
    
    

You're done. Browse to http://localhost/index.php/accounts/index to see the results of your handiwork.

Total elapsed time from when I read Michelangelo's article to come up with the Solar conversion and write this article: under two hours.

Solar makes model creation and interaction very easy and very powerful. Maybe your next project should use Solar instead of Zend Framework?

Are you stuck with a legacy PHP application? You should buy my book because it gives you a step-by-step guide to improving you codebase, all while keeping it running the whole time.