Cannot access Eloquent attributes on Twig - php

I am trying to access an Eloquent attribute with Twig in Slim, and getting an error.
I have a Field and a Type object, and the relationship is as follows
class Field extends \Illuminate\Database\Eloquent\Model {
protected $table = 'fields';
public function type()
{
return $this->belongsTo('models\Type');
}
When doing {{ f }} (being f a field), the output is this:
{"field_id":"1","field_name":"Your name","form_id":"2","type_id":"1","placeholder":"Please give us your name"}
And when doing {{ f.type }}, the result is:
Message: An exception has been thrown during the rendering of a template ("Object of class Illuminate\Database\Eloquent\Relations\BelongsTo could not be converted to string") in "pages/editform.html" at line 97.
If I try to do {{ f.type.name }}, doesn't throw up an exception but doesn't print anything either.
If I do it in PHP
$fields = $form->fields;
var_dump($fields[0]->type->name);
The value gets output correctly.
Any ideas?,
Thanks

If you don't want to do an eager load, you can override the magic __isset method in your Field class to return true for the type relationship property:
public function __isset($name)
{
if (in_array($name, [
'type'
])) {
return true;
} else {
return parent::__isset($name);
}
}
Explanation
The problem is in the interaction between how Eloquent implements the "dynamic property" for a relation (in your case, f.type), and the rules that Twig uses for accessing "attributes" of a variable.
From the Twig documentation:
For convenience's sake foo.bar does the following things on the PHP layer:
check if foo is an array and bar a valid element;
if not, and if foo is an object, check that bar is a valid property;
if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
if not, and if foo is an object, check that getBar is a valid method;
if not, and if foo is an object, check that isBar is a valid method;
if not, return a null value.
foo['bar'] on the other hand only works with PHP arrays:
check if foo is an array and bar a valid element;
if not, return a null value.
The key here is the part where it says "check that bar is a valid property". This means that on the level of PHP, Twig is calling isset on $f->type. Eloquent implements the magic __isset method in Model, so you might think that this wouldn't be a problem.
However, take a look at how it actually implements __isset for model relations:
/**
* Determine if an attribute exists on the model.
*
* #param string $key
* #return bool
*/
public function __isset($key)
{
return (isset($this->attributes[$key]) || isset($this->relations[$key])) ||
($this->hasGetMutator($key) && ! is_null($this->getAttributeValue($key)));
}
It determines whether a relation is "set" by looking in its array of loaded relationships (isset($this->relations[$key])). The problem is that if type hasn't been loaded yet, Eloquent will say that it is not "set".
Thus when Twig looks at $f->type, it will think that type is not a valid property, and move on to the next rule:
...if foo is an object, check that bar is a valid method
So now it will look for the method type(), which it finds. The only problem? type() (the method) returns a Relation (specifically, a BelongsTo), rather than a Type object. And BelongsTo objects aren't models.
If you want Twig to know that the property $f->type does indeed exist, you have two choices:
You can eager-load the related Type object along with your Field, as suggested by #roger-collins, or;
You can overload the __isset magic method in Field:
public function __isset($name)
{
if (in_array($name, [
'type'
])) {
return true;
} else {
return parent::__isset($name);
}
}
This will force Twig to recognize that type is a valid property for Field, even before the related model has actually been loaded.

I had the same issue and stumbled upon this question. After solving it myself, I thought I'd try to help you out.
Try doing a Eager Load on the model:
Field::with('type')->get()
This should allow you to do the following with no other issues.
{{ f.type }}
See more info here: http://laravel.com/docs/4.2/eloquent#eager-loading

Related

Laravel object property is null when looping but has value when json_encode()

My View piece of code:
#forelse ($questions as $question)
<a href="#" class="list-group-item">
<h4>{{$question->title}}</h4>
<p>{{Str::limit($question->body, 100)}}</p>
<span>Asked {{$question->created}} by {{$question->user_name}}</span>
{{json_encode($question)}}
</a>
#empty
<h2>No questions!</h2>
#endforelse
I'm getting html like this ('...' is ok, that's valid but value is long):
<a href="#" class="list-group-item">
<h4>...</h4>
<p>...</p>
<span>Asked by </span>
<br>
{"id":1,"title":"...","path":"...","body":"...","created":"2 hours ago","user_name":"Nash Bogisich","user_id":11}
</a>
As you see, inside html created is null, as well as user_name. But inside json, these props have theirs values. I don't understand how that's possible, please help.
QuestionResource is completely plain:
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' =>$this->id,
'title' =>$this->title,
'path' =>$this->path,
'body' =>$this->body,
'created' => $this->created_at->diffForHumans(),
// 'modified' =>$this->modified_at,
'user_name' =>$this->user->name,
'user_id' =>$this->user_id,
];
}
}
I also checked that created is really null.
if ($question->created==null){
$created = 'null value';
}
Yes, null value.
$questions isn't really array (I think), it's a Laravel collection.
Controllers (I have two, one is for api and one is for web. Web uses api.):
API:
public function index()
{
// now each transform of question use QuestionResource
return QuestionResource::collection(Question::latest()->paginate(15));
}
WEB:
public function index()
{
$questions = $this->apiController->index();
// return $questions;
return View::make('question.index')->with('questions', $questions);
}
First, let's stabilish this:
From PHP's Type Juggling: ...a variable's type is determined by the
context in which the variable is used.
Simple example for insight:
$thisIsAnObject = new Object;
echo $thisIsAnObject;
// as echo expects a string, PHP will attempt to cast it to string and it
// will fail with a message similar to: "Object could not be converted to string"
However, Laravel, specially in Illuminate\Support\Collection, takes over of this automatic casting and feeds the context in the type it requires.
Simple example for insight:
$simpleCollection = collect([['key' => 'value'],['key' => 'value']]);
echo $simpleCollection;
// instead of letting it error, laravel feeds the echo statement with the json
// representation of the collection (which is a string): [{"key":"value"},{"key":"value"}]
// check Illuminate\Support\Collection::__toString() function
Laravel does the exactly same for foreach:
$collection = Question::latest()->paginate(15);
foreach ($collection as $model) {}
// collection feeds the foreach an array of models for better manusing.
// check Illuminate\Support\Collection::toArray() function
To wrap it all up, notice that the QuestionResource's mapping takes place inside a toArray() function.
So this:
$questionResource = QuestionResource::collection(Question::latest()->paginate(15));
if you feed a foreach with the $questionResource above, the framework intentions are to feed the foreach with an array of its collection models, ignoring your intention to cast everything to primitive array with QuestionResource's ->toArray().
However, for example, if you were to pass $questionResource to the json_encode() function, which expects a string and does not involves any looping, the framework has no problem resolving the QuestionResource's ->toArray() first and then casting it to the json representation of the collection.
That's what's happening with your code above.
I will write 2 ways of getting by this issue, from fastest to finest:
1) calling ->toArray() to force the casting beforehand
$questions = $this->apiController->index()->toArray(request());
#forelse ($questions as $question)
<a href="#" class="list-group-item">
<h4>{{$question['title']}}</h4>
<p>{{Str::limit($question['body'], 100)}}</p>
<span>Asked {{$question['created']}} by {{$question['user_name']}}</span>
</a>
#empty
<h2>No questions!</h2>
#endforelse
2) create an accessor for created and user_name fields in your Question model
class Question extends Model
{
// $this->created
public function getCreatedAttribute()
{
return $this->created_at->diffForHumans();
}
// $this->user_name
public function getUserNameAttribute()
{
return $this->user->name;
}
}
Like so, you are persisting those fields, by having them available everywhere there's an instance of the Question model. Of course, that includes QuestionResource.
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'path' => $this->path,
'body' => $this->body,
'created' => $this->created,
// 'modified' => $this->modified_at,
'user_name' => $this->user_name,
'user_id' => $this->user_id
];
}
}
#forelse ($questions as $question)
<a href="#" class="list-group-item">
<h4>{{$question->title}}</h4>
<p>{{Str::limit($question->body, 100)}}</p>
<span>Asked {{$question->created}} by {{$question->user_name}}</span>
{{json_encode($question)}}
</a>
#empty
<h2>No questions!</h2>
#endforelse
Hope it helps.
I think you are getting a bit confused by the API and Web responses.
First you define the QuestionResource API resource, that's fine and you put the created attribute correctly.
If you then look at your view file, you are iterating over a collection where each object is a QuestionResource object type.
To get the attribute you want you should print out {{ $question->created_at->diffForHumans() }} as you are still interacting magically with a Question model.
Quick in-depth explaination
When using API resources you should keep in mind that the PHP Object -> JSON string conversion happens as later stage after your controller have hadled the request and before the router componen send out the response to the user, if you call your method from another one, you still haven't been through the json transforming process as it is eventually part of a later stage in the core code.
This means that the $question return value you are getting from $this->apiController->index() call, would give you a collection of QuestionResource, not a json string of the response.
To understand how you can still access the Question model even if you have an array of QuestionResource objects, you have to take a look at the JsonResource class in the laravel core.
Every API resource class in laravel inherits from it. This class uses the DelegateToResource trait (this one is defined as well in Laravel's core) which defines this method:
/**
* Dynamically get properties from the underlying resource.
*
* #param string $key
* #return mixed
*/
public function __get($key)
{
return $this->resource->{$key};
}
This means that when you have a $question object that is of type QuestionResource you can still access the underlying resource by simply "pretending" your resource object is a question, as it will act like so and would automatically forward all the method calls and attribute read to the Question model.
If you are questioning where you did tell the QuestionResource to wrap the Question model in itself, you did it in the return instruction of your index method:
return QuestionResource::collection(Question::latest()->paginate(15));
You can see this return instruction as a "simple" wrapping of each Question model you pass as an argument in a bigger object (the QuestionResource class) that adds a method to represent the Question as an array that will be therefore used to converted the model to json at a later stage (as I mentioned eariler).
So to retrive fields correctly in your #foreach loop you have to refer to your model attributes' names, not the ones you defined as a return for the toArray() method, as that would be executed at a later stage.
So, why does {{json_encode($question)}} output the fields defined in the resource?
Because JsonResource class implements the PHP's JsonSerializable interface, this means it implements the jsonSerialize method (defined by the interface) to force the json conversion.
When you call json_encode method, php will recognize that $question implements that interface and therefore is serializable, so it call the jsonSerialize method and get the desired string.
As a result you get the api resource output and converted to json, and that output have the fields you defined in the QuestionResource class
Thanks to #mdexp answer and #Welder Lourenço.
These solutions worked for me:
Just call json_encode and json_decode in my web QuestionController. Very simple, do not produce much overhead. But it's a workaround.
Specify property get-accessors in Question Model class. Subtle. And the Model class is the right place to define accessors. Now I do use that solution.
Code for #1:
public function index()
{
$str = json_encode($this->apiController->index());
$questions = json_decode($str);
return View::make('question.index')->with('questions', $questions);
}
Code for #2:
// in Question model class...
// $this->created
public function getCreatedAttribute()
{
return $this->created_at->diffForHumans();
}
// $this->user_name
public function getUserNameAttribute()
{
return $this->user->name;
}

How to implement PDO FETCH_CLASS in correct way?

Hi world genius of programming. I am quite newbie in PDO and OOP, Please understand.
I try to do the most simple thing in the world - get data from a table in MySQL.
I want to:
1) SELECT * from ... it's about 20 fields.
2) To get an array of object with 4-6 of properties.
3) I want to use fetchAll and FETCH_CLASS...
PDOStatement PDO::query ( string $statement , int $PDO::FETCH_CLASS , string $classname , array $ctorargs )
I've found that we can pass an array of argument but can't implement it.
So what am I doing?
class handler{
connection etc..
public $params = array('surname','id','country','display' );
return $stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'person',$this->params);
class person {
public $surname=null;
public $id=null;
public $country=null;
public $status=null;
and then
__construct ()
i will not put it - cause i ve got 50 variants of them(((
}
So, I need to filter options from 20 fields fetching a class but not in SELECT mode instead of *...
Is it possible?
I know that you are genius!
Forgive for newbieness
UPDATE
function __construct($surname,$id,$country,$display) {
$this->surname=$surname;
$this->country=$country;
$id->id->$id
// that the only i need in this oblject
}
function __construct() {
$arg=array('surname','id');
foreach ($arg as $val) {
$this->{$val}=$$val;
}
}
it seems it maybe the next.. not construct function that will filter properties...
UPDATE
I tried solutions as #GolezTrol kindly proposed.
Solution 1 is arguing for... Notice: Undefined property: Person::$_attributes in
if i make
class Entity {
public $_attributes;
function __construct() { ....
or
class Person extends Entity {
public $_attributes;
}
it works.. but i get an object...
[0] => Person Object
(
[_attributes] => Array
(
[0] => surname
[1] => id
[2] => country
[3] => status
)
[id] => 298
.. it's not good(
I think you mean that you want to load only the properties that you specified instead of all values that were returned from the query. Your attempt is to do that by passing the desired field names to the constructor.
Solution 1: Just specify the array of properties and block the rest
Your way might just work, if you get a little help from the __set magic method. Using func_get_args() you can get all the arguments of a function (the constructor in this case) into an array. This way, you get the array of field names that you passed to fetch_all.
The magic setter only sets the properties if they exist in the array that was given to the constructor, so essentially it filters out all fields you don't want.
Advantage: easy. No specific implementation needed in descendant classes. You could just use Entity as a class for all entities.
Disadvantage: magic setter is called for every property and calls in_array this may be slow. fetch_all is determining which fields to read, while maybe this should be the class's responsibility.
class Entity {
function __construct() {
$this->_attributes = func_get_args();
}
function __set($prop, $value) {
if (in_array($prop, $this->_attributes)) {
$this->$prop = $value;
}
}
}
// If you would need a descendant class to introduce methods, you can..
class Person extends Entity {
}
$stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person', array('id', 'surname', 'gender'))
Solution 2: block all properties that don't exist
Similar solution, but much cleaner, I think. Implement the magic setter and make it do... nothing. It will be called for properties that don't exist and only for properties that don't exist. So in Person you just declare whatever values you want to read. All other properties will be directed to the empty __set method so they are implicitly ignored.
Advantage: Still easy. Hardly any implementation. You can put the empty method in a base class or just implement it in Person and every other class you have. You just declare the properties in Person. You don't even need to specify the fields you want to read in fetch_all. Also, reading into existing properties is faster.
Disadvantage: if you want to read different sets of information into the same class, this is not possible. The person in my example below always has an id, surname and gender. If you want to read for instance id only, you have to introduce another class. But would you want that?..
class Entity {
function __set($prop, $value) {
// Ignore any property that is not declared in the descendant class.
}
}
class Person extends Entity {
public $id = null;
public $surname = null;
public $gender = null;
}
$stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person')
Solution 3: Read only the fields you want
This is actually the best solution. Instead of selecting all fields, SELECT *, select only the fields you want to have: SELECT id, surname, gender ... This way, you won't have objects with too many values, but more importantly, you also decrease the load on your database. The database doesn't need to fetch the data, PHP doesn't need to receive it, and if the database server is separate from the webserver, you also save network traffic. So in all regards, I think this is the best option.

Extending Yii model with predefined value

I have a model class with some basic values, and now I want to extend it with a calculated ID field. In my system we use an ID for every entity, that is containing the type of the entity and the auto-increment id from the DB.
I would need a parameter, call it now $cid (calculated id) that is setted when it initialized.
I've tried to set it in the init/model functions, but I get Property "Product.cid" is not defined. Exception.
And I've tried to create a function:
public function _cid($value = null) {
if($value == null){
return $this->cid;
}else{
$this->cid = $value;
return $this->cid;
}
}
How should I extend my model to have this value as a parameter of the Model?
Update
Jon answered really well and the official docs are really helpful. But, with this solution, now the getCid function is only called, when I call it independently. When I call it via the model's getAttributes($model->safeAttributeNames) (or getAttributes(array('cid'))), I get null as the value of $model->cid and the getCid method is not called. (attribute is setted to be safe)
Why don't you simply use a read-only property?
private $_cid;
public function getCid()
{
if ($this->_cid) === null {
// calculate the value here on demand
$this->_cid = 'whatever';
}
return $this->_cid;
}
Thanks to the implementation of __get in CComponent, you can access this value as a property with $model->cid.

Twig & PHP ActiveRecord - Cannot access field from join table

I cannot access joined table with PHPActiveRecord/Twig. Here is simplified code. It has two models (Code and User), and each code belongs to one user, so I want to list codes with the name of code's author.
php
// model
class Code extends ActiveRecord\Model {
static $belongs_to = array(
array('user'),
);
}
class User extends ActiveRecord\Model {
static $has_many = array(
array('code'),
);
}
// controller
$codes = Code::all(array('include' => 'user'));
var_dump($codes); //-> successfully displayed codes list and their authors
$this->twig->display('codelist.twig', $codes);
template.twig
{% for code in codes %}
{{ code.name }} //-> successfully displayed code's name
{{ code.user.name }} //-> failed to output user's name with error
{% endfor %}
// error:
// An exception has been thrown during the rendering of a template ("Call to undefined method: user") in "inc/template.twig" at line **.
I saw this page:
http://twig.sensiolabs.org/doc/templates.html
Implementation
For convenience sake foo.bar does the following things on the PHP
layer:
check if foo is an array and bar a valid element; if not, and if foo
is an object, check that bar is a valid property; if not, and if foo
is an object, check that bar is a valid method (even if bar is the
constructor - use __construct() instead); if not, and if foo is an
object, check that getBar is a valid method; if not, and if foo is an
object, check that isBar is a valid method; if not, return a null
value. foo['bar'] on the other hand only works with PHP arrays:
check if foo is an array and bar a valid element; if not, return a
null value.
Although I can access user attribute via $codes[0]->user, why can't I access user attribute in twig templates file?
Thanks to greut, I solved the problem. I replaced function __isset in lib/Model.php in PHPActiveRecord.
/**
* Determines if an attribute exists for this {#link Model}.
*
* #param string $attribute_name
* #return boolean
*/
public function __isset($name)
{
// check for getter
if (method_exists($this, "get_$name"))
{
$name = "get_$name";
$value = $this->$name();
return $value;
}
return $this->read_attribute($name);
}
https://github.com/kla/php-activerecord/issues/156

PropertyObject Class Help

I am new to php and currently I am reading Wrox Professional PHP 5.
Can anyone explain me the following code ?
<? php
abstract class PropertyObject
{
//Stores name/value pairs that hook properties to database field names
protected $propertyTable=array();
//List of properties that have been modified.
protected $changedProperties=array();
//Actual data from the database.
protected $data;
//Any validation errors that might have occured.
protected $errors=array();
public function __construct($arData)
{
$this->data=$arData;
}
function __get($propertyName)
{
if(!array_key_exits($propertyName,$this->propertyTable))
{
throw new Exception("Invalid property \"$propertyName\" !");
}
if(method_exists($this,'get'.$propertyName))
{
return call_user_func(array($this,'get'.$propertyName));
}
else
{
return $this->data[$this->propertyTable[$propertyName]];
}
}
function __set($propertyName,$value)
{
if(!array_key_exits($propertyName,$this->propertyTable))
{
throw new Exception("Invalid property \"$propertyName\" !")
}
if(method_exits($this,'set'.$propertyName))
{
return call_user_func(array($this,'set'.$propertyName),$value);
}
else
{
//If the value of the property really has changed and it's not already in the changedProperties array, add it.
if($this->propertyTable[$propertyName] !=$value && !in_array($propertyName,$this->changedProperties))
{
$this->changedProperties[]=$propertyName;
}
//Now set the new value
$this->data[$this->propertyTable[$propertyName]]=$value;
}
}
}
?>
I can't understand the code inside assessor get and set methods.
The __get magic method is called when a property of the object is requested but it wasn't declared or specifically assigned (for dynamic properties). This implementation:
First tries to see if the logical property exists as an entry in the actual declared property named $propertyTable.
If it doesn't exist, it throws an exception, therefore leaving the method,
If it exists and additionaly exists a method named 'get'.$propertyName (i.e., "get" concatenated with the request property name), that method is called and its value is returned.
If it exists but there's no such method, it returns the value of the entry with key $propertyName in the declared property $propertyTable.
Given this, I think you can figure __set out. See Magic Methods in the PHP manual.
This is a really common way of setting up a DB storage class. What happens is you instantiate an object based on PropertyObject (as PropertyObject is abstract)
class MyObj extends PropertyObject {
}
$m = new MyObj();
Which inherits the __get() and __set() methods. Any time the object's data is accessed via the -> operator, the __get() and __set() methods are called, respectively.
$m->foo; #calls MyObject::__get('foo');
$m->bar = 'baz'; #calls MyObject::__set('bar','baz');
The __get() method first checks to see if the there is a key defined in the property table (which here models fields from the DB), and if one does not exist, throws an exception.
Then, get() will see if there is a function defined with the word 'get' prepended. So, assuming foo was a key in the propertyTable, __get() would see if we had defined a method getfoo, and if we had, call it for us, and return its value.
//if(method_exists($this,'get'.$propertyName))
//{
// return call_user_func(array($this,'get'.$propertyName));
//}
$m->foo; # checks if MyObj::getfoo is defined, and if so, calls it
Lastly, if there is a key foo in the propertyTable but no method named getfoo, it would simply return the value of the array position in $m->data whose key is the value of the array position in propertyTable whose key is foo
__set() is defined much the same way, but rather than returning the value stored in the data array instead checks for a prepended 'set', and checks to see if the value being set on the object is any different from the value in the data array, and if it is, adds the property name to the changedProperties array before setting the new value.

Categories