Custom accessors Eloquent Model - arrays

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);

Related

Typescript private and protected members exposed to angular 1.x view

It seems like when combining TS and angular, everything i have on a controller is exposed to the view. In my case, myPrivate will appear on $ctrl.
class MyController extends BaseController implements SomeInterface {
private myPrivate: string = 'myPrivateString';
}
Is there any workaround around this issue?
It's pretty obvious why when you look at the generated javascript.
var MyController = (function (_super) {
__extends(MyController, _super);
function MyController() {
_super.apply(this, arguments);
this.myPrivate = 'myPrivateString';
}
return MyController;
}(BaseController));
Your private property ends up as any other property on your controller.
You can see the full transpilation here.
A solution would be to have a parameterized base controller able to set something like a view model for the view to use, instead of the regular $ctrl.
It would look something like this:
class BaseController<T> {
protected scope;
protected viewModel: T;
constructor(scope: any, modelType: { new (): T; }) {
this.scope = scope;
this.viewModel = new modelType();
this.scope["viewModel"] = this.viewModel;
}
}
class MyParticularViewModel {
public somethingForTheView: string;
}
class MyController extends BaseController<MyParticularViewModel> implements SomeInterface {
private myPrivate: string = 'myPrivateString';
constructor(scope) {
super(scope, MyParticularViewModel);
}
}
In the view you can then use the viewModel property to access the needed properties.
I have used this in a project in practice and it worked out pretty well. You can see a starter template that I used here for more info.

Backbone model level class method

I'm getting some model names dynamically and I want to access a method from each of the models. Is there a way that I can declare and access model level class method or constant in BB like Namespace.models["MyModel"].classMethod()/MY_CONSTANT?
Here's how implement instance and class methods in Backbone.
var instance_properties = {
myInstanceMethod: function() {console.log('instance method');}
};
var class_properties = {
myClassMethod: function() {console.log('class method');}
};
var Model = Backbone.Model.extend(instance_properties ,class_properties);
Model.myClassMethod(); // class method
var model = new Model();
model.myInstanceMethod(); // instance method

callback functions for cakephp elements?

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';
}

Backbone Collection of polymorphic Models

I have a collection of Animals.
App.Collections.Animals extends Backbone.Collection
model: App.Animal
url: '/animals/' #returns json
And these animal classes:
App.Models.Animal extends Backbone.Model
App.Models.Monkey extends App.Models.Animal
defaults:{type:'Monkey'}
App.Models.Cat extends App.Models.Animal
defaults:{type:'Cat'}
App.Models.Dog extends App.Models.Animal
defaults:{type:'Dog'}
When collection is filled with JSON (records contain the type attribute) I want models to be instantiated as sub-classed models (Monkey,Cat,Dog) and not as Animal. How can you achieve this?
From Backbone documentation:
A collection can also contain polymorphic models by overriding this
property with a function that returns a model.
var Library = Backbone.Collection.extend({
model: function(attrs, options) {
if (condition) {
return new PublicDocument(attrs, options);
} else {
return new PrivateDocument(attrs, options);
}
}
});
The solution is straightforward (pardon the JS, I don't know CoffeeScript):
var SmartZoo = Backbone.Collection.extend({
model: function (attrs, options) {
// This code assumes that the object looks something like '{ type: "Cat", ... }'.
switch (attrs.type) {
case 'Cat':
return new Cat(attrs, options);
case 'Dog':
return new Dog(attrs, options);
default: // Unknown subclass
return new Animal(attrs, options);
}
}
});
You have to:
Include an attribute in your model from which you can infer the type of Backbone model to create. In this example, my objects contain an attribute called "type" whose value is the full name of the Backbone type that represents it. Be sure to set it in the defaults or initialize of your Model so that you can also add real model instances to the collection.
Define the models property of your collection as a function. The first parameter to this function will be the raw JS object (if that's what you passed in), or the attributes object of a Backbone model. Either way, you can access your type field as a property of this object.
Execute your logic to infer the proper model from your type field.
Return an instance of the correct model from the models function.
Here is a JSFiddle that shows this polymorphic collection in action: http://jsfiddle.net/FiddlerOnTheTarmac/uR2Sa/
Override backbone collection's _prepareModel. The collection new uses subclasses when defined otherwise uses the default model.
class App.Collections.Animals extends Backbone.Collection
model: App.Models.Animal
_prepareModel: (attrs, options) ->
if attrs instanceof Backbone.Model
attrs.collection = #
return attrs
options || (options = {})
options.collection = #
model_class = APP.Models[attrs.ntype] or this.model
model = new model_class(attrs, options)
if (!model._validate(attrs, options))
false
else
model

Variables in CakePHP's models: how to acces them from another controller?

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.

Resources