Using CakePHP Components within Components via App::import
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.
Nearly there... the corrected startup() method is
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);
}
}
In Cake 2.3 the solution given in the original post yields a warning error though it does function properly and not return false:
Warning (4096): Argument 1 passed to Component::startup() must be an instance of Controller, instance of MessagesComponent given, called in ...(deleted).../MessagesComponent.php on line 26 and defined [CORE/cake-lib-2.3.1/Controller/Component.php, line 120]
The offending line is:
$this->$component_name->startup($this);
I updated it to
$this->$component_name->startup($controller);
and it eliminates the error and functions properly.
A complete working version for 2.3 is:
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($controller);
}
}
Addendum:
Upgrading the OS to Ubuntu 14.04 with a newer version of PHP (5.5.9) yielded a new warning message (still using CakePHP 2.3.6, later versions of Cake may not have this problem):
Strict (2048): Declaration of MessagesComponent::startup() should be compatible with Component::startup(Controller $controller) [APP/Controller/Component/MessagesComponent.php, line 0]
To eliminate this the declaration for startup had to be changed from:
function startup(&$controller) {
to:
function startup(Controller $controller) {
Regarding to cookbook, you can load other components the same way as controllers. You can declare it inside $components var.
// app/Controller/Component/CustomComponent.php
App::uses('Component', 'Controller');
class CustomComponent extends Component {
// the other component your component uses
public $components = array('Existing');
public function initialize(Controller $controller) {
$this->Existing->foo();
}
public function bar() {
// ...
}
}
// app/Controller/Component/ExistingComponent.php
App::uses('Component', 'Controller');
class ExistingComponent extends Component {
public function foo() {
// ...
}
}
You can use as bellow :)
class YourComponent extends Component {
public function initialize(Controller $controller)
{
$this->controller = $controller;
if (!isset($this->controller->presetVars)) {
$this->controller->presetVars = true;
}
$model = $this->controller->modelClass;
if (!empty($settings['model'])) {
$model = $settings['model'];
}
if ($this->controller->presetVars === true) {
// auto-set the presetVars based on search definitions in model
$this->controller->presetVars = array();
$filterArgs = array();
if (!empty($this->controller->$model->filterArgs)) {
$filterArgs = $this->controller->$model->filterArgs;
}
foreach ($filterArgs as $key => $arg) {
if ($args = $this->_parseFromModel($arg, $key)) {
$this->controller->presetVars[] = $args;
}
}
}
foreach ($this->controller->presetVars as $key => $field) {
if ($field === true) {
if (isset($this->controller->$model->filterArgs[$key])) {
$field = $this->_parseFromModel($this->controller->$model->filterArgs[$key], $key);
} else {
$field = array('type' => 'value');
}
}
if (!isset($field['field'])) {
$field['field'] = $key;
}
$this->controller->presetVars[$key] = $field;
}
/* now you can use Component existing in your Component :) */
public function sayHello(){
$this->controller->Session->setFlash(__('Hello you'));
}
}
精彩评论