Laravel Models - add non-database field - php

I have a model in Laravel called Checkout. It is just tied to a table in the database called checkouts.
namespace App;
use Illuminate\Database\Eloquent\Model;
class Checkout extends Model
{
protected $primaryKey = 'id';
protected $table = 'checkouts';
}
What I would like to do is add a field to the model that isn't a field in the table. Is this even possible?
If need be, I will completely manually build the model, but I have never seen any examples of that either.
Any help would be greatly appreciated! Thanks,

You can use Laravel's Accessor as:
public function getSomeExtraFieldAttribute()
{
return 2*4; // just for exmaple
}
Then you can access it using
$checkout = App\Checkout::find(1);
$checkout->some_extra_field;

Related

spatie/laravel-medialibrary change primary key

I'm using the package spatie/laravel-medialibrary and I want to change the primaryKey on their modal called Media, without editing the package src file.
In my project, I'm using uuids as primary keys for all my models, so naturally, I want to do the same thing for the Media.php model offered by this package.
I already changed the migration to reflect that, by removing the line $table->bigInteger('id') and changing the line $table->uuid('uuid')->nullable(); to table->uuid('uuid')->unique()->primary();
However, now I also want to let the model know I'm using a different key, by setting up protected $primaryKey = 'uuid'; and protected $keyType = 'string'; but I can't find a way to do this outside the packages src file for the Media.php model
Basically, what I want to end up doing is just implementing the HasMedia interface and using the InteractsWithMedia trait on my Profile model, like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Profile extends Model implements HasMedia
{
use InteractsWithMedia;
}
Any suggestions on how to achieve this?
Thanks.
Spatie's medialibrary package gives you the option to use your own media model, as described in their docs.
Just create your custom model and extend the library's Media model. You can then modify that csutom model to fit your needs.
use Spatie\MediaLibrary\MediaCollections\Models\Media as BaseMedia;
class Media extends BaseMedia
{
protected $primaryKey = 'uuid';
protected $keyType = 'string';
public $incrementing = false;
// ...
}
Remember to set the media_model key in config/media-library.php to your model's FQCN.
'media_model' => App\YourMediaModel::class,

Extending Eloquent models, more magic methods

Situation: In my work they have conventions on their database, table and column names, which are a bit long and repetitive. Being used to Eloquent I figured it wouldn't be much trouble to reimplement __get and __set methods, and not making lots of getters. Something like this (toConvention implements company's conventions):
use Illuminate\Database\Eloquent\Model;
class CompanyModel extends Model {
public function __get($key){
return $this->getAttribute($this->toConvention($key));
}
public function __set($key){
return $this->setAttribute($this->toConvention($key));
}
}
Which works well, for retrieving attributes, but not for retrieving relationships. Here are the implementations:
use App\CompanyModel as Model
class Location extends Model
{
protected $table = 'tablename';
protected $primaryKey = 'primarykeycolumn';
//...
public function comissionCurrency(){
return $this->belongsTo('App\Currency', 'foreign', 'other');
}
}
use App\CompanyModel as Model
class Currency extends Model
{
protected $table = 'tablename';
protected $primaryKey = 'primarykeycolumn';
//...
}
When requesting for attributes, like $location->name, or $location->comission_currency_id everything works as expected, retrieving the corresponding column name. But when I try to retrieve the belongsTo relationship, after using toSql() I see almost the correct query formed: select * from table where table.column is null the is null part should be comparing with the corresponding id.
I know it's due to my implementation, because when I use Illuminate\Database\Eloquent\Model everything works ok. Funny thing is that when I use Eloquent's model on the child model and the reimplemented on the other, it works to (but I'm not able to use my magic methods in the child model, in the parent one fetched from the relationship I can...)
I haven't figured out which method to reimplement to make this work by reading Eloquent's code, any ideas, or suggestions?
Thanks in advance.

Laravel on some kind of Model Ready method

Well i don't know how to format the title of this post in very clear way, but here's my question:
Say i have
Posts::find('1);
Photos:find('1');
... and so on, every mode db request
now by default i can access db columns, for instance the id: through model->id
$Photos = Photos::find('1')->first();
echo $Photos->id; // will return 1
what i want is that i need all those kind of requests to add a custom field automatically like hashed_id, which is not in the database, which in return will make all models have a hashed_id as well, i know i can add that field to database and then grab it but i need it for different reasons/implementations
i did create a BaseModel and every Model will extend that BaseModel, so Photos extends BaseModel, BaseModel extends Model... and all that etc etc.
but i need some kind of constructor, upon retrieving data to process the data automatically without having to add -let's say- a hash_id() after retrieving the data.
something like, onAfterGet(), onReady()....sort of commands.
i hope my question is clear.
Thanks.
What you're looking for is an Accessor. Accesors can be used to add custom attributes to the model. Combine this with the $appends property and you have exactly what you need. The $appends property adds the custom accessor in every result.
You can do this by creating a base model like you've stated in the question or by using traits. I'll show you an example on how to achieve this using a base model.
Let's create base model called BaseModel. All other models that need this custom attribute will extend this.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
protected $appends = ['hashed_id'];
public function getHashedIdAttribute()
{
return some_hash_function($this->id);
}
}
We have a Image model which extends our BaseModel.
<?php
namespace App;
class Image extends BaseModel
{
}
Now every result from the Image model will have the hashed_id field added by default.
Accesor documenation https://laravel.com/docs/5.4/eloquent-mutators#defining-an-accessor
If I understand you right, all you need to do is to define mutator, for example:
<?php
class Photo extends Model
{
/* ... model implementation ... */
public function getHashedIdAttribute()
{
return md5($this->id);
}
}
Then you can access property like it was in database:
echo Photo::find(5)->hashed_id;

Laravel Dynamic Fillable in Models

Got stuck in a issue with laravel 5.2.
Following is the error during eloquent create operation(post call),
Mass Assignment Exception in Model.php 453: column_name
Following are the prerequisites, which are to be taken into consideration:
Fillables in model are filled in a dynamic manner by the following code:
public function __construct() {
$this->fillable(\Schema::getColumnListing($this->getTable()))
}
Following are the methods which are debugged till now:
Before insertion, in controller, $model::getillableField(), gives proper fillable array.
In model.php line(450),
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
}
the above code returns the value as "false" and $model::getFillableField() has the column_name in the array list.
Hardcoding $fillable variable with columns of table removes the error.
Please Help, where i am going wrong and what is the solution for it?
Thanks in advance.
What you are really trying to do is make ALL fields fillable.
The correct way to do this in Laravel is this:
protected $guarded = [];
This works in 5.2, even though the documentation for it is found in 5.3.
(relevant source code for 5.2)
(Documentation from 5.3):
If you would like to make all attributes mass assignable, you may define the $guarded property as an empty array:
By setting $guarded to an empty array, you are creating an empty black list, allowing all fields to be mass assignable.
Also, if this model is ever going to be constructed directly from user input, please do not do this. Laravel requires either $fillable or $guarded to be defined for a reason. Unless your model has fields that are literally 1:1 with a public form, then allowing all fields to be writable on mass assignment is a security vulnerability.
Try this.
Put the below code in your model,
public function __construct()
{
$this->setFillable();
}
public function setFillable()
{
$fields = \Schema::getColumnListing('table_name_here');
$this->fillable[] = $fields;
}
This makes each and every column is fillable from that table.
Create a trait that uses the database columns.
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Schema;
trait ColumnFillable
{
public function getFillable()
{
return Schema::getColumnListing($this->getTable());
}
}
Now use this trait in your models.
<?php
namespace App;
use App\Traits\ColumnFillable;
class MyModel extends Model
{
use ColumnFillable;
...
Now you'll never have to manually specify $fillable.

Eloquent doesn't get the "belongsTo" item

I have the Project model and the Contract model. When i execute Project:all() it gets me only the projects without the contract, same for contract. I tried to dd() inside contract and doesn't do anything, like is never executed. I also tried with App\ prefix and without.
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
protected $table = 'project';
public function contract() {
return $this->belongsTo('Contract');
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
class Contract extends Model
{
protected $table = 'contract';
public function project() {
return $this->hasMany('Project', 'ContractID', 'ContractID');
}
}
I try to retrieve them like this:
$projects = Project::all()->take(10);
You have a few problems here.
Project::all()->take(10);
This only returns a collection of projects. You havent specified that you want the contracts also.
$projects = Project::with('contract')->get();
In your belongsTo - You havent specified the column that the table should join on. You need to do this, because you have not used a standard id for primary key and contract_id for foreign key.
unrelated to specific question, but your relationship in contract model is also wrong.
public function project() {
return $this->hasMany('Project', 'ContractID', 'ContractID');
}
If one contract has many projects, then your public function project() should be public function projects();
Finally - Why are you using non-standard table / column naming conventions? What's wrong with contract_id? Are you aware that mysql is non-case sensitive? Also the project table could be renamed projects and the contract table could be renamed contracts. It will make you writing your eloquent relations much easier and makes more sense!
If you used standard naming conventions, then you could just do this to declare your model relations.
namespace App;
use Illuminate\Database\Eloquent\Model;
class Contract extends Model
{
public function projects() {
return $this->hasMany('Project');
}
}
Notice you dont need to specify the table name in the model, or how the table is related to the Project.

Categories