I'm trying to look up a model in my database based on 2 fields, and if it doesn't exist, create a new model which contains those two values. I'm attempting to use the firstOrNew method to achieve this:
$store = Store::firstOrNew(array('ext_hash' => $ext_hash, 'ext_type_id' => EXT_TYPE_ID));
However, this code is throwing a MassAssignmentException.
Is the only way to avoid this exception to assign fillable properties on the class level? According to the documentation, I should be able to assign fillable properties on the instance level, rather than for the entire class, but how would I do that?
Here's the code for the Store model:
<?php
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Store extends Eloquent{
use SoftDeletingTrait;
public function products(){
return $this->hasMany('Product');
}
public function faqs(){
return $this->hasMany('ProductFaq');
}
public function customer_questions(){
return $this->hasMany('CustomerQuestion');
}
public function users(){
return $this->hasMany('User');
}
}
fillable() is the method you need:
$search = array('ext_hash' => $ext_hash, 'ext_type_id' => EXT_TYPE_ID);
$store = (Store::where($search)->first())
?: with(new Store)->fillable(array_keys($search))->fill($search);
or:
$store = new Store;
$store = ($store->where($search)->first()) ?: $store->fillable(array_keys($search))->fill($search);
Related
I built a one to one relationship between two Eloquent models and I want to replace an relationship attribute called profile_picture inside my User Eloquent model but I don't know how to do it.
This is a summary of my User model.
class User extends Authenticatable implements JWTSubject{
use Notifiable;
public function profile_picture(){
return $this->hasOne(UserProfilePicture::class);
}
}
And this is a summary of my UserProfilePicture model.
class UserProfilePicture extends Model{
public function user(){
return $this->belongsTo(User::class);
}
}
This is how I pretent to do it in my UserController file.
public function publicUserData($username){
$user = User::where("username", $username)->with("profile_picture")->first();
if($user){
if(!$user->profile_picture){
$user->profile_picture = UserProfilePicture::$defaultUserProfilePicture;
}
return response()->json($user);
}
return response()->json(false);
}
This is $defaultUserProfilePicture static array.
public static $defaultUserProfilePicture = [
"url" => "public/avatars/defaultUserPhoto.jpg",
"size" => 5229
];
I tried to use mutators but looks like it isn't work on relationships attributes.
I found this but doesn't work for me.
Thanks in advance.
You can provides the relation default model with attributes, you may pass an array or Closure to the withDefault method
public function profile_picture(){
return $this->hasOne(UserProfilePicture::class)->withDefault(UserProfilePicture::$defaultUserProfilePicture);
}
Reference: https://laravel.com/docs/7.x/eloquent-relationships#default-models
I solved it, first I put the static array UserProfilePicture::$defaultUserProfilePicture into my database then I do:
$user->setRelation("profile_picture", UserProfilePicture::find(1));
That take one more database request but is the one solution to I have for now.
So I have the following models:
class TemplateEntity extends Model {
protected $table = "TemplateEntities";
const UPDATED_AT = null;
const CREATED_AT = null;
public function element() {
return $this->morphTo("element", "entity_type", "id_Entity");
}
public function getEntityTypeAttribute($entity_type) {
return 'App\\' . $entity_type;
}
}
class Template extends Model {
protected $table = "Template";
const UPDATED_AT = null;
const CREATED_AT = null;
public function entities() {
return $this->hasMany("App\TemplateEntity", "id_Template");
}
}
class TemplateEntity extends Model {
protected $table = "TemplateEntities";
const UPDATED_AT = null;
const CREATED_AT = null;
public function element() {
return $this->morphTo("element", "entity_type", "id_Entity");
}
public function getEntityTypeAttribute($entity_type) {
return 'App\\' . $entity_type;
}
}
I want to eager load template entity elements using Eloquent ORM's ::with() method, however whenever I do this I get an error:
//$template_id is defined as a controller param
$template = Template::with("entities", "entities.element")->where("id", "=", $template_id)->get()
"Class 'App\' not found"
I did some debugging and when I echo $entity_type in TemplateEntity's GetEntityTypeAttribute() method I get an empty value. However, my models generally work fine if I don't use eager loading, but I would like to add it to my application if possible to make it more efficient.
Any help you all can provide would help!
edit: fixed a typo, should have been Template::with instead of $template::with
Part of the problem might be a blank class in that variable. Suggest you use the class name when calling get(). So \App\Template:: instead of $template::.
Another item to help may be the way you are calling the relationship's eager load. Perhaps try to call through the function. This might work better for you:
\App\Template::with(['entities' => function($query){
$query->with('element');
}])->get();
The accessor function might be interfering with the Laravel morph function. I realise you want to use the shortened name of the class in the DB. To do this without the use of the getter (and globally), I suggest using a morphMap.
In AppServiceProvider inside the boot() method:
\Illuminate\Database\Eloquent\Relations\Relation::morphMap([
'MyTemplate' => \App\MyTemplate::class,
'Section' => \App\Section::class,
// etc.
]);
This will allow you to add only 'Section' to the DB and remove the accessor function from your class.
I'm using Laravel 4, and have 2 models:
class Asset extends \Eloquent {
public function products() {
return $this->belongsToMany('Product');
}
}
class Product extends \Eloquent {
public function assets() {
return $this->belongsToMany('Asset');
}
}
Product has the standard timestamps on it (created_at, updated_at) and I'd like to update the updated_at field of the Product when I attach/detach an Asset.
I tried this on the Asset model:
class Asset extends \Eloquent {
public function products() {
return $this->belongsToMany('Product')->withTimestamps();
}
}
...but that did nothing at all (apparently). Edit: apparently this is for updating timestamps on the pivot table, not for updating them on the relation's own table (ie. updates assets_products.updated_at, not products.updated_at).
I then tried this on the Asset model:
class Asset extends \Eloquent {
protected $touches = [ 'products' ];
public function products() {
return $this->belongsToMany('Product');
}
}
...which works, but then breaks my seed which calls Asset::create([ ... ]); because apparently Laravel tries to call ->touchOwners() on the relation without checking if it's null:
PHP Fatal error: Call to undefined method Illuminate\Database\Eloquent\Collection::touchOwners() in /projectdir/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php on line 1583
The code I'm using to add/remove Assets is this:
Product::find( $validId )->assets()->attach( $anotherValidId );
Product::find( $validId )->assets()->detach( $anotherValidId );
Where am I going wrong?
You can do it manually using touch method:
$product = Product::find($validId);
$product->assets()->attach($anotherValidId);
$product->touch();
But if you don't want to do it manually each time you can simplify this creating method in your Product model this way:
public function attachAsset($id)
{
$this->assets()->attach($id);
$this->touch();
}
And now you can use it this way:
Product::find($validId)->attachAsset($anotherValidId);
The same you can of course do for detach action.
And I noticed you have one relation belongsToMany and the other hasMany - it should be rather belongsToMany in both because it's many to many relationship
EDIT
If you would like to use it in many models, you could create trait or create another base class that extends Eloquent with the following method:
public function attach($id, $relationship = null)
{
$relationship = $relationship ?: $this->relationship;
$this->{$relationship}()->attach($id);
$this->touch();
}
Now, if you need this functionality you just need to extend from another base class (or use trait), and now you can add to your Product class one extra property:
private $relationship = 'assets';
Now you could use:
Product::find($validId)->attach($anotherValidId);
or
Product::find($validId)->attach($anotherValidId, 'assets');
if you need to attach data with updating updated_at field. The same of course you need to repeat for detaching.
From the code source, you need to set $touch to false when creating a new instance of the related model:
Asset::create(array(),array(),false);
or use:
$asset = new Asset;
// ...
$asset->setTouchedRelations([]);
$asset->save();
Solution:
Create a BaseModel that extends Eloquent, making a simple adjustment to the create method:
BaseModel.php:
class BaseModel extends Eloquent {
/**
* Save a new model and return the instance, passing along the
* $options array to specify the behavior of 'timestamps' and 'touch'
*
* #param array $attributes
* #param array $options
* #return static
*/
public static function create(array $attributes, array $options = array())
{
$model = new static($attributes);
$model->save($options);
return $model;
}
}
Have your Asset and Product models (and others, if desired) extend BaseModel rather than Eloquent, and set the $touches attribute:
Asset.php (and other models):
class Asset extends BaseModel {
protected $touches = [ 'products' ];
...
In your seeders, set the 2nd parameter of create to an array which specifies 'touch' as false:
Asset::create([...],['touch' => false])
Explanation:
Eloquent's save() method accepts an (optional) array of options, in which you can specify two flags: 'timestamps' and 'touch'. If touch is set to false, then Eloquent will do no touching of related models, regardless of any $touches attributes you've specified on your models. This is all built-in behavior for Eloquent's save() method.
The problem is that Eloquent's create() method doesn't accept any options to pass along to save(). By extending Eloquent (with a BaseModel) to accept the $options array as the 2nd attribute, and pass it along to save(), you can now use those two options when you call create() on all your models which extend BaseModel.
Note that the $options array is optional, so doing this won't break any other calls to create() you might have in your code.
I'm trying to get an array of all of my model's associations. I have the following model:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public function author()
{
return $this->belongsTo('Author');
}
public function category()
{
return $this->belongsTo('Category');
}
}
From this model, I'm trying to get the following array of its relations:
array(
'author',
'category'
)
I'm looking for a way to pull this array out from the model automatically.
I've found this definition of a relationsToArray method on an Eloquent model, which appears to return an array of the model's relations. It seems to use the $this->relations attribute of the Eloquent model. However, this method returns an empty array, and the relations attribute is an empty array, despite having my relations set up correctly.
What is $this->relations used for if not to store model relations? Is there any way that I can get an array of my model's relations automatically?
It's not possible because relationships are loaded only when requested either by using with (for eager loading) or using relationship public method defined in the model, for example, if a Author model is created with following relationship
public function articles() {
return $this->hasMany('Article');
}
When you call this method like:
$author = Author::find(1);
$author->articles; // <-- this will load related article models as a collection
Also, as I said with, when you use something like this:
$article = Article::with('author')->get(1);
In this case, the first article (with id 1) will be loaded with it's related model Author and you can use
$article->author->name; // to access the name field from related/loaded author model
So, it's not possible to get the relations magically without using appropriate method for loading of relationships but once you load the relationship (related models) then you may use something like this to get the relations:
$article = Article::with(['category', 'author'])->first();
$article->getRelations(); // get all the related models
$article->getRelation('author'); // to get only related author model
To convert them to an array you may use toArray() method like:
dd($article->getRelations()->toArray()); // dump and die as array
The relationsToArray() method works on a model which is loaded with it's related models. This method converts related models to array form where toArray() method converts all the data of a model (with relationship) to array, here is the source code:
public function toArray()
{
$attributes = $this->attributesToArray();
return array_merge($attributes, $this->relationsToArray());
}
It merges model attributes and it's related model's attributes after converting to array then returns it.
use this:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public $relationships = array('Author', 'Category');
public function author() {
return $this->belongsTo('Author');
}
public function category() {
return $this->belongsTo('Category');
}
}
So outside the class you can do something like this:
public function articleWithAllRelationships()
{
$article = new Article;
$relationships = $article->relationships;
$article = $article->with($relationships)->first();
}
GruBhub, thank you very much for your comments. I have corrected the typo that you mentioned.
You are right, it is dangerous to run unknown methods, hence I added a rollback after such execution.
Many thanks also to phildawson from laracasts, https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships
You can use the following trait:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Relations\Relation;
trait EloquentRelationshipTrait
{
/**
* Get eloquent relationships
*
* #return array
*/
public static function getRelationships()
{
$instance = new static;
// Get public methods declared without parameters and non inherited
$class = get_class($instance);
$allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC);
$methods = array_filter(
$allMethods,
function ($method) use ($class) {
return $method->class === $class
&& !$method->getParameters() // relationships have no parameters
&& $method->getName() !== 'getRelationships'; // prevent infinite recursion
}
);
\DB::beginTransaction();
$relations = [];
foreach ($methods as $method) {
try {
$methodName = $method->getName();
$methodReturn = $instance->$methodName();
if (!$methodReturn instanceof Relation) {
continue;
}
} catch (\Throwable $th) {
continue;
}
$type = (new \ReflectionClass($methodReturn))->getShortName();
$model = get_class($methodReturn->getRelated());
$relations[$methodName] = [$type, $model];
}
\DB::rollBack();
return $relations;
}
}
Then you can implement it in any model.
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use App\Traits\EloquentRelationshipTrait;
class User extends Authenticatable
{
use Notifiable, HasApiTokens, EloquentRelationshipTrait;
Finally with (new User)->getRelationships() or User::getRelationships() you will get:
[
"notifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"readNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"unreadNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"clients" => [
"HasMany",
"Laravel\Passport\Client",
],
"tokens" => [
"HasMany",
"Laravel\Passport\Token",
],
]
I have published a package in order to get all eloquent relationships from a model. Such package contains the helper "rel" to do so.
Just run (Composer 2.x is required!):
require pablo-merener/eloquent-relationships
If you are on laravel 9, you are able to run artisan command model:show
I'd like to be able to add a custom attribute/property to an Laravel/Eloquent model when it is loaded, similar to how that might be achieved with RedBean's $model->open() method.
For instance, at the moment, in my controller I have:
public function index()
{
$sessions = EventSession::all();
foreach ($sessions as $i => $session) {
$sessions[$i]->available = $session->getAvailability();
}
return $sessions;
}
It would be nice to be able to omit the loop and have the 'available' attribute already set and populated.
I've tried using some of the model events described in the documentation to attach this property when the object loads, but without success so far.
Notes:
'available' is not a field in the underlying table.
$sessions is being returned as a JSON object as part of an API, and therefore calling something like $session->available() in a template isn't an option
The problem is caused by the fact that the Model's toArray() method ignores any accessors which do not directly relate to a column in the underlying table.
As Taylor Otwell mentioned here, "This is intentional and for performance reasons." However there is an easy way to achieve this:
class EventSession extends Eloquent {
protected $table = 'sessions';
protected $appends = array('availability');
public function getAvailabilityAttribute()
{
return $this->calculateAvailability();
}
}
Any attributes listed in the $appends property will automatically be included in the array or JSON form of the model, provided that you've added the appropriate accessor.
Old answer (for Laravel versions < 4.08):
The best solution that I've found is to override the toArray() method and either explicity set the attribute:
class Book extends Eloquent {
protected $table = 'books';
public function toArray()
{
$array = parent::toArray();
$array['upper'] = $this->upper;
return $array;
}
public function getUpperAttribute()
{
return strtoupper($this->title);
}
}
or, if you have lots of custom accessors, loop through them all and apply them:
class Book extends Eloquent {
protected $table = 'books';
public function toArray()
{
$array = parent::toArray();
foreach ($this->getMutatedAttributes() as $key)
{
if ( ! array_key_exists($key, $array)) {
$array[$key] = $this->{$key};
}
}
return $array;
}
public function getUpperAttribute()
{
return strtoupper($this->title);
}
}
The last thing on the Laravel Eloquent doc page is:
protected $appends = array('is_admin');
That can be used automatically to add new accessors to the model without any additional work like modifying methods like ::toArray().
Just create getFooBarAttribute(...) accessor and add the foo_bar to $appends array.
If you rename your getAvailability() method to getAvailableAttribute() your method becomes an accessor and you'll be able to read it using ->available straight on your model.
Docs: https://laravel.com/docs/5.4/eloquent-mutators#accessors-and-mutators
EDIT: Since your attribute is "virtual", it is not included by default in the JSON representation of your object.
But I found this: Custom model accessors not processed when ->toJson() called?
In order to force your attribute to be returned in the array, add it as a key to the $attributes array.
class User extends Eloquent {
protected $attributes = array(
'ZipCode' => '',
);
public function getZipCodeAttribute()
{
return ....
}
}
I didn't test it, but should be pretty trivial for you to try in your current setup.
I had something simular:
I have an attribute picture in my model, this contains the location of the file in the Storage folder.
The image must be returned base64 encoded
//Add extra attribute
protected $attributes = ['picture_data'];
//Make it available in the json response
protected $appends = ['picture_data'];
//implement the attribute
public function getPictureDataAttribute()
{
$file = Storage::get($this->picture);
$type = Storage::mimeType($this->picture);
return "data:" . $type . ";base64," . base64_encode($file);
}
Step 1: Define attributes in $appends
Step 2: Define accessor for that attributes.
Example:
<?php
...
class Movie extends Model{
protected $appends = ['cover'];
//define accessor
public function getCoverAttribute()
{
return json_decode($this->InJson)->cover;
}
you can use setAttribute function in Model to add a custom attribute
Let say you have 2 columns named first_name and last_name in your users table and you want to retrieve full name. you can achieve with the following code :
class User extends Eloquent {
public function getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
}
now you can get full name as:
$user = User::find(1);
$user->full_name;
In my subscription model, I need to know the subscription is paused or not.
here is how I did it
public function getIsPausedAttribute() {
$isPaused = false;
if (!$this->is_active) {
$isPaused = true;
}
}
then in the view template,I can use
$subscription->is_paused to get the result.
The getIsPausedAttribute is the format to set a custom attribute,
and uses is_paused to get or use the attribute in your view.
in my case, creating an empty column and setting its accessor worked fine.
my accessor filling user's age from dob column. toArray() function worked too.
public function getAgeAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes['dateofbirth'])->age;
}