Have you had a look into Resources? Sounds like the exact use case for that (considering you return the models from an API).
Why is Eloquent's $visible, $hidden, etc not static?
I'm trying to understand the reasoning behind these decisions because we are running into issues when trying to modify them at runtime. I cannot think of a scenario where you would want two instances of a Model to have two different sets of visible attributes.
We have a model that is part of a response from two separate controllers. Each controller is showing a certain subset of the model's attributes (some are appropriate to be public, others need auth to view). Since the $visible array is not static, we are having to loop over every instance of the model and call its makeVisible() method individually, which is not efficient. I would expect this to be set at the class level, by calling MyModel::makeVisible() instead.
Can someone please explain the reasoning behind making this functionality non-static, and let me know if there's an approach I can use for this scenario that's more in line with Eloquent's design philosophy?
Thanks in advance.
We're currently on Laravel 5.3, which I don't think has Resources.
We implemented our own class-level visibility logic and overrode the existing visibility methods such as makeVisible, but that's obviously a deviation from Eloquent's intent. That's why I want to understand the reasoning behind the non-static properties. If I could understand the reasoning, I would be able to make a better decision on how to move forward.
I'm not sure about the official philosophy but I could totally think of reasons for different visibility on a per model basis. I work on a project where one party can "unlock" their contact data for conversations so I could use that logic to make the contact data visible depending on that "unlock status" (although I have solved that differently).
I'd say it gives you a lot more flexibility on a per-model basis with the drawback of more work for a few people.
You could have a public model which extends the base model and overloads visibility?
or check if user is auth within the model constructor?
by the way, you seem to have invented a cool new word 'Medology'
Every "Model" class extends the Illuminate\Database\Eloquent\Model (BaseModel) class, which is abstract. This class offers certain functionality that needs those variables out of the box. (In the latter versions, this functionality is grouped into traits). If the variables were declared static, then all your model classes that inherited from the BaseModel class would have to use the same values for these variables, because now they would belong to the BaseModel class, not to the instance of the objects created from the class. Yes, they could be changed at runtime, but you would have an even bigger headache, because you would really have to remember to change the values every time when the out-of-the-box underlying code needed to use them. For instance, you would have to change them every time your objects are sent over the wire in their JSON representation. A user may have a hidden password field, but credit card may have the exp_date, billing_zip and CVV fields hidden. You would be faced with the inspection of the class name every time you wanted to send the JSON representation of your models over the wire; in this case you would throw away all that functionality that comes out of the box and get for free.
That's not a problem at all. You can either use late static binding to access the extending model's static $visible array or if you're using a PHP version that doesn't have late static binding, you can use a function that returns the concrete model's static $visible array.
Our implementation uses the late static binding approach.
If the model instance specific visibility is functionality that's desired, it would be pretty trivial to create a hybrid approach where the class-level visibility list is either overridden or extended by the instance-level visibility list.
Here's an rough example of how you'd use late static binding to accomplish this:
<?php namespace App\Model;
use Illuminate\Database\Eloquent\Model as BaseModel;
class Model extends BaseModel
{
/** @var array The attributes that should be visible in model class. */
protected static $class_visible = [];
/**
* Make the given class, typically hidden, attributes visible.
*
* Class-level variant of the non-static makeVisible() method.
*
* @param array|string $attributes The class attributes to make visible.
*/
public static function makeClassVisible($attributes)
{
if (!empty(static::$class_visible)) {
static::addClassVisible($attributes);
}
}
/**
* Get the visible attributes for the class.
*
* Class-level variant of the non-static getVisible() method.
*
* @return array
*/
public static function getClassVisible()
{
return static::$class_visible;
}
/**
* Set the visible attributes for the model.
*
* Class-level variant of the non-static setVisible() method.
*
* @param array $visible The attributes to set visible.
*/
public static function setClassVisible(array $visible)
{
static::$class_visible = $visible;
}
/**
* Add visible attributes for the class.
*
* Class-level variant of the non-static addVisible() method.
*
* @param array|string|null $attributes The attributes to add in the class_visible array.
*/
public static function addClassVisible($attributes = null)
{
$attributes = is_array($attributes) ? $attributes : func_get_args();
static::$class_visible = array_merge(static::$class_visible, $attributes);
}
/**
* {@inheritdoc}
*
* Overridden to use the static 'getClassVisible' method instead of 'getVisible'.
*/
protected function getArrayableItems(array $values)
{
if (count(static::getClassVisible()) > 0) {
$values = array_intersect_key($values, array_flip(static::getClassVisible()));
}
if (count($this->getHidden()) > 0) {
$values = array_diff_key($values, array_flip($this->getHidden()));
}
return $values;
}
}
class User extends Model {
protected static $class_visible [
'visible_property_1',
'visible_property_2',
'visible_property_3',
];
}
class CreditCard extends Model {
protected static $class_visible [
'visible_property_a',
'visible_property_b',
'visible_property_c',
];
}
I was thinking about the same. Actually all those Eloquent Model property variables could and from today's perspective should be static, since they describe the class, not the instance, so they should be part of the class (i.e. static). But I think they are not static because late static binding first appeared in PHP v5.3, whereas Laravel up to v3.2 was compatible with earlier PHP versions. So this means ever since Laravel v4 these could have been switched to be static, but I'd assume at no point in time they wanted to make such a backward incompatible change, as for such change anyone upgrading from an older version would have to change all their models and methods. To improve compatibility the first time a Model is instantiated the parent constructor could use Reflection to find any instance properties that are present and match the known now static properties which have no value and just assign them to the static counterpart providing a buffer for those who don't want to change all their model,s but still that may have compatibility issues if for example someone was setting these from their own constructor or an any other non standard way.
Interestingly properties I assume added to later are declared static, e.g. GuardsAttributes::$fillable is instance, but GuardsAttributes::$unguarded is static.
It'd be nice to have all these as static, but probably rightfully Laravel don't want to introduce a huge breaking change just for niceness.
There could be an argument about smaller memory footprint, but with the HW available today those instance properties are not making any difference. Also it may not be a benefit for everyone, as static properties are allocated at the time the class is loaded, so someone with an application typically working with e.g. 10 model instances, but having 100 different models using static would consume more memory, not less. Although the differences are insignificant.
EDIT: Doing some more search I've found GitHub issues and threads where people share lots of ways they dynamically set these properties during runtime for individual model instances and static would not give them that ability.
EDIT: Doing some more search I've found GitHub issues and threads where people share lots of ways they dynamically set these properties during runtime for individual model instances and static would not give them that ability.
I would need more info on exactly why they need to do that, but my initial reaction is that it sounds like an anti-pattern. Our current approach is to not rely on these arrays any longer, and instead use API resources. (I wanted to post a link to the Laravel docs for this, but this site won't let me post links). This avoids the whole problem of them not being static, while also providing the ability to have different visibility constraints depending on things like if the user us authenticated and what their role is. It's also a better enforcement of SRP IMHO.
Oh, they seem to be an anti-pattern for sure to me too, but apparently people do this all the time, so from that perspective it is reasonable that Laravel don't want to ruin the game for them, as for any framework not listening to the actual users is always bad in the long run.
Look around in this topic and the linked ones: github dot com laravel/ideas/issues/945 (does not allow me to link)
I've also seen another long discussion where people shared their usecases for non static model properties, like how they change table name for different instances to store them in separate tables and these kind of weird stuff.
Please or to participate in this conversation.