Laravel getAttribute not appended to the model - php

I have added a accessor to my User.php model getArticlesCountAttribute
protected $appends = ['articles_count'];
public function getArticlesCountAttribute()
{
return (int) $this->articles()->count();
}
but when i access it from my laravel blade views articles count database query runs every-time i use the accessor in my view
For example: if i have Auth::user()->articles_count 3 times in my blade view it will run articles count query 3 times.
Shouldn't it be appended at the start and query should only run once no matter how many times i reference it in my views?

I think you should not cache or store it. It is still a model and a model should not have this kind of implementations.
You should restructure your view and pass the result of the count from the controller to the view.
public function myAction()
{
return view('myview', [
'articleCount' => Auth::user()->articles_count
]);
}
And in the view have than once the result in the variable $articleCount.

Try this one code
protected $hidden = ['articles_count'];
public function getArticlesCountAttribute()
{
return (int) $this->articles()->count();
}

You just have to access the attribute, not the method:
protected $appends = ['articles_count'];
public function getArticlesCountAttribute()
{
return (int) $this->articles->count();
}
This way if articles is not loaded it will be the first time :)

You could cache it, for example:
protected $articlesCounted;
public function getArticlesCountAttribute()
{
if (is_null($this->articlesCounted)) {
return $this->articlesCounted = $this->articles()->count();
}
return $this->articlesCounted;
}

Related

Laravel - hasOne relation based on model attribute

I have a problem, I need to make a relation based on an attribute from the model$this->type, my code is as follows:
class Parent extends Model {
protected $attributes = [
'type' => self::TYPE_A
];
public function child()
{
return match ($this->type) {
self::TYPE_A => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_A),
self::TYPE_B => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_B),
self::TYPE_C,
self::TYPE_D => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_C),
self::TYPE_E,
self::TYPE_F => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_D)
};
}
public function children()
{
return $this->hasMany(Cousin::class);
}
}
If I do this inside artisan tinker, I get the child relation correctly:
$parent = Parent::first();
$parent->child;
However if my queues execute the code the child relation is returned as null.
If I put logger($this) in the first line of the relation I get an empty model when inside queues:
[2021-09-29 23:51:59] local.DEBUG: {"type":1}
Any idea on how to solve this?
Apparently it is not possible to use the relationship in this way.
The relationship I want uses composite keys and Eloquent doesn't support it.
They created a package to enable this to happen: https://github.com/topclaudy/compoships
But I don't want to add any more packages to my application as it is already too big.
My solution was to add one more column in the cousins table to know which one to use:
public function child() {
return $this->hasOne(Cousin::class)
->where('main', true);
}
protected $attributes = [
'type'
];
public function getTypeAttribute() {
return $this->hasOne(Cousin::class)
->where('main', true);
}
Then call it anywhere
$parent->type
Or you can do it as follow
public function typeMainTrue() {
return $this->hasOne(Cousin::class)
->where('main', true);
}
Then call it anywhere
$parent->typeMainTrue

call model relation function in controller laravel

In my Model I have the function
App\Akte.php
public function lvs() {
return $this->hasMany('App\lvs', 'lv_id');
}
In my Controller I call
public function index()
{
$aktes = \App\Akte::all();
return view('admin.akte.index', ['aktes' => $aktes]);
}
And i´d like to extend my collection $aktes with the lvs table. Can somebody explain how to do this?
So my result should be a collection in which every single element of "Akte" has its Many collections of lvs in it..
If you also want the relationship loaded, just use:
$aktes = \App\Akte::with('lvs')->get();
See that. May be usefull for your needs.
https://laravel.com/docs/5.7/eloquent-relationships
public function index()
{
$aktes = \App\Akte::->all()->where('lvl', 2);
return view('admin.akte.index', ['aktes' => $aktes]);
}
somthing like this ...

How to change default Yii 2 Rest API GET response result

According to Yii 2 Rest APi documentation, I have a CountriesCountry that extends \yii\rest\ActiveController and a corresponding Countries model. This is the code for my Controller class.
<?php
namespace app\controllers;
class CountriesController extends \yii\rest\ActiveController{
public $modelClass = 'app\models\Countries';
public function actionIndex(){
}
public function actionView(){
}
public function actionCreate(){
}
public function actionUpdate(){
}
public function actionDelete(){
}
public function actionOptions(){
}
}
When I send a get request, it returns all the countries in my database.
My Question
is it possible to return my own result from action methods. Like in the actionIndex(), I will like to limit the result to 20 records. I did something like this but it is not working.
public function actionIndex(){
$model = Countries::find()->limit(20);
print_r($model);
}
I know that I can get all the countries from by database and loop through it and obtain only 20 results but I want to just query for 20 records from database.
Your class CountriesController that extends from \yii\rest\ActiveController automatically supports GET, PUT, POST calls etc. No need for actionIndex(), actionCreate(), etc if you just want regular REST functionality. Read about it in the Yii2 guide.
To limit the results you could just set another page size in your controllers afterAction-method. Add this to your controller. (I believe that 20 records is default of Pagination class, so if that is what you want you don't need this code at all. Just use the default functionality of yii/rest/ActiveController.)
public function afterAction($action, $result) {
if (isset($result->pagination) && ($result->pagination !== false)) {
$result->pagination->setPageSize(100);
}
return parent::afterAction($action, $result);
}
Suppose your api link is:
http://localhost/yii2-rest/api/country/?limit=15&order=id
Controller:
public function actionIndex(){
$model = Countries::find()
->orderBy($_GET['order'])
->limit($_GET['limit'])
->all();
return $model;
}
Take care about security!
You can get query string this way:
$limit = Yii::app()->getRequest()->getQuery('limit');
Straightway
//SELECT * FROM countries LIMIT 20
$countries= Countries::find()->limit(20)->all();

In my view, why does Laravel overwrite variables I declare in my controller with those from my model?

Am I understanding the MVC design pattern incorrectly? Why does Laravel seemingly overwrite variables I declare in my controller and pass to my view with those from my model? Say I'm passing the variable $journey from my controller to my view like so:
class JourneyController extends BaseController {
public function journey($id) {
$journey = Journey::find($id);
// I overwrite one of the attributes from the database here.
$journey->name = "Overwritten by the Controller";
return View::make('journey', array(
'journey' => $journey,
'bodyClass' => 'article'
));
}
}
But, I'm using an accessor to also modify the $journey->name attribute in my Journey model:
class Journey extends Eloquent {
protected $table = 'journeys';
protected $primaryKey = 'id';
public $timestamps = false;
public function getNameAttribute($value) {
return 'Overwritten by the Model';
}
}
So when my view is created, and I display $journey->name like so:
{{ $journey->name }}
I'm left with:
"Overwritten by the Model";
Why does this occur? Doesn't the controller handle a request, fetch information from my model, manipulate it, and then pass it to the view where it can be outputted? If this is the case, why, and also how, is the model seemingly 'jumping' in between to overwrite my controller-written variable with its own?
I know this is old, but I just found a solution on Laravel 4.2 today.
class Journey extends Eloquent {
protected $table = 'journeys';
protected $primaryKey = 'id';
public $timestamps = false;
public function getNameAttribute($value = null) {
if($value)
return $value;
return 'Overwritten by the Model';
}
}
You should update your getNameAttribute function as above to return the set value (if there is one) instead of always returning the string. Previously, calling this value would always run the function and ignore the set value, but now the function takes checks first for the value that you have set.
Hopefully 2 years isn't too late to still help some people!
Have a look at using Presenters, Take Jeffery Way's Presenter Package. Install it normally and then you can add the $presenter variable to your model.
For instance:
use Laracasts\Presenter\PresentableTrait;
class Journey extends Eloquent {
use PresentableTrait;
protected $presenter = "JourneyPresenter";
}
Then you can create your JourneyPresenter Class:
class JourneyPresenter {
public function journeyName()
{
return "Some Presentation Name";
}
}
In your view you can call this, like so:
<h1>Hello, {{ $journey->present()->journeyName }}</h1>
It really helps keep this sort of "presentation" logic out of your view and controller. You should try hard to keep your controller solely for its intended purpose, handling routes and basic guards and keep your views logic-less.
As for your problem, you may just be experiencing the natural order of Laravel operations.

Add a custom attribute to a Laravel / Eloquent model on load?

I'd like to be able to add a custom attribute/property to an Laravel/Eloquent model when it is loaded, similar to how that might be achieved with RedBean's $model->open() method.
For instance, at the moment, in my controller I have:
public function index()
{
$sessions = EventSession::all();
foreach ($sessions as $i => $session) {
$sessions[$i]->available = $session->getAvailability();
}
return $sessions;
}
It would be nice to be able to omit the loop and have the 'available' attribute already set and populated.
I've tried using some of the model events described in the documentation to attach this property when the object loads, but without success so far.
Notes:
'available' is not a field in the underlying table.
$sessions is being returned as a JSON object as part of an API, and therefore calling something like $session->available() in a template isn't an option
The problem is caused by the fact that the Model's toArray() method ignores any accessors which do not directly relate to a column in the underlying table.
As Taylor Otwell mentioned here, "This is intentional and for performance reasons." However there is an easy way to achieve this:
class EventSession extends Eloquent {
protected $table = 'sessions';
protected $appends = array('availability');
public function getAvailabilityAttribute()
{
return $this->calculateAvailability();
}
}
Any attributes listed in the $appends property will automatically be included in the array or JSON form of the model, provided that you've added the appropriate accessor.
Old answer (for Laravel versions < 4.08):
The best solution that I've found is to override the toArray() method and either explicity set the attribute:
class Book extends Eloquent {
protected $table = 'books';
public function toArray()
{
$array = parent::toArray();
$array['upper'] = $this->upper;
return $array;
}
public function getUpperAttribute()
{
return strtoupper($this->title);
}
}
or, if you have lots of custom accessors, loop through them all and apply them:
class Book extends Eloquent {
protected $table = 'books';
public function toArray()
{
$array = parent::toArray();
foreach ($this->getMutatedAttributes() as $key)
{
if ( ! array_key_exists($key, $array)) {
$array[$key] = $this->{$key};
}
}
return $array;
}
public function getUpperAttribute()
{
return strtoupper($this->title);
}
}
The last thing on the Laravel Eloquent doc page is:
protected $appends = array('is_admin');
That can be used automatically to add new accessors to the model without any additional work like modifying methods like ::toArray().
Just create getFooBarAttribute(...) accessor and add the foo_bar to $appends array.
If you rename your getAvailability() method to getAvailableAttribute() your method becomes an accessor and you'll be able to read it using ->available straight on your model.
Docs: https://laravel.com/docs/5.4/eloquent-mutators#accessors-and-mutators
EDIT: Since your attribute is "virtual", it is not included by default in the JSON representation of your object.
But I found this: Custom model accessors not processed when ->toJson() called?
In order to force your attribute to be returned in the array, add it as a key to the $attributes array.
class User extends Eloquent {
protected $attributes = array(
'ZipCode' => '',
);
public function getZipCodeAttribute()
{
return ....
}
}
I didn't test it, but should be pretty trivial for you to try in your current setup.
I had something simular:
I have an attribute picture in my model, this contains the location of the file in the Storage folder.
The image must be returned base64 encoded
//Add extra attribute
protected $attributes = ['picture_data'];
//Make it available in the json response
protected $appends = ['picture_data'];
//implement the attribute
public function getPictureDataAttribute()
{
$file = Storage::get($this->picture);
$type = Storage::mimeType($this->picture);
return "data:" . $type . ";base64," . base64_encode($file);
}
Step 1: Define attributes in $appends
Step 2: Define accessor for that attributes.
Example:
<?php
...
class Movie extends Model{
protected $appends = ['cover'];
//define accessor
public function getCoverAttribute()
{
return json_decode($this->InJson)->cover;
}
you can use setAttribute function in Model to add a custom attribute
Let say you have 2 columns named first_name and last_name in your users table and you want to retrieve full name. you can achieve with the following code :
class User extends Eloquent {
public function getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
}
now you can get full name as:
$user = User::find(1);
$user->full_name;
In my subscription model, I need to know the subscription is paused or not.
here is how I did it
public function getIsPausedAttribute() {
$isPaused = false;
if (!$this->is_active) {
$isPaused = true;
}
}
then in the view template,I can use
$subscription->is_paused to get the result.
The getIsPausedAttribute is the format to set a custom attribute,
and uses is_paused to get or use the attribute in your view.
in my case, creating an empty column and setting its accessor worked fine.
my accessor filling user's age from dob column. toArray() function worked too.
public function getAgeAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes['dateofbirth'])->age;
}

Categories