Retrieve fillable fields in Laravel - php

In laravel 5.4, I'm able to retrieve fillable fields by using fillable index of model instance.
$model = new AnyClass();
dd($model['fillable']);
The above code prints all fillable fields of AnyClass. But the same code prints null on laravel 5.6. I know I can retrieve fillable fields using $model->getFillable(). My question is what is the reason / why it is not working in laravel 5.6 but works in 5.4?

From the upgrade guide here I believe this is the answer to the question:
Model Methods & Attribute Names
To prevent accessing a model's private properties when using array access, it's no longer possible to have a model method with the same name as an attribute or property. Doing so will cause exceptions to be thrown when accessing the model's attributes via array access ($user['name']) or the data_get helper function.

If you look at Laravel's source code you'll see the difference.
The Model class, which is extended by the application models, implements the ArrayAccess interface, which, among others, force the class to define the offsetGet method.
In Laravel 5.4 the offsetGet method looks like:
public function offsetGet($offset)
{
return $this->$offset;
}
which means that if you call $model['fillable'], you actually call $model->offsetGet('fillable') which actually returns the fillable property of the class.
I couldn't find the Laravel 5.6 tag but I'm pretty sure it is the same code as Laravel 5.5.45. In this version the offsetGet method was changed to:
public function offsetGet($offset)
{
return $this->getAttribute($offset);
}
which means that it actually returns the attribute if found or null otherwise.

In Laravel 7, I'm doing this by calling the getFillable method on a new instance of my model. Like so:
$model = new MyModel();
$fillable = $model->getFillable();

Late to the party but I don't like the concept of having to always instance a Model, specially if you're using Eloquent serialization.
Let's say you wanted to build some filters, but wanted to whitelist the columns based on the model's fillable. You don't want to instance an entire model, so you can instead use reflection:
(new ReflectionClass(MyModel::class))->getDefaultProperties()['fillable']
See it working over at 3v4l.org - Here I demonstrate why you potentially wouldn't want to instance this model due to having serialization and always eager loading.

Change the property in the class to public $fillable = [ instead of protected $fillable = [

Related

What happened to Laravel's Eloquent public static function "create" in Model.php?

In previous versions of Laravel 5.x (I'm not sure when it was changed) I was able to call static method create on any Eloquent Model class to insert records into a database.
For example:
EloquentUser::create([
'name' => self::ADMIN_NAME,
'email' => self::ADMIN_EMAIL,
'password' => bcrypt(self::ADMIN_PASSWORD),
]);
That was calling public static function create in Model.php (vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php).
public static function create(array $attributes = [])
{
$model = new static($attributes);
$model->save();
return $model;
}
In Laravel 5.5 I'm still able to call create however Model.php is totally rearranged and does not contain this method. What's more important, searching within whole vendor / Illuminate gives me nothing like that. Please explain, how it still works, what it calls behind the scenes.
Thanks.
Eloquent's _call and _callStatic are forwarding calls to an Eloquent Builder instance. The create method was moved out of the Model and into the Builder.
Illuminate\Database\Eloquent\Model::__callStatic -> __call -> newQuery -> Illuminate\Database\Eloquent\Builder#create
Model uses QueryBuilder which uses EloquentBuilder where method's code is. Best way for finding particular properties or methods is to use framework api docs.

Model Contracts/Interfaces in Laravel 5

What is the most Laravel way to impose an interface or contract on a factory mock of a model?
For example, let's say I have a model Dog. I use a factory to mock it, and I write a test for it. I want to make sure that if I mock an instance that does not conform to the contract/interface, an error is thrown.
\App\Dog.php:
class Dog extends Model
{
protected $table = 'dogs';
protected $fillable = ['name', 'breed' ]; // Dogs must have both of these
}
database/factories/ModelFactory.php
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name // no mention of breed, which is required
];
});
tests/DogTest.php
class RoadshowUnitTest extends TestCase
{
/** #test **/
public function i_should_be_able_to_make_a_dog()
{
$dog = factory(App\Roadshow::class, 1)->make();
// ... internal type assertions go here
}
}
When I run this test, I should get some kind of error; the factory should not be able to produce a Dog at all, since it is missing an important field. How do I impose the requirement on the Model, and anything that uses it (like the factory)?
The model factory just creates a new instance of the model object you pass it and assigns values to the attributes that you specify (in this case, just the 'name' attribute). Just as you can do $user = new User; and instantiate an empty user object, your factory can have as few attributes as you want when you run make.
To ensure that a persisted instance of that model class has the appropriate data attributes, you can: add validation logic that validates request data (in a custom request class or in your controller), or apply constraints to your database table that holds the model data. Ideally you would do both.
Using the make command is the same as doing new Model, meaning the data is not saved to the database yet (if you want it persisted you can do save() after make(), or use create() in place of make()). Therefore, any database constraints you have in place will not come into play. Similarly, since it's not an HTTP request (and not calling a controller method), any validation logic you have is also not being hit.
Sorry this is kind of wordy - short answer is that the model factory itself does not validate your data.

Laravel accessing collection attributes and model attributes from reusable view

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'];
}

Can Eloquent model observer access model data that are being affected by the event?

I am trying to create an observer for an Eloquent model to work as a logger for the changes on that model. I am not sure what parameters are passed to the observer's methods from the model. If the parameters don't include an instance of the updated model, is there another way around it without having to redefine the model class and override the needed methods?
class UserObserver{
public static function saved($user){
Activity::create([
"activity-name" => "save",
"user" => $user->id
]);
}
}
I found out that the model is actually passed, my mistake was not adding user property to the fillable array in the Activity model.
usually, I get an exception when my application tries to update fields that are not included in the fillable array, but this time I didn't. anybody knows why?

Temporary property for Laravel Eloquent model

I have a Laravel Eloquent model User, which has a table with username and email columns. I need to add a property for the model on runtime, something like $user->secure. This property doesn't need to go to database.
When i add this property and hit $user->save() i get an error saying i don't have database column 'secure'. I can unset 'secure' before save but somehow it feels there should be a better way to do so. Any suggestions?
Just add an attribute to your class.
class User extends Eloquent {
public $secure;
// ...
}
Note that it is better to declare it protected and add corresponding accessors and mutators (getSecure and setSecure methods) to your model.
For those who still find this post (like I did), you may need to explicitly declare the property as null to prevent it from being automatically added to the attributes array, and thus being added to INSERT/UPDATE queries:
class User extends Eloquent {
public $secure = null;
// ...
}
Source: http://laravel.io/forum/02-10-2014-setting-transient-properties-on-eloquent-models-without-saving-to-database
If you intend to make use of that add-on property, then $append will blow your mind.
http://laraveldaily.com/why-use-appends-with-accessors-in-eloquent/

Categories