Solar 0.21 and 0.22 released in quick succession
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.)