Laravel accessing collection attributes and model attributes from reusable view - php

I have a view that I'm trying to re-use for two different actions to display data from the database. For one of those actions, an Eloquent collection object is passed to the view, and data is retrieved with
#foreach($buildings as $key=>$value)
{!! $value->build_name !!}
Obviously 'build_name' is a column in the table. So far simple..
Now I need this same view to display data that requires a lot of processing and it's not possible to generate an eloquent statement to pass to the view.
In order to re-use the $value->build_name code, I'm assuming I have to still pass an object (model??) to the view.
I have a Building.php Model
class Building extends Model
{
protected $fillable =[
'buildingtype_id',
'build_name',
];
and I'm thinking I could just add public $build_name; to the Building model, but then I should also add a method to set and get the $build_name. So my Building Model will now look like..
class Building extends Model
{
public $build_name;
protected $fillable =[
'buildingtype_id',
'build_name',
];
public function getBuildName () {
return $this->build_name;
}
public function setBuildName ($name) {
$this->build_name = $name;
}
And I can just create the object myself in the controller...
If I do this, is {!! $value->build_name !!} still appropiate for the view? Or should I now be using {!! $value->getBuildName() !!}
Or am I missing a key concept somewhere? I'm still new to Laravel and OOP.
Edit
I just implemented this, and it's not working. If I add the public $build_name attribute to the model, getBuildName does not return anything, however if I remove public $build_name it does... (which would break my attempting to create that object manually)

When you declare public $build_name, this will override (or more precisely, reset) any other field with the same name in the model. So, you'll have to call setBuildName() setter method before you get it.
I just implemented this, and it's not working. If I add the public $build_name attribute to the model, getBuildName does not return anything
That's because you've called the getter method before the setter, so there is nothing (null) set in the public variable $build_name.
Although you haven't quite mentioned why exactly you want to reuse the Eloquent model, but you can achieve your desired purpose with a little tweak on the model's setter methods:
class Building extends Model
{
/* ... */
public function setBuildName ($name) {
$this->build_name = $name;
return $this;
}
}
Notice returning the current object ($this) in case you would want to chain multiple setter methods in one go.
e.g.:
$model->setBuildName('some name')->setBuildHeight(400);
UPDATE
You can use the default eloquent model to serve your purpose (hence, ridding you of making a duplicate class to achieve roughly the same effect).
Now suppose you have your model Building and would like to set it's attributes manually, then, the following operation on the model is still appropriate:
$building = new App\Building();
$building->build_name = 'Some Building Name'; // you can substitute this with your setter method as well
$building->build_height = 110; // assuming you have a column named `build_height` in your model's table
Note that the only difference in what you'd be doing here is:
You DON'T declare public variables at all in the Eloquent model.
You don't call Eloquent's save() method on the model to persist the manually set data (giving it a transient behavior).
The model now is totally eligible to be passed to your view and you can access it's attributes as you would with a regular model:
<!-- in your view -->
{{ $building->build_name }}
{{ $building->build_height }}
As an alternative approach to setting your arbitrary data, you can have a single setter which accepts an array of key value data to be stored in the model (e.g. ['build_name' => 'Building Name', 'build_height' => 110]):
//in class Building
public function setData($data){
$this->build_name = $data['build_name'];
$this->build_height = $data['build_height'];
}

Related

can i add new key in models?

I want to add a key while I'm getting any response of an API using Models in laravel,
Currently, I'm developing an API to get a response with a new one key to add to dynamically.
If I'm not wrong I guess you are trying to create a new field which is not present in table but need to be created dynamically with some logic inside it and you want to pass that field to each and every product of yours.
If so than I guess Laravel Eloquent Accessor will be a best option to use in this case. With the help of Accessor you can create any kind of field and can call it as same as we are calling others fields.
for more reference please take a look at this https://laravel.com/docs/5.7/eloquent-mutators Laravel Documentation.
A sample picked from Laravel Documentation.
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
Now I can use full_name inside my controller or blade files like $user->full_name and than it will give me the concatenated string of first_name and last_name.
Add the attribute to $appends.
class MyModel extends Model {
...
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['extra'];
public function getExtraAttribute(){
return "Extra Value";
}
...
}
first of all in your model add this to add custom field
protected $appends = ['is_favorite'];
public function getIsFavoriteAttribute($value)
{
return $this->name; // whatever you want add here
}
But it problem while you getting other models details while applying relations and select from relation.

Laravel belongs to many with additional methods

For followers relation on same User model I used belongsToMany()
public function followers() {
return $this->belongsToMany('App\User', 'followers', 'follow_id', 'user_id');
}
But since I am using this for chat list on load with vue I am on page load passing json_encode(auth()->user()->followers) which works as I needed.
But when I am lets say using only some columns like:
->select(['id', 'name', 'avatar']);
I have additional method for avatar:
public function image() {
return $this->avatar || 'some-default-image';
}
How can I pass that as well for each of many? Without withDefault method..
Try adding this in your User model
class User extends Model {
protected $appends = ['image'];
// other stuff..
}
this will forcefully inject your computed property ie. image in every User model instance but for it work you have to name your method (or create another) like getImageAttribute()and not simply image()
// getter for `image` property in user model object
public function getImageAttribute() {
return $this->avatar || 'some-default-image';
}
What you are looking for is the Accessor function:
Laravel Accessors and Mutators
Basically you define an accessor function in your model:
public function getAvatarAttribute($value)
{
return $value || 'some-default-image';
}
Then when you access the avatar property using ->avatar, the accessor function will get called and you will get the computed value.
====================================================================
The comment has words limit.
You have followers table where each follower is a User. You use relationship to filter all followers which are a group of Users. You wanted getInfo() to be called on each follower so that the additional data is appended to your JSON structure.
In that case, you don't need to filter through each follower and call getInfo() yourself. You use accessor method, put your code in getInfo() into an accessor method, and modify $appends array in your User model, then then JSON data will be automatically appended.
public function getUserInfoAttribute()
{
$userInfo = ... //use logic in your original getInfo method
return $userInfo;
}
Then you add user_info into your User model's $appends array:
protected $appends = ['user_info'];
This way, your user_info will be automatically included when your instance is serialized into JSON.
Like I said in the comment, you should check out:
Appending Values To JSON
for more information.
As to APIs , whether you are using Vue or React or anything, when passing JSON data for your frontend code to consume, you are basically creating apis. FYI:
Eloquent: API Resources

Force create an eloquent model through a relationship

In a small project with Laravel 5.3 and Stripe, I am trying to force create a Subscription on a User through a hasOne relationship:
// User.php
public function subscription() {
return $this->hasOne('App\Subscription');
}
public function subscribe($data) {
return $this->subscription()->forceCreate(
// $data contains some guarded fields;
// create() will simply ignore them...
);
}
However, I get :
Call to undefined method Illuminate\Database\Query\Builder::forceCreate()
Even though forceCreate() is a valid Eloquent method.
Any ideas how I can simulate this behavior? Or should I just save a Subscription manually assigning each field? The complication is that certain fields should be kept guarded, e.g. stripe_id.
EDIT
My quick' n' dirty solution:
// User.php # subscribe($data)
return (new Subscription())
->forceFill([
'user_id' => $this->id,
// $data with sensitive guarded data
])
->save();
I'm sure there is a better way though!
The call to $this->subscriptions() returns an instance of HasMany class, not a Model.
Contrary to create and createMany, there's no forceCreate() implemented in HasOneOrMany class. And thus, there's no first-hand API to use in such situations.
You see? when you call $this->subscriptions()->create(), you're calling the create method on a HasMany instance, not a Model instance. The fact that Model class has a forceCreate method, has nothing to do with this.
You could use the getRelated() method to fetch the related model and then call forceCreate() or unguarded() on that:
public function subscribe($data) {
return $this->subscription()
->getRelated()
->forceCreate($data);
}
But there's a serious downside to this approach; it does not set the relation as we're fetching the model out of the relation. To work around it you might say:
public function subscribe($data)
{
// $data['user_id'] = $this->id;
// Or more generally:
$data[$this->courses()->getPlainForeignKey()] = $this->courses()->getParentKey();
return $this->courses()
->getRelated()
->forceCreate($data);
}
Ehh, seems too hacky, not my approach. I prefer unguarding the underlying model, calling create and then reguarding it. Something along the lines of:
public function subscribe($data)
{
$this->courses()->getRelated()->unguard();
$created = $this->courses()->create($data);
$this->courses()->getRelated()->reguard();
return $created;
}
This way, you don't have to deal with setting the foreign key by hand.
laravel/internals related discussion:
[PROPOSAL] Force create model through a relationship

How to override result of hasMany relationship?

Imagine I have a couple of simple objects like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function posts()
{
return $this->hasMany("App\Post");
}
}
class Post extends Model
{
public function user()
{
return $this->belongsTo("App\User");
}
}
We'll say the \App\Post object has an database column called jsondata which contains JSON-encoded data. When I want to display the user's posts in a view with that column decoded, I need to do this in the controller:
$posts = Auth::user()->posts()->get();
foreach ($posts as $post) {
$post->jsondata = json_decode($post->jsondata);
}
return view("user.show", ["posts"=>$posts]);
Is there a way to avoid that foreach loop in my controller and do the JSON decoding at a lower level?
I'm sure I could do this in App\User::posts() but that doesn't help other places where I need to display the decoded data. I tried defining App\Post::get() to override the parent method, but it doesn't work because hasMany() doesn't seem to return an instance of the model at all.
It can be done in different places/ways, but I would suggest to use an append for this property in your model if you want this data is always decoded everywhere and every time you retrieve a Post model, or simply a mutator.
see https://laravel.com/docs/master/eloquent-mutators
In your model you can define:
protected $appends = [
'name_of_property'
];
// calculated / mutated field
public function getNameOfPropertyAttribute()
{
return jsondecode($this->jsondata);
}
You then can always access this property with:
$post->name_of_property
Note the conversion from CamelCase to snake_case and the conversion from getNameOfPropertyAttribute > name_of_property. By default you need to respect this convention to get it working automagically.
You can substitute the name_of_property and NameOfProperty with what you want accordingly.
Cheers
Alessandro's answer seemed like the best one; it pointed me to the Laravel documentation on accessors and mutators. But, near the bottom of the page is an even easier method: attribute casting.
In typical Laravel fashion, they don't actually list all the types you can cast to, but they do mention being able to cast a JSON-formatted database column to an array. A little poking through the source and it turns out you can do the same with an object. So my answer, added to the App\Post controller, is this:
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = ["jsondata"=>"object"];
This automatically does the decode and makes the raw data available. As a bonus, it automatically does a JSON encode when saving!

How do I Bind an Instance of a Model to a Parameter in a Controller Action

Is it possible to automatically bind an instance of a model to a parameter in a controller action? Is their any workaround for doing this if it does not already exist within Yii itself?
I know this is possible in Laravel and ASP.NET MVC. Here is what I want to achieve:
class PostController extends Controller {
public function actionEdit(Post $post) {
if($_POST['Post']){
$post->attributes = $_POST['Post'];
$post->save();
}
$this->render('edit', array('post'=>$post));
}
}
Given a url like localhost/?r=post/edit&post=1
[eg Yii::app()->createUrl('post/edit',array('post'=>$mypost->id))] the id 1 is converted to an instance of CActiveRecord [i.e. Post::model()->findByPk(1) is called automatically]
from your snippet it seems you are trying to pass both a $_POST and $_GET variable to your action.
Values in $_POST,$_GET are stored as associated arrays not objects themselves.
In Yii you can't do that directly load the model, however you can achieve your objective like this.
class PostController extends Controller {
public function actionEdit($post) {
$postModel = Post::model()->findByPk($post)
$this->render('edit', array('post'=>$postModel));
}
}
This is slightly longer but more useful if you want to bind additional conditions for example allow editing only is post is not deleted, or is active etc. You can use then findByAttributes, or findAll with additional conditions which may be implicit and not necessarily passed as parameter
Alternatively if you really required such a functionality you can write a component class with a custom action, where you can load a model as you wish
<?php
class LoadModel extends CComponent {
public function loadModel($type,$id){
if(!in_array($type,$arrayofpossibleModels || !is_numeric($id)){ //Validation if $type is a model name is required.
throw new CHttpException("400","Bad Request")
}
return $type::model()->findByPk($id);
}
}

Categories