laravel / ideas

Issues board used for Laravel internals discussions.
940 stars 28 forks source link

Refactor HasAttributes so it can be used 'standalone' #1196

Open axyr opened 6 years ago

axyr commented 6 years ago

Related to https://github.com/laravel/ideas/issues/965

I regularly have models that do not come from a database, but i want to behave like an Eloquent model, by having accessors to format attributes or add calculations to them.

Some use cases are:

Using the HasAttributes trait on a POPO is also very convenient to access arrays with $model->attribute instead of $array['key'], which involves a lot of isset() or array_get() calls.

Unfortunately pulling in HasAttributes alone is not enough.

I had to make a seperate trait like this, to pull in HasAttributes behaviour on a plain object:

<?php

namespace App\Whiteboards\Traits;

use Illuminate\Database\Eloquent\Concerns\HasAttributes;
use Illuminate\Database\Eloquent\Concerns\HasTimestamps;
use Illuminate\Database\Eloquent\Concerns\HasRelationships;

trait ActsAsModel
{
    use HasAttributes, HasRelationships, HasTimestamps;

    /**
     * @return array
     */
    public function getDates()
    {
        return [];
    }

    /**
     * @return bool
     */
    public function getIncrementing()
    {
        return false;
    }

    /**
     * @param  string $key
     *
     * @return mixed
     */
    public function __get($key)
    {
        return $this->getAttribute($key);
    }

    /**
     * @param  string $key
     * @param  mixed  $value
     *
     * @return void
     */
    public function __set($key, $value)
    {
        $this->setAttribute($key, $value);
    }

}

Which is - among others - used in a model to display json stored data like this:

<?php

namespace App\Models\BusinessReports;

use App\Whiteboards\Traits\ActsAsModel;

class Data
{

    // Here I would prefer to just pull in HasAttributes...
    use ActsAsModel;

    /**
     * @param array $attributes
     */
    public function __construct($attributes = [])
    {
        $this->attributes = (array)$attributes;
    }

    /**
     * @return int
     */
    public function getTotalJobsAttribute()
    {
        return count($this->jobs);
    }

}

Example use:

<?php

namespace App\Models;

use App\Pdfs\BusinessReportPdf;
use App\Models\Traits\HasConsultant;
use App\Models\BusinessReports\Data;

class BusinessReport extends Model
{
    use HasConsultant;

    /**
     * @return \App\Models\BusinessReports\Data
     */
    public function getDataAttribute()
    {
        $data = isset($this->attributes['data']) ? json_decode($this->attributes['data'], true) : $this->defaultData;
        return new Data($data);
    }
}   

The problem I have is that to use HasAttributes, I need to pull in other traits as well and adding the methods getDates, getIncrementing, __get and __set.

How would you think of refactoringHasAttributes, so it can used standalone to add 'attributes' behaviour to arbitrary models?

vaites commented 5 years ago

Have the same issue. I created a Pseudomodel class which uses HasAttributes and HasTimestamps traits and all the methods needed to make it work. The goal for me is to have accesors, mutators and type casting (relations are more complex here).

Making this traits standalone must allow us to use it without define anything else. Anyway, I think get/set/__unset methods must not be included in any trait.

musicin3d commented 4 years ago

+1 "Models" don't always need to be backed by a database. Sometimes a standalone class can be useful to model something. HasAttributes could make such models more eloquent.

jiimka commented 4 years ago

After working with Yii2 for quite a while (where we have Model class for the same purpose), it seems to me quite annoying that Laravel is missing such a simple and needed feature. AFAIU, Taylor has his own opinion on that - but it definitely doesn't 'make developers happy' :( Maybe, we should think about making it as a separate package - since a similar PR was rejected by Taylor...https://github.com/laravel/framework/pull/22946

MasonSpeed commented 4 years ago

I know it's not quite the same but maybe https://github.com/calebporzio/sushi might work for these use-cases?

jiimka commented 4 years ago

I know it's not quite the same but maybe https://github.com/calebporzio/sushi might work for these use-cases?

Looks like this guy was thinking about something in a similar direction, but I don't think his package covers my (and most common) scenarios. There is still need of a different solution, as to me.

Radiergummi commented 3 years ago

FWIW, we maintain an Elasticsearch integration for Laravel which provides Models with (near) feature parity to Eloquent. I used most of the Eloquent traits, which is working, but I'll admit there were some annoyances with the default assumption of Eloquent everywhere.
If you're interested in how this works, check out the Elasticsearch Model source - maybe it helps in implementation. I wouldn't hang my hopes too high this will ever be resolved, though -- Laravel is pretty settled on the topic.