Flourish PHP Unframework
This is an archived copy of the forum for reference purposes

Allow overloading of relationship getters

posted by marcus 9 years ago

I have no idea if this is possible with the way things are built or how much time it would take, but allowing overloading of the buildRelationship() functions would be really awesome. This would essentially mean that you could send in the same arguments as in fRecordSet::build() minus the class since that's already set.

I'm not sure exactly what you wan't to achieve - post some code :)

But you can do something like this:

class A /* The fActiveRecord class */
{
	public function __call($method_name, $parameters)
	{
		list($action, $subject) = fORM::parseMethod($method_name);
		echo __CLASS__ . ' is ' . $action . 'ing ' . $subject . '<br/>';
	}
	
}

class B extends A /* Your active record class */
{
	public function __call($method_name, $parameters)
	{
		list($action, $subject) = fORM::parseMethod($method_name);
		echo __CLASS__ . ' is ' . $action . 'ing ' . $subject . '<br/>';
		parent::__call($action . 'Plans', $parameters);
	}
}

$b = new B;
$b->buildRoads();

// B is building roads
// A is building plans
posted by mblarsen 9 years ago

I was primarily thinking of being able to restrict and sort the data you get back from it in the same way that you can when you do a normal fRecordSet::build().

As the class name is already given that can be excluded but being able to do for example

$b->buildRoads(array("road_length>" => 5), array("road_length" => "asc"));

Assuming of course that there is a road_length column and it's an INT. Right now if you want the data returned by the relationship getter to be sorted you have to do that through fORMRelated::setOrderBys() which of course works but there's no way to put in a WHERE clause or limiting the number of returned records.

posted by marcus 9 years ago

The reason such functionality does not exist is for the sake of clarity. When calling a build{RelatedRecords}() method on an fActiveRecord, it will return all related records for the related class. Now, due to things like populate{RelatedRecords}(), the related records may or may not be coming from the database. In addition, if the related records have been retrieved from the database already, they are cached to prevent excess database calls. Because of this, there is no way to allow for the exact same functionality as fRecordSet::build().

You can use fRecordSet::filter() on the returned record set, however that may be inefficient if you have many related records and may not provide all of the same functionality as the SQL-based build.

I've considered changing the API so that if an array of conditions and order bys is passed, that way a SQL statement will always be executed, but then it can get confusing. Instead of knowing that all of the related records are returned, you would be pulling from two totally different sources and could get very different results. Imagine calling a build with conditions and getting 5 records, but then calling build without conditions and getting no records.

I have to be honest, I also haven't spent too much time trying to work this all out due to what I view the benefits of an ORM being. I think one of main benefits of an ORM isn't so much writing less SQL, but encapsulating all of the business rules and making conditionals cleaner. By that, I mean that writing methods to handle slight variations in where clauses ends up creating messy looking concatenation of SQL strings. fRecordSet::build() removes that by encapsulating all of the SQL string concatenation without really abstracting away much of the syntax. fActiveRecord provides a nice container to wrap all of the business logic up into. Thus, when I want to do this sort of thing, I wrap all of the conditionals and fRecordSet::build() operations up into methods on my fActiveRecord classes. Here is an example:

class User extends fActiveRecord
{
    static public function build()
    {
        return fRecordSet::build(__CLASS__);
    }

    static public function buildActive()
    {
        return fRecordSet::build(__CLASS__, array('status=' => 'Active'));
    }
}

So, in your situation I would probably make a method such as:

class City extends fActiveRecord
{
    public function buildRoadsOfLength($length)
    {
        return fRecordSet::build(
            __CLASS__,
            array('road_length>' => $length),
            array('road_length' => 'asc')
        );
    }
}

That said, I know different developers have different styles and many would approach the problem from a different angle. Because of that, I'd be open to hearing ideas about how to handle this situation.

Should I create a prefix other than build for always querying the database, instead of possibly hitting the cache? Should I try to find a way to translate fRecordSet::build() where conditions into fRecordSet::filter() conditions, and use fRecordSet::filter() internally when a record set has already been cached?

Related to this concept, should there be a new method to add one or more new related records, instead of having to list all related records for the associate methods? Should there be a method to remove only one or a few records instead of specifying the full list via associate?

As always, I appreciate any ideas or suggestions.

posted by wbond 9 years ago