-
Notifications
You must be signed in to change notification settings - Fork 0
Rebaker
Note This wiki page reflect my thoughts and don't indicate changes that will actually happen in CakePHP.
The baker is one the of things that originally attracted me to CakePHP. As useful as the tool is, I use it surprisingly very little; mostly for baking fixtures. I find it more work to remove the unwanted code baked from the core templates; so I build from scratch. I could build custom templates but I find that process to be a bit cumbersome and not mutable.
- Bake templates are cumbersome
- Baking cannot be done programmatically
- You can only bake once... cannot rebake
- Bake CLI is exhaustive
- cough cough tests!
An example meta class to represent a class could be:
Lib/MetaClass
class MetaClass {
public $name;
public $properties = array();
public $methods = array();
// Array of Trait `MetaClass`'es
public $uses = array();
// Hold an instance of a MetaClass, more about this below...
protected $_metaClass;
// Class name and file location, similar to `App::uses()`
public function __construct($name, $path) { }
// Add/Remove methods and properties
public function addMethod(MetaMethod $method, $location) { }
public function addProperty(MetaProperty $property, $location) { }
public function removeMethod(MetaMethod $method) { }
public function removeProperty(MetaProperty $property) { }
// Convenience methods for creating methods and properties
// $options would contain: access, type, docblock and parameters for method
public function createMethod($name, $value, $options) { }
public function createProperty($name, $value, $options) { }
// Method to merge methods/properties from one or more other MetaClass'es
public function merge(MetaClass $class /*[, ... ]*/) { }
// Return source code for the entire class
public function write() { }
// Use Reflection to read source of class and return an instance of MetaClass
public function read() { }
}Lib/MetaProperty
class MetaProperty {
public $name;
public $type = 'string';
public $value = 'Contents of the method here';
public $docblock = array('@inheritDoc');
public $access = 'public';
// Return fully formed source of the property (not just value)
public function write() { }
}Lib/MetaMethod
class MetaMethod {
public $name;
public $parameters = array();
public $value = 'Contents of the method here';
public $docblock = array('@inheritDoc');
public $access = 'public';
public function createParameter($name, $default, $type) { }
// Return fully formed source of the method (not just value)
public function write() { }
}Lib/MetaParameter
class MetaParameter {
public $name;
public $default;
public $type;
}We can then use this interface like this:
// Read `app/Controller/PagesController.php` if it exists
$controller = new MetaClass('PagesController', 'Controller');
$controller
->createMethod('admin_index', "$this->set('data', $this->paginate());")
->createMethod('admin_edit', "$this->set('id', $id;", array(
'parameters' => array(
array('name' => 'id', 'type' => 'integer', 'default' => null)
),
))
->createProperty('paginate' array(
'limit' => 25,
'order' => array('Page.title' => 'asc'),
));
// Merge with a core template preferring the code from $controller
$controller = $coreTemplateController->merge($controller);
// Bake the controller
$controller->write();You can only define a class once in PHP. Holding an instance of MetaClass allows this to be more flexible. For example if you wanted to create a MetaClass, read/merge the properties of an existing class, modify and then merge in another class... extending ReflectionClass wouldn't allow this but an instance of MetaClass would. This makes this very testable and avoid hitting fatal errors.
I originally planned using arrays but objects have some advantages:
- Using an object based API will produce cleaner looking code.
- More reliable and modular. Using arrays we rely on the
Hashclass which will only work as long as the array structure is strictly enforced. This is not always the case with arrays. - Objects are easier to traverse.
If code is represented as an object then it can be manipulated. This affords re-baking. This allows a user to bake a class, modify that class then bake again without overwriting the changes. New functionality can be baked into old code. A single class can be built by merging multiple templates.
App skeletons could also be represented with objects:
class MetaApp {
// Accept an array of an App structure
public function __construct($structure = array()) { }
// Create the folders and files
public function write() { }
}This interface could be used like:
$app = new MetaApp(array(
'Config' => array(
'Schema',
),
'Controller' => array(
'Component',
'PagesController.php' => new MetaClass('PagesController', 'Controller'),
),
'Model' => array(
'Behavior',
'Page.php' => new MetaClass('Page', 'Model'),
),
));
// Bake our App
$app->write();Using the above methods enables bake templates and skeletons to become testable.
Using a programatic method over flat file templates and folder structures allows bake to become an API. Users could then utilize and extend it.
As @lorenzo pointed out in IRC: baking PHP 5.4 traits would be ideal. Traits would be a much cleaner method. Using a MetaClass to bake traits would still be useful as the traits could be rebaked and more easily tested.
An example to bake a trait could be:
$trait = new MetaClass('PageTrait', 'Trait');
$trait
->createMethod('admin_index', "$this->set('data', $this->paginate());")
->createMethod('admin_edit', "$this->set('id', $id;", array(
'parameters' => array(
array('name' => 'id', 'type' => 'integer', 'default' => null)
),
))
->createProperty('paginate' array(
'limit' => 25,
'order' => array('Page.title' => 'asc'),
));
// Now we can create our Controller and use the above Trait
$controller = new MetaClass('PagesController', 'Controller');
$controller->uses = array($trait);
// Bake the controller and trait
$trait->write();
$controller->write();This would also allow a user to create a config file outlining how they would like their app baked. Then issue a single command like: cake bake Config/Bake/template.json As opposed to responding to the interactive questions or know the exhaustive list of commands to run.
A very early example of this can be seen in Oven (uses arrays at the moment): https://github.com/shama/oven/blob/master/Lib/PhpBaker.php.