Solar 0.21 and 0.22 released in quick succession

By | July 21, 2006

I released Solar 0.21.0 yesterday, and a quick followup 0.22.0 today. The rest of this entry covers highlights for changes in both versions. The main highlights are three new classes (Solar_Struct, Solar_Sql_Row, and Solar_Sql_Rowset) along with a backwards-compatibility break to how classes store their default configuration values.

Changes To $_config Use

There is one really big change to the configuration system. It should not require more than a search-and-replace in your files, but it’s still a big deal.

The change is that you now put the default config values for your class in a property called $_{Class_Name} instead of in $_config. (Keep using the $_config property for reading configs, but put the defaults in $_{Class_Name}.) For example …

Previously, you would do this to define a default config key and value:


<?php
    // parent class
    class Vendor_Foo extends Solar_Base {
        protected $_config = array('foo' => 'bar');
        public function getConfig()
        {
            Solar::dump($this->_config);
        }
    }

    // child class
    class Vendor_Foo_Baz extends Vendor_Foo {
        protected $_config = array('baz' => 'dib');
    }
?>

This was great for reading from the config file, but it meant that the parent class config keys were not inherited by the child class. The child $_config overrides the parent $_config entirely; you would have to put some checking code in your parent constructor to make sure all the keys were there. Thus, a call to Vendor_Foo_Baz::getConfig() would show only the ‘baz’ key, and not the combination of the parent ‘foo’ key and the child ‘baz’ key.

In this release, we change from $_config to $_{Class_Name} for default config values.


<?php
    // parent class
    class Vendor_Foo {
        protected $_Vendor_Foo = array('foo' => 'bar');
        public function getConfig()
        {
            Solar::dump($this->_config);
        }
    }

    // child class
    class Vendor_Foo_Baz extends Vendor_Foo {
        protected $_Vendor_Foo_Baz = array('baz' => 'dib');
    }
?>

(Note from getConfig() that the $_config property does not go away, you just don’t put anything in it yourself.)

In the new system, Solar_Base collects all the $_{Class_Name} arrays and merges them all into $_config for you. This means that child classes inherit their parent config keys and values unless you override them in the child class.

Basically, you should keep reading from $_config when getting config values in your class methods. But for property definitions, use $_{Class_Name} for the default config keys and values.

Solar_Struct

The new Solar_Struct class is an ideological cousin to Matthew Weier O’Phinney’s Phly_Hash class (originally Phly_Struct).

When you build a struct instance, you give it a series of keys and values as an array; those keys become properties in the struct object, and the values are used as the property values.


<?php
    $data = array(
        'foo' => 'bar',
        'baz' => 'dib',
        'zim' => 'gir',
    );

    $struct = Solar::factory(
        'Solar_Struct',
        array('data' => $data)
    );
?>

Using this class, you can access data using both array notation ($foo['bar']) and object notation ($foo->bar). This helps with moving data among form objects, view helpers, SQL objects, etc.


<?php
    echo $struct['foo']; // 'bar'
    echo $struct->foo;   // 'bar'
?>

The struct object implements ArrayAccess, Countable, and Iterator, so you can treat it just like an array in many cases.


<?php
    echo count($struct); // 3

    foreach ($struct as $key => $val) {
        echo "$key=$val ";
    } // foo=bar  baz=dib zim=gir
?>

One problem with Solar_Struct is that casting the object to an array will not reveal the data; you’ll get an empty array. Instead, you should use the toArray() method to get a copy of the object data.


<?php
    $array = (array) $struct;    // $struct = array();
    $array = $struct->toArray(); // $struct = array(
                                 //     'foo' => 'bar',
                                 //     'baz' => 'dib',
                                 //     'zim' => 'gir',
                                 // );
?>

One other problem is that double-quote interpolation isn’t as intuitive. If you want to use a struct property in double-quotes, you should wrap it in curly braces.


<?php
    // won't work right, will show "Object #23->foo"
    echo "Struct foo is $struct->foo";

    // will work as expected
    echo "Struct foo is {$struct->foo}";
?>

Solar_Sql_Row

The new Solar_Sql_Row class is extended from Solar_Struct, and adds a new method called save().

From now on, any time you call select(‘row’) or fetch() from an SQL class, you’re going to get a Solar_Sql_Row object instead of an array. Because Solar_Struct acts like both an array and an object, you shouldn’t have to change any of your code to use the new return type.

If you want to, you can start using object notation with rows. For example, instead of $row['id'] you can use $row->id (which is a lot easier to read in most cases).

An additional benefit is that Solar_Sql_Row has a save() method that can be tied back to a table object. Previously, if you wanted to insert or update a row, you had to pass it as an array into its related table object:


<?php
    $row['foo'] = "bar";
    $row['baz'] = "dib";
    $table->save($row);
?>

Now you can save the row directly:


<?php
    $row->foo = "bar";
    $row->baz = "dib";
    $row->save();
?>

Because it is tied back to the source table object, it follows all the validation and insert/update rules of that object, so you don’t need to duplicate them just for the row object.

Solar_Sql_Rowset

The new Solar_Sql_Rowset class also extends from Solar_Struct, but it works as a collection of Solar_Sql_Row objects. You can iterate through it with foreach() and get one row at a time, working with each and calling save() as you go.

From now on, any time you call select(‘all’) or fetchAll() from an SQL class, you’re going to get a Solar_Sql_Rowset object instead of an array. Because Solar_Struct acts like both an array and an object, you shouldn’t have to change any of your code to use the new return type.

User-Defined Row Classes

With Solar_Sql_Row and Solar_Sql_Rowset in place, Solar_Sql_Table now returns these when you call fetch() and fetchAll(). Even better, you can set the new properties $_row_class and $_all_class to a custom class descended from a Row or Rowset; if you do, the fetch*() methods will use those classes as the return objects. This paves the way for the beginning of something like a formal ActiveRecord pattern as described by Martin Fowler (as opposed to the one popularized by Rails.)

8 thoughts on “Solar 0.21 and 0.22 released in quick succession

  1. jules

    According to ‘Changes To $_config Use’, using a property with its _{classname} as config key is certainly a nice approach (reminding me of four’s constructors), also for debugging purposes it might be handy to see where all properties come from; nevertheless, what exactly is the reason not using get_class_vars() in combination with get_parent_class()?

    Reply
  2. pmjones Post author

    Hi Jules –

    The get_class_vars() method returns only the public properties of the class, not the protected ones, and it’s important to me that the config defaults be protected from outside tampering.

    There are other variations to that approach, such as using statics or trying to instantiate the parent class or using the Reflection engine. None of the ones I tried worked quite right or very quickly. I think I attempted almost every reasonable alternative, but I’d be happy to hear of other approaches that might work.

    Although it adds overhead at construction time, and is somewhat inelegant, I think the $_Class_Name approach solves the problem of passing default configs to child classes *and* automated mergiing of added configs nicely.

    Thus, until PHP provides something like get_class_vars() for non-public properties, I think this is the best way to go.

    Reply
  3. jules

    Hi Paul -

    According to some comments on the php site, and ofcourse a microtest, get_class_vars() do return protected variables if it is called inside the class, the only problem I encounter, is the fact that I do not know how to infer the current classname, when calling, for example a static function of the parent class.

    Reply
  4. pmjones Post author

    Hi again Jules — right, inside the class it works as expected; the problem is that even though we’re in a constructor, we’re not technically “in” each of the parent classes, so it doesn’t see the protected values of the parent classes. So it still doesn’t help us. :-(

    And you’re right about the static functions, too; you can’t call (e.g.) $class::methodName() or $class::var_name, or I’d try to use protected statics.

    Other ideas? :-)

    Reply
  5. moraes

    Hey, you. I’m intrigued by the reference to “four’s constructors”. What should I look for?

    Reply
  6. pmjones Post author

    Moraes — He means the constructor naming in PHP4; instead of “public function __construct()” it was “function Class_Name_Here()”.

    Reply
  7. Pingback: PHPDeveloper.org

  8. moraes

    Haha. I’ve searched for “four constructors” and found things about haskell, python or java. I would not imagine php 4′s constructors. :-)

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *