Creating a Laravel attribute (accessor) on model but unable to access model properties - laravel

Here's my code:
protected function expires(): Attribute
{
if ($this->started_at) {
$expiry = $this->started_at->addDays(20);
}
return Attribute::make(
get: fn () => $expiry ?? null
);
}
Running this code gives me an ErrorException with the message Undefined property: Models\Job::$started_at
I have found that I can work around this error by accessing the property through $this->attributes['started_at'] as follows:
protected function expires(): Attribute
{
if ($this->attributes['started_at']) {
$expiry = Carbon::parse($this->attributes['started_at'])->addDays(20);
}
return Attribute::make(
get: fn () => $expiry ?? null
);
}
However, this code feels a little inefficient because I'm manually using Carbon to parse the property back into a Carbon object. But if I do a dd($this->started_at) right before the if statement, it's already been cast to a Carbon object by Laravel and I'd really just like to use this object to make my code as clean as in the first example above.
I'd like to know the reason why $this->started_at is apparently available as a Carbon object in this context but somehow not usable (an undefined property) in the way I'm using it, and also I would like to know if there is another way to go about achieving my goal?

you can add custom attributes with
public function getExpireAttribute()
{
if ($this->started_at) {
$this->started_at->addDays(20);
}
return $this->started_at;
}
now you can access expire attribute like other, with
$model->expire
to make Eloquent casts dates to Carbon for you, add attribute to casts:
protected $casts = [
'started_at' => 'datetime',
];

The reason you are getting an "Undefined property" error when trying to access $this->started_at in your accessor method is because Laravel's model accessor methods are executed before the model attributes are hydrated.
This means that when your expires() method is executed, the started_at attribute may not have been set yet, and thus accessing it directly on the model instance will result in an "Undefined property" error.
One way to work around this is to use the getAttribute method provided by Laravel's Model class. This method allows you to retrieve the value of an attribute, even if it has not been set yet. Here's an updated version of your expires() method that uses getAttribute:
use Carbon\Carbon;
protected function getExpiresAttribute(): ?Carbon
{
$startedAt = $this->getAttribute('started_at');
if ($startedAt) {
return $startedAt->addDays(20);
}
return null;
}
In this version, we are using the getAttribute method to retrieve the value of the started_at attribute, even if it has not been set yet. We then use Carbon to manipulate the date, and return the result.
Note that we are using the getExpiresAttribute method instead of the expires method, because Laravel automatically maps get{AttributeName}Attribute method calls to corresponding attribute accessors. So, in this case, calling
$model->expires
will automatically execute the getExpiresAttribute method.
With this approach, you can use the started_at property directly in your code, and it will be automatically cast to a Carbon object by Laravel, without the need to manually parse it with Carbon.
Hope this helps.

Related

How do I convert an API resource class in Laravel 5.5 to an array BEFORE returning from controller?

Ordinarily, in Laravel 5.5, when using an api resource class, you simply return the resource class instance from your controller method, like so:
public function show(Request $request, MyModel $model)
{
return new MyModelResource($model);
}
This converts the model to an array (and ultimately to json) in the response to the client.
However... I am trying to figure out how to convert everything to an array BEFORE returning it from the controller method. I tried this:
public function show(Request $request, MyModel $model)
{
$array = (new MyModelResource($model))->toArray($request);
// ...
}
The problem here is that any relationships loaded on the resource aren't also converted to an array. They show up inside $array as an instance of a resource class. Obviously calling toArray() manually does not result in a recursive call, and methods such as ->whenLoaded('relationship_name') aren't really respected either.
So how do I get Laravel to do everything it does to convert the resource to an array recursively WITHOUT having to return it from my controller method?
I believe what you are looking for is the resolve method on the resource class. See definition.
From the looks of it, it should handle converting the relationships into an array as well. Just be sure you are setting up your resource relationships properly.
Neither the toArray() or resolve() methods convert the related models to arrays which is really annoying because you'd expect them to.
You're better off using toResponse(null) which will return a JsonRepsonse object. Which you can then use the getContent() method for a json encoded string or the getData() method for an object.
So if you wanted an array not wrapped in a data variable it would be:
$array = json_decode(
json_encode(
(new MyModelResource($model))
->toResponse(null)
->getData()
->data
),
true);
Ugly but it works unlike the accepted answer.

Laravel Error while retrieving a Model with a custom connection

I have a Model Correo with a custom connection that changes dinamically.
The problem is that when I want to retrieve results from the database like this: Correo::on(session('conexion'))->get(), session('conexion') has the connection name, the following error appears:
Call to a member function newCollection() on null
I can get the results using this: DB::connection(session('conexion'))->table('correos')->get(), but I need the Model's methods and the previous one just returns a generic Collection.
Thanks!
You can use setConnection function
$correo = new Correo;
$correo->setConnection('yourConnectionName');
$data = $correo->find(1);
dd($data);
So based on the session ( if you don't have that many remote connections )
if (session('xyz')) {
$correo->setConnection('xyz');
} else {
$correo->setConnection('pqr');
}
`
Well, I solved it, when I created the model I wrote every property and then created every getter and setter, apparently it didn't like the new setConnection setter. I don't know why, but it stopped me from using it.

laravel: function in model must return a relationship instance

I try to build a path for a model on laravel
I created a function in my model:
public function path()
{
return App\Helper\GeneralController::getURL($this);
}
with dd(App\Helper\GeneralController::getURL($this)) test I got the right answer. (output is a URL)
but in view with the call: $article->path I get this error:
App\Article:: path must return a relationship instance.
What is wrong?
You need to call it:
$article->path()
When you do $article->path, you're trying to use Eloquent relationship which you don't have.
I know this has already been answered and accepted. However, if the OP did want to use a property accessor rather than a method use the "get{property name}Attribute" syntax of Laravel to create a custom attribute.
Here is what it would look like for this specific case:
public function getPathAttribute()
{
return App\Helper\GeneralController::getURL($this);
}
using this approach "path" can now be called as an attribute and will not be resolved to a relationship using the syntax:
$article->path;
You're calling a relationship.
$article->path
To call the method, use '()', like so,
$article->path()
I faced that error when I forgot to write return before relation in the model!
check it out now!
path() is method not object element you need to call as method
$article->path();
Laravel 9 introduced a new way to define accessors/mutators within a model using Illuminate\Database\Eloquent\Casts\Attribute.
https://laravel.com/docs/9.x/eloquent-mutators#defining-an-accessor
public function path(): Attribute
{
return new Attribute(fn () => GeneralController::getURL($this));
}
For future visitors from Google, all the other answers can be applicable in certain scenarios, but you might want to also look if your method access modifier, if your method is protected and you try to call it you will be welcome with this error. You need change your method to public.

Yii2 virtual attribute getter had to change to function call

I'm new to OOP and Yii2. I have a function in Model:
public function getDatRev() {
if ($this->rev) {
return $this->rev;
} else {
return $this->datum;
}
}
in the View until now I have used it like this:
$model->datRev;
and it would return the correct value. Now I don't know what has changed, maybe I was also updated the framework, but the old construct doesn't work anymore, and in order to make it work I have to change it to:
$model->getDatRev();
Can you please explain to me why that is?
When you try get property the Yii2 calls magic method __get (). Return value is depend from implementation of this method in parent class. Yii2 can check if this property exist in some container, or if exist getter of this property.
In your case seems like you don't call parent's method __get(). This may have happened because you override __get() method or initialized this property.
Your class needs to extend yii\base\Object (directly or not) in order to use short property syntax ($model->abc instead of $model->getAbc()). Magic method __get() #Timur mentioned is defined there and further extended in yii\base\Component class.

How to acces is_* property in laravel model?

I am working with laravel 4.2 and have table in db with property is_active.
When I try to access this model property:
$model->is_active
I am getting following error:
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
So question is how to access this property?
Please do not recommend to rename this field in the database if possible because this is already existing database in production.
Here is my model class:
class Position extends \Eloquent {
protected $table = "hr_positions";
protected $fillable = ['slug', 'info_small', 'info_full', 'is_active', 'start_date', 'end_date', 'tags', 'user_create_id', 'user_update_id'];
use \MyApp\Core\StartEndDateTrait;
public function postulations(){
return $this->hasMany('Postulation', 'position_id', 'id');
}
}
Latest notice:
All this error ocurrs on a page where I am creating my entity. In the controller before forwarding to the page I am doing:
$position = new \Position();
and then, for example, following code produce error as well:
dd(($position->getAttribute('is_active')));
but if I replace $position = new \Position(); with
$position = \Position::first();
error is gone?
What is going on here?????
Laravel does a lot of magic behind the scenes, as in, calls a lot of php magic methods.
If a called property is not defined, __call is invoked which in Eloquent calls getAttribute().
Steps taken by getAttribute($key) are
Is there a database field by this key? If so, return it.
Is there a loaded relationship by this key? If so, return it.
Is there a camelCase method of this key? If so, return it. (is_active looks for isActive method)
Returns null.
The only time that exception is thrown is in step 3.
When you create a new instance, eloquent has no idea what kind of fields it has, so if you have a method by the same name, it will always throw a relation error, this seems to be the case in both Laravel4 and Laravel5.
How to avoid it? Use the getAttributeValue($key) method. It has no relation checks and returns null by default.
Alternatively you can also add a get mutator for your field.
I have found a hack for this. Still not ideal but at least I have some solution. Better any than none.
So This code produce problem:
$position = new \Position();
if($position->is_active){
//
}
and this one works fine, this is solution even hacky but solution:
$position = new \Position(['is_active' => 0]);
if($position->is_active){
//
}
I will wait if someone give better, cleaner solution. If no one comes in next few days I will accept mine.

Resources