I'm using CakePHP to build an application which, among other things, generates dozens of graphs. In order to make loading these graphs easy, I'm following a factory pattern using components. There's one ChartComponent which is responsible for loading components to generate the individual graphs, and these individual graphs in turn may make use of components to prepare their data.
Sample GraphComponent:
class ChartComponent extends Object {
var $name = 'Chart';
function getChart($category, $chart, $node) {
// Build the name we expect to find the component as.
$component_name = Inflector::camelize("{$category}_{$chart}_graph");
$component_class = "{$component_name}Component";
// Import the component, making sure it actually exists.
if (! App::import('Component', $component_name)) {
return false;
}
$chart = new $component_class();
return $chart->getData($node);
}
}
Sample component for individual graph:
class StoreRevenueGraphComponent extends Object {
var $name = 'StoreRevenueGraph';
var $components = array('Calculations');
function getData($node) {
var_dump(isset($this->Calculations));
}
}
However, when I run this code, the Calcluations component does not successfully load, and isset($this->Calculations) returns false.
I assume this is because I am missing out on some initialization code somewhere. Does anyone know what other steps I must take to use components inside another component via App::import, or is what I am attempting to do not possible inside cake?
Solution
As Andrew pointed out, when manually instantiating components the var $components array is never processed, causing them to never load. The solution is to manually do this like so:
function startup(&$controller) {
foreach ($this->components as $component_name) {
App::import('Component', $component_name);
$component_class = "{$component_name}Component";
$this->$component_name = new $component_class();
$this->$component_name->startup($this);
}
}
The If you want one component to load multiple other components you can override the $components
property can only be used in controllers.startup
method in your component and create the component and manually assign it to the ->Calculations
property.
Like this:
function startup( $controller ) {
$this->Calculations = new Foo();
$this->Calculations->startup($component);
}
However, this is a bit of an odd thing to do. What is more likely in most apps is that you want to load it into the controller itself. So the code becomes:
$component->Calculations = new Foo();
$component->Calculations->startup($component);
If the component is not ever used directly by the controller it may be better to put the classes in the vendors directory and use them as external libraries.
[edit]
See comments after this answer.