Reputation: 1596
Looking to see if there is any way to implement the kind of functionality of the __get()
magic method, but when trying to instantiate a new class.
My goal is to have
$foo = new Cat();
Ultimately result in the creation of a generic object, i.e.
new Mammal('Cat');
Such that $foo
would be an instance of the class Mammal
, with the called class name ('Cat'
) passed as an argument to Mammal
's constructor.
Note: The end game I have in mind, for those familiar with Lithium or CakePHP, is to avoid having to define a whole bunch of classes for each different database table. If I did, most would simply be empty, the basic CRUD operations being all that is necessary. Plus, all those includes and class definitions can't be great for overhead. My idea would be to use a single "Model" class to handle most of the generic functions, and I could always create subclasses if I needed more advanced functionality for a specific database table.
Upvotes: 0
Views: 188
Reputation: 455
I know this is an old question now but still relevant today for a lot of people.
My way of overcoming this exact scenario is to have a base modeler which holds all basic table interaction:
<?php
class base {
/**
* @name $table_values
* @description This holds all data about the table including the field names and data for the record loaded.
* @example {
* "primary_keys" : [],
* "table_data" : {
* "name" : "value",
* "name" : "value"
* }
* }
*/
private $table_values = array();
/**
* @name __get
* @param $name The name of the property to return.
* @description The PHP magic getter.
*/
public function __get($name) {
$keys = array_keys($this->table_values['table_data']);
if (in_array($name, $keys)) {
return $this->table_values['table_data'][$name];
}
}
/**
* @name __set
* @param $name The name of the property to set.
* @param $value The value to assign to the property.
* @description The PHP magic setter.
*/
public function __set($name, $value) {
$this->table_values['table_data'][$name] = $value
}
/**
* @name table_name
* @description Must override in child class. Should return the name of the table to use.
*/
public function table_name() {}
/**
* @name load
* @param $ids int|array Can be a single primary key or an assoc array of primary keys depending on the table with the keys for the array being the field names.
* @description This method handles loading a single record from the table and storing it as a PHP object.
*/
public function load($ids) {
// Use the primary key(s) to find and load the record into $table_values from the database.
}
/**
* @name save
* @description Saves the record in the database
public function save() {
// Use the primary key(s) to find and either insert or update the database table record.
}
}
?>
Use the base class as:
<?php
class person extends base {
public function table_name() {
return "person";
}
}
class car extends base {
public function table_name() {
return "car";
}
}
?>
etc.
Use and autoloader to automatically load classes and to handle when a class doesn't exist for a table:
<?php
spl_autoload_register(function($class_name) {
$filename = "/path/to/class/{$class_name}.php";
if (class_exists($class_name)) {
// Do nothing.
} elesif (file_exists($filename)) {
require_once($filename);
} else {
// Check if the class name exists in the database as a table.
if ($database->table_exists($class_name)) {
$class_def = "
class {$class_name} extends base {
public function table_name() {
return \"{$class_name}\";
}
}";
eval($class_def);
}
}
});
?>
The base class can have a lot more functionality. I also have functions to override in children to do things before and after load and save, and to check for duplicate records with the same data for instance.
Upvotes: 0
Reputation: 316969
The way I remember it from Cake is that you define the model on top of the controller class and then simply use $this->Modelname. This should be as simple to implement as:
public function __get($prop)
{
if(in_array($prop, $this->uses)) {
return new $prop;
}
}
Each time you call a non-existing property, your class would check if the property name exists in an array $uses and if so, assumes $prop is classname and instaniates it. You will want to store the instance somewhere to avoid reinstantiating each time you fetch it.
This is somewhat cleaner than writing new Klass
all over the place, because that makes it hard to exchange Klass for something else. You hardwire it into the controller then. It's a dependency you want to avoid. With that said, you might want to have a look at the Symfony Dependency Injection framework.
Upvotes: 0
Reputation: 165201
Well, it's a bit hacky, but you could abuse the class autoloader...
So, hijack the loader to check for a class "Cat", if it doesn't exist, then eval it into existance...
if (!$foundClass) {
$code = 'Class '.$class.' extends Mammal {
public function __construct() { parent::__construct("'.$class.'");}
}';
eval($code);
}
I said it was hacky... But you could always white-list the classes you want to do this for... Plus, it has the benefit that if you wanted to modify the class, just create a firm instance of it.
But then again, I would personally find another solution. To me, new Cat()
is not readable at all. That's because of two reasons, first there's no contextual clues as to what it is and second I can't find the class Cat... What I would do is similar to what Jani suggested: $table = DatabaseTable::getInstance('cat');
... I find that MUCH more readable (even though it is more characters)...
Upvotes: 0
Reputation: 43243
I don't think there is any straightforward non-hacky way to achieve what you want with the class instantiation mechanism.
I would recommend using a factory:
$tableFactory->createTable('Cat');
Which would then work out what needs to be done behind the scenes. This also has the benefit of if you ever decide Cat requires its own class, you can simply modify the factory and you may not need to modify the code that is using it.
Upvotes: 2