This might be a naive question since I am new to cakephp.
I am working on a project where there are many layouts, helpers and elements. Because this is a mutli-language site, I am intercepting the final rendered output to do some language conversion(so each visitor only sees his native language on the site including user input).
I've managed to convert most of the layouts and helpers by adding code in two places: AppController's afterFilter() and AppHeler's afterRender() function. But I can't figure out a centralized way to handle the elements and there are dozens of them.
So here are my questions: do all elements in cakephp have a common ancestor class? If so, does this ancestor class have callback functions like afterRender()?
Many thanks!
I'm not sure such a callback exists specific for 'elements', but looking at the source code, View::element() renders an element using the same _render() method as the View itself, and should trigger beforeRender() and afterRender()
Creating a custom View Class and Custom Callbacks
You may use a custom 'View' class and override the element() method, for example to have your own 'custom' callbacks being triggered in helpers
Something like this;
app/view/app_view.php
class AppViewView extends View {
/**
* custom 'element()' method, triggers custom
* before/aferRenderElement callbacks on all loaded helpers
*/
public function element($name, $params = array(), $loadHelpers = false)
{
$this->_triggerHelpers('beforeRenderElement');
$output = parent::element($name, $params, $loadHelpers);
$this->_triggerHelpers('afterRenderElement');
}
/**
* Names of custom callbacks
*/
protected $_customCallBacks = array(
'beforeRenderElement',
'afterRenderElement',
);
function _triggerHelpers($callback)
{
if (!in_array($callback, $this->_customCallbacks)) {
// it's a standard callback, let the parent class handle it
return parent::_triggerHelpers($callback);
}
if (empty($this->loaded)) {
return false;
}
$helpers = array_keys($this->loaded);
foreach ($helpers as $helperName) {
$helper =& $this->loaded[$helperName];
if (is_object($helper)) {
if (
is_subclass_of($helper, 'Helper')
&& method_exists($helper, $callback)
) {
$helper->{$callback}();
}
}
}
}
}
Then, in your AppController specify the 'view' class to use;
class AppController extends Controller {
public $view = 'AppView';
}
Related
The question i am asking is very similar and already asked question.But It is working for me.
ViewReportsController.php
class ViewReportsController extends AppController {
public function index() {
$count_table = 10;//sample variable that is available in view
$this->set('count_tablen',$count_table);
}
}
APP/View/Layouts/default.ctp
pr($count_tablen);
Now i am getting the error says- Undefined variable: count_tablen [APP/View/Layouts/default.ctp, line 228]
You are using a variable in your main layout template which is likely used by multiple controller actions. Therefore, the code example you've provided would only work on /view_reports/index. If you want to set variables to be used in the layout templates you need to do this in the beforeRender callback of AppController so that it can be used everywhere:-
public function beforeRender() {
parent::beforeRender();
$count_table = 10;
$this->set('count_tablen', $count_table);
}
If you use multiple layout templates you can check which template will be used in beforeRender before setting the variable:-
public function beforeRender() {
parent::beforeRender();
if ($this->layout === 'default') {
$count_table = 10;
$this->set('count_tablen', $count_table);
}
}
I have a Eloquent Model and I want to create a customized toArray method...
class Posts extends Model {
public function scopeActives($query)
{
return $query->where('status', '=', '1');
}
public function toCustomJS()
{
$array = parent::ToArray();
$array['location'] = someFunction($this->attributes->location);
return $array;
}
}
//In my controller:
Posts::actives()->get()->toArray(); //this is working
Posts::actives()->get()->toCustomJS(); //Call to undefined method Illuminate\Database\Eloquent\Collection::toCustomJS()
How can I override the toArray method or create another "export" method?
get() actually returns a Collection object which contains 0, 1, or many models which you can iterate through so it's no wonder why adding these functions to your model are not working. What you will need to do to get this working is to create your custom Collection class, override the toArray() function, and also override the function in your model responsible for building that collection so it can return the custom Collection object.
CustomCollection class
class CustomCollection extends Illuminate\Database\Eloquent\Collection {
protected $location;
public function __construct(array $models = Array(), $location)
{
parent::__construct($models);
$this->location = $location;
}
// Override the toArray method
public function toArray($location = null)
{
$original_array = parent::toArray();
if(!is_null($location)) {
$original_array['location'] = someFunction($this->location);
}
return $original_array;
}
}
Overriding the newCollection method on your models
And for the models you wish to return CustomCollection
class YourModel extends Eloquent {
// Override the newCollection method
public function newCollection(array $models = Array())
{
return new \CustomCollection($models, $this->attributes['location']);
}
}
Please note this may not be what you are intending. Because a Collection is really just an array of models, it's not good to depend on the location attribute of a single model. Depending on your use-case, it's something that can change from model to model.
It might also be a good idea to drop this method into a trait and then just use that trait in each model you wish to implement this feature in.
Edit:
If you don't want to go through creating a custom Collection class, you can always just do it manually each time...
$some_array = Posts::actives()->get()->toArray();
$some_array['location'] = someFunction(Posts::first()->location);
return Response::json($some_array);
I know that's kinda simple and lame question, but still.
I have a Form which should not show all Model fields, but only some of them. That's why I can't use Form->setModel($m), because that'll automatically add all fields into Form.
So I add Model into page, then add form into page and then use form->importFields like this:
$m = $p->add('Model_Example');
$f = $p->add('Form');
//$f->setModel($m); // can't use this because that'll import all model fields
$f->importFields($m,array('id','description'));
$f->addSubmit('Save');
What I don't understand in this situation is - how to save this data in database, because $f->update() in onSubmit event will not work. Basically nothing I tried will work because Form have no associated Model (with setModel).
How about this way?
$your_form->setModel($model,array('name','email','age'));
I have solution for mixed form. Add custom form fields in form init and manipulate with them by hooks ('afterLoad','beforeSave')
In this case you can use setModel() method
$form->setModel('Some_Model',array('title','description'));
class Form_AddTask extends Form {
function init(){
parent::init();
$this->r=$this->addField('autocomplete/basic','contact');
$this->r->setModel('ContactEntity_My');
}
function setModel($model,$actual_fields=undefined){
parent::setModel($model,$actual_fields);
$this->model->addHook('afterLoad',array($this,'setContactId'));
$this->model->addHook('beforeSave',array($this,'setContactEntityId'));
return $this->model;
}
// set saved value for editing
function setContactId() {
$this->r->set($this->model->get('contact_entity_id'));
}
function setContactEntityId() {
$this->model->set('contact_entity_id',$this->get('contact'));
}
}
There is a hook 'validate' as well in Form_Basic::submitted(), so you can add
$this->addHook('validate',array($this,'validateCustomData'));
and validate your data in Form::validateCustomData()
Why not set the fields to hidden in the model?
I.e.:
class Model_Example extends Model_Table {
public $table='assessment';
function init() {
parent::init();
$grant->addField('hidden_field')->hidden(true);
}
}
And then:
$m = $p->add('Model_Example');
$f = $p->add('Form');
$f->setModel($m);
What I want to do is the following:
I have a model called "Control":
class Control extends AppModel {
var $name = 'Control';
var $myVariable:
function getMyVariable() {
$this->$myVariable = 'hello';
return ($this->$myVariable);
}
function getMyVariable2() {
$myVariable2 = 'hello';
return ($myVariable2);
}
}
Then, from another controller I do:
class TestsController extends AppController {
var $name = 'Tests';
var $uses = array('Test','Control');
function index() { //whatever }
function doStuff() {
$aux = $this->Control->getMyVariable(); //not working, variable not declared
$aux2 = $this->Control->getMyVariable2(); //works
}
I assumed (probably wrong) that I could declare a variable as a property (or atribute) in a model Class like in any other OO languaje, and access it from other places of the aplication, but I guess it doesn't work like this in CakePHP. Am I missing something? Is there any other way to do this? I mean, to having a variable in a model (which content doesn't come from a table) and acces it from other controllers/views?
$this->$myVariable is the syntax for "variable variables" (or in this case, variable properties). The correct syntax is $this->myVariable. CakePHP does not alter the basics of PHP OOP.
Setting a variable in a getter is pretty weird though, you should not do that.
Also, if you are using getters, you should make the property protected or private, otherwise it's somewhat pointless.
I want to extend, not just create a new instance of a class I have sitting in my vendors directory. I googled and read the docs but I see no support for it.
Can I do an app import of the 3rd party class, then write up the extended class followed by a component that will use my child class?
i.e
/* vendors/yahooapi/yahoo.class.php */
class YahooAPI {
var $key = 'demo';
}
/* controllers/components/yahoo.php */
App::import("Vendor", "YahooAPI", array("file"=>"yahooapi.class.php"));
class Yahoov2 extends YahooAPI {
var $key = 'newKey';
function go() {}
}
YahooComponent extends Object {
function goFaster() {
$a = new Yahoov2;
return $a->go() * 2;
}
}
Basically, I will tell you how I would do it (at least I've did it in some projects):
1 add your vendor vendors/yahooapi/yahoo.class.php as you did
2 create a file inside the vendors/yahooapi/ or outside in vendors/ which will extend the original vendor class let's say vendors/yahoov2.php
i.e.
include_once('.../vendors/yahooapi/yahoo.class.php');
class Yahoov2 extends YahooAPI {
var $key = 'newKey';
function go() {}
}
3 And finally include in the component your extension as you did in your controller.
I believe that also extending the class in your controller directly would do the job, but it's really a matter of taste.