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

Namespaced ActiveRecords

posted by mblarsen 9 years ago

Hi I've using namespaces for my model. For example:

namespace My\\Namespace;

class Shop extends \\fActiveRecord {}

This seems to be a problem for tablzation of the class - as the class name is My\\Namespace\\Shop. So I added:

	fORM::mapClassToTable('My\\Namespace\\Shop', 'shops');

This works except for that I would have to add this for all my model classes.

So I tried changing the fORM::tablize() method instead, like this (uses only the last part of the class name):

	static public function tablize($class)
	{
		if (!isset(self::$class_table_map[$class])) {
			$stripped_class = $class;
			$slash_position = strrpos($class, '\\\\');
			if ($slash_position >=0) {
				$stripped_class = substr($class, $slash_position + 1);
			}
			self::$class_table_map[$class] = fGrammar::underscorize(fGrammar::pluralize($stripped_class));
		}
		return self::$class_table_map[$class];
	}

This will work as well, but naturally this will not do well with multiple models of the same name in different namespaces.

Is there a way of setting a default namespace or any other way of getting around this problem?

Any ideas are appreciated :)

Hmm, I overlooked this part, not good.

Both Layout and Page are in the namespace My
Namespace. As you can see Flourish cannot reason from relation name to class name. It seems that

\\fORM::mapClassToTable('My\\Namespace\\Layout', 'layouts');

is not used in determining the class?

[internal function]: fActiveRecord->__call('createLayout', Array)
/.../libs/flourish/fActiveRecord.php(925): fORMRelated::createRecord('My\\Namespace\\Page', Array, Array, 'Layout')
/.../libs/flourish/fORMRelated.php(365): fActiveRecord::validateClass('Layout')
/.../libs/flourish/fActiveRecord.php(739): fActiveRecord::checkClass('Layout')
/.../libs/flourish/fActiveRecord.php(224): class_exists('Layout')
[internal function]: spl_autoload_call('Layout')
[internal function]: autoload('Layout')
/.../src/config.php(100): autoload()
/.../src/config.php(100)
require_once(/.../src/Layout.php): failed to open stream: No such file or directory

Shouldn't fORM:mapClassToTable be used in the case of relations as well? Or really the reverse should be used. Which table is registered with which class. But that naturally gives a problem with single table inheritance (implicit or explicit) . Hmm.

UPDATE : Adding this piece of code to should will enable namespaced support, but it does not solve any single table inheritance issues.

		if (!isset(self::$method_name_cache[$method_name])) {
			list ($action, $subject) = fORM::parseMethod($method_name);
			if (in_array($action, array('get', 'encode', 'prepare', 'inspect', 'set'))) {
				$subject = fGrammar::underscorize($subject);
			} elseif (in_array($action, array('build', 'count', 'inject', 'link', 'list', 'tally'))) {
				$subject = fGrammar::singularize($subject);
			}
   // ==== THIS PART IS NEW ========
			if (in_array($action, array('create', 'build'))) {
				$reflector = new ReflectionClass($this);
				$subject = $reflector->getNamespaceName() . '\\\\' . $subject;
			}
  // ==== END OF NEW STUFF =======
			self::$method_name_cache[$method_name] = array(
				'action'  => $action,
				'subject' => $subject
			);	
		} else {
			$action  = self::$method_name_cache[$method_name]['action'];
			$subject = self::$method_name_cache[$method_name]['subject'];	
		}

This 'fix' only handles create and build. I guess it should handle count, link and others as well. Which ones?

posted by mblarsen 9 years ago

Currently there is not a way to alias classes or provide a default namespace. In your case you are telling it to load the Layout class since the second part of createLayout() is the class name. For 5.2-style namespaces, you can just put the _s in the method, such as createMy_Namespace_Layout(). Unfortunately the 5.3 namespace \\ can't be part of a method name.

Now, for people who use both PHP 5.2 and 5.3 namespaces, I've thought about adding a way to specify a default namespace, however with 5.2, the code end up being a weird mix of prefixed and unprefixed.

// Image a method such as this to set a default prefix
fORM::setDefaultNamespace('Model_');

$user  = Model_User(1);
$group = Model_Group(1);

// It would be weird to have a default prefix here since you have
// to explicitly specify it above when creating an individual record
$users = $group->buildUsers();

For PHP 5.3, this isn't so much of an issue since PHP allows aliasing via the as keyword.

What would your opinion be of adding two methods, fORM::addDefaultNamespace() for general usage and fORM::mapClassToNamespace() for special situations?

posted by wbond 9 years ago

You could also use namespace by default, then you would have to override if you for instance had two or more User models.

You do this by assuming that all related models are of the same namespace. If this is not the case a method like fORM:mapeTableToClass($table, $class_name, $class_column_value=NULL); would be handy.

$reflector = new ReflectionClass($this);
$subject = $reflector->getNamespaceName() . '\\\\' . $subject;

What would the input to fORM::mapClassToNamespace()'s $class value look like? Or more importantly where would this be called. If you call it out of a specific class context, you would not solve the problem of several models by the same name. But if you put it in configure() it may work.

(I know very little of 5.2 namespacing)

posted by mblarsen 9 years ago

Just a follow up for anyone reading this thread:

In r928 (ticket #524), support for namespaced fActiveRecord classes was added. By default the namespaced fActiveRecord classes will ignore their namespace when automatically mapping to a database table. This can obviously be overridden with fORM::mapClassToTable().

// This will work with the table "users" and not "models_users" or "models.users"
namespace Models;

class User extends \\fActiveRecord { }

All methods that accept a related class name as part of the method name will automatically prepend the namespace of the current class to the related class. This means all related fActiveRecord classes should be in the same namespace.

namespace Models;

class User extends \\fActiveRecord { }
class Group extends \\fActiveRecord { }

$user = new User(1);
// This will load the related Models\\Group objects, not
// Group objects since User is in the Models namespace
$groups = $user->buildGroups();
posted by wbond 9 years ago