I want to send emails after a record has been saved to a 'lesson hours' table. I am getting very odd behavior however.
In my test pages I have discovered that the error (trying to get property of a non object) seems to happen based on the number of items I have in the 'Packages' table. If I have two packages, the MessageSent Event fires to send the email once. After that the error(s) begin. (Sometimes the error is 'Call to member function getFullName(lessonhours_id)' comes up.) If I have 4 packages, I can send 3 emails etc...
The records get inserted into the database except for the log file which is inserted when the MessageSent Event fires as well. It seems that there is some kind of relationship glitch as somehow the related properties seem to be invisible after a couple of records have been created. I have a 1 to many relationship with Packages and Lessonhours. I can see the relationship work when I use 'Tinker'. Any insights would be appreciated.
Tinker code:
$ php artisan tinker
Psy Shell v0.7.2 (PHP 5.5.9-1ubuntu4.19 — cli) by Justin Hileman
>>> $les = App\Lessonhours::first();
=> App\Lessonhours {#662
id: 1,
created_at: "2016-11-03 13:16:42",
updated_at: "2016-11-03 13:16:42",
players_id: 1,
packages_id: 1,
signup_date: "2016-09-06",
}
>>> $les = App\Lessonhours::first();
=> App\Lessonhours {#664
id: 1,
created_at: "2016-11-03 13:16:42",
updated_at: "2016-11-03 13:16:42",
players_id: 1,
packages_id: 1,
signup_date: "2016-09-06",
}
>>> $les->players->getFullName($les->players_id)
=> "Girltest Testfam"
>>> $les->packages->name
=> "04 Pack Pvt"
>>>
Lessonhours Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Lessonhours extends Model
{
protected $fillable = array('signup_date', 'players_id', 'packages_id');
public $table = "lessonhours";
public function players()
{
return $this->belongsTo('App\Players', 'id');
}
public function packages()
{
return $this->belongsTo('App\Packages', 'id');
}
public function hoursused()
{
return $this->hasMany('App\Hoursused', 'lessonhours_id');
}
protected $dates = ['signup_date'];
}
Packages Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Packages extends Model
{
public $table = "packages";
protected $fillable = ['name', 'cost', 'numberofhours', 'type'];
public function lessonhours()
{
return $this->hasMany('App\Lessonhours', 'packages_id');
}
}
Event MessageSent:
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\Lessonhours;
class MessageSent extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Lessonhours $lessonhours)
{
$this->lessonhours = $lessonhours;
$this->player = $lessonhours->players->getFullName($lessonhours- >players_id);
$this->email = $lessonhours->players->family->email;
$this->package = $lessonhours->packages->name;
}
Listener:
public function handle(MessageSent $event)
{
$lessonhours = $event->lessonhours;
$player = $event->player;
$email = $event->email;
$package = $event->package;
Mail::send('admin.email.lessonhoursnotification', ['name' => $lessonhours], function($message) use ($player, $email, $package){
$message->from('test#test.com', 'Lesson Mgmt');
$message->to($email, $player);
$message->subject('Lesson Package Update');
});
}
First Insert with Two Packages
Error thrown on the new record
It looks like the issue is in your Lessonhours model. The 2nd parameter of belongsTo() should be the column that references the parent's key on the current table. So it should be:
public function players()
{
return $this->belongsTo('App\Players', 'players_id');
}
public function packages()
{
return $this->belongsTo('App\Packages', 'packages_id');
}
Docs
Related
I'm trying to set an uuid as primary key in a Laravel Model. I've done it setting a boot method in my model as stablished here so I don't have to manually create it everytime I want to create and save the model. I have a controller that just creates the model and saves it in database.
It is saved correctly in database but when controller returns the value of the id is always returned with 0. How can I make it to actually return the value that it is creating in database?
Model
class UserPersona extends Model
{
protected $guarded = [];
protected $casts = [
'id' => 'string'
];
/**
* Setup model event hooks
*/
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$uuid = Uuid::uuid4();
$model->id = $uuid->toString();
});
}
}
Controller
class UserPersonaController extends Controller
{
public function new(Request $request)
{
return UserPersona::create();
}
}
You need to change the keyType to string and incrementing to false. Since it's not incrementing.
public $incrementing = false;
protected $keyType = 'string';
Additionally I have an trait which I simply add to those models which have UUID keys. Which is pretty flexible. This comes originally from https://garrettstjohn.com/articles/using-uuid-laravel-eloquent-orm/ and I added some small adjustments to it for issues which I have discovered while using it intensively.
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
/**
* Class Uuid.
* Manages the usage of creating UUID values for primary keys. Drop into your models as
* per normal to use this functionality. Works right out of the box.
* Taken from: http://garrettstjohn.com/entry/using-uuids-laravel-eloquent-orm/
*/
trait UuidForKey
{
/**
* The "booting" method of the model.
*/
public static function bootUuidForKey()
{
static::retrieved(function (Model $model) {
$model->incrementing = false; // this is used after instance is loaded from DB
});
static::creating(function (Model $model) {
$model->incrementing = false; // this is used for new instances
if (empty($model->{$model->getKeyName()})) { // if it's not empty, then we want to use a specific id
$model->{$model->getKeyName()} = (string)Uuid::uuid4();
}
});
}
public function initializeUuidForKey()
{
$this->keyType = 'string';
}
}
Hope this helps.
Accepted answer not worked for me on Laravel 9, but this way worked perfect, you can try it:
1- Create new Trait Class in project path app/Traits/IdAsUuidTrait.php (if you not found Traits folder create it, this is full code of this Class:
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait IdAsUuidTrait
{
public function initializeIdAsUuidTrait(): void
{
$this->keyType = 'string';
$this->id = Str::orderedUuid()->toString();
}
}
2- In any model you want to make id as UUID just call trait like this:
use App\Traits\IdAsUuidTrait;
class YourModelName extends Model
{
use IdAsUuidTrait;
...
That is it, now try to create, select, update any row in database by this model...
Ok, this is weird... You ready?
I have an item type on my site, lets call it SomeItem
It can have tags associated with it via a one-to-many relationship.
The sorts of queries that Laravel builds when dealing with tags for SomeItem are like this, for instance in response to route api/someitem/10:
select `tags`.*, `someitem_tag`.`someitem_id` as `pivot_someitem_id`, `someitem_tag`.`tag_id` as `pivot_tag_id` from `tags` inner join `someitem_tag` on `tags`.`id` = `someitem_tag`.`tag_id` where `someitem_tag`.`someitem_id` in (10)
When I create a second Item with identical settings - let's call it AnotherItems - it treats the database query for extracting tags in a different manner, using a different syntax in the queries. Extremely weird.
(and yes, I have an s at the end of the model name...)
For instance, this route api/anotheritems/1
produces this error:
Base table or view not found: 1146 Table 'mysite.tag_anotheritems' doesn't exist (SQL: select `tags`.*, `tag_anotheritems`.`anotheritems_id` as `pivot_anotheritems_id`, `tag_anotheritems`.`tag_id` as `pivot_tag_id` from `tags` inner join `tag_anotheritems` on `tags`.`id` = `tag_anotheritems`.`tag_id` where `tag_anotheritems`.`anotheritems_id` in (1))
See what is happening? Of course I am getting this error - in the database this tag table for AnotherItems is created as anotheritems_tag. That is analogous to SomeItem.
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item??? WTF?
First let me show you how SomeItem is set up.
Here is the database structure related to Tags:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSomeItemTagTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('someitem_tag', function (Blueprint $table) {
$table->integer('tag_id')->unsigned();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->integer('someitem_id')->unsigned();
$table->foreign('someitem_id')->references('id')->on('someitems')->onDelete('cascade');
$table->primary(array('tag_id', 'someitem_id'));
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('someitem_tag');
}
}
There is a Tags model/class that has this:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
protected $fillable = ['name'];
protected $hidden = [];
public $timestamps = false;
public function someitems()
{
return $this->belongsToMany(SomeItem::class);
}
}
And here is some relevant lines for SomeItem model/class:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use App\Presenters\Presentable;
use Illuminate\Notifications\Notifiable;
use Auth;
class Exercise extends Model
implements Presentable
{
use Traits\SerializesUniversalDate;
use Traits\Presents;
use Notifiable;
protected $presenter = 'App\Presenters\SomeItemPresenter';
protected $fillable = ['title', etc];
protected $hidden = [];
public function parentitem()
{
return $this->belongsTo(ParentItem::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
/**
* Update lesson tag array.
*
* #param array \App\Tag $tags
* #return void
*/
public function updateTags($tagsArray)
{
foreach ($tagsArray as &$value)
{
$tag = Tag::where('name', $value['name'])->first();
if (is_null($tag))
{
$tag = new Tag([
'name' => $value['name']
]);
$tag->save();
}
if (!$this->tags->contains($tag->id))
{
$this->tags()->attach($tag->id);
}
}
foreach($this->tags as &$existingTag)
{
if (!self::arrayContains($tagsArray, 'name', $existingTag->name))
{
$this->tags()->detach($existingTag->id);
}
}
$this->load('tags');
}
private static function arrayContains($array, $key, $value)
{
foreach ($array as $item)
{
if($item[$key] == $value) return true;
}
return false;
}
}
And here is some relevant code for SomeItem API controller:
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Input;
class SomeItemController extends Controller
{
public function index(Request $request)
{
$query = \App\SomeItem::query();
return $query->get()->load('parentitem')->load('tags');
}
//show item for editing
public function show($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->load('parentitem')->load('tags');
$someitem->attachKindToFiles();
return $someitem;
}
//store new entry to db
public function store()
{
$someitem = \App\SomeItem::create(Input::all());
isset(Input::all()['tags']) ? $someitem->updateTags(Input::all()['tags']) : '';
return $someitem;
}
//update/save
public function update($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->update(Input::all());
$someitem->updateTags(Input::all()['tags']);
$someitem->load('tags');
return $someitem;
}
There is also a SomeItem presenter and composer but they don't do anything with tags.
With AnotherItems, I literally I duplicated everything from SomeItem and just changed names as needed.
So in the Tag model there is
public function anotheritems()
{
return $this->belongsToMany(AnotherItems::class);
}
In AnotherItems model there is this, for instance
public function tags()
{
return $this->belongsToMany(Tag::class);
}
In the AnotherItems API controller there is this, for instance (which is for route api/anotheritems/1):
public function index(Request $request)
{
$query = \App\AnotherItems::query();
if ($request->has('id')) {
$query->where('id', $request['id']);
}
return $query->get()->load('parentitem')->load('tags');
}
So, this is a total mystery. I have been trying to figure this out for 2 days now. And I continue asking myself
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item???
I upgraded from laravel 5.2 to 5.3 and it is after the upgrade that I added this AnotherItems. But I can't figure out how that could possibly alter things in terms of these database queries.
I have tried a ton of artisan commands for clearing everything imaginable, but somewhere in the framework it wants to handle SomeItem and AnotherItems differently when building these join queries to extract/save tags.
Thoughts?
thanks,
Brian
Decided to step through code in debugger. Seems things are breaking down in Str.php in various snake related function, and I also noticed a snakeCache call, whatever the heck that is. Not sure why such a strange methodology to determine table names... Also in these functions there is some pluralizing related checks, so maybe this is related to me using an s at the end of my item name. Pretty messed up stuff if an s at the end of a model name can cause two different logic branches...
Is there a way to specify target id column on morphTo method?
I'll give an example which is in documentation for laravel:
Post - Id, text
Video - Id, url
Comment - Id, text, commentable_id, commentable_type
But what if Ids on post and video were renamed to custom_id? How would I set up my eloquent model then? Thanks.
Edit:
I still don't get it here is the complete code:
Table structure:
comments - id, text, commentable_id, commentable_type, user_id
posts - custom_id, text
videos - custom_id, url
users - id, name, email, password,...
Comment model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
Video model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
public function comments()
{
return $this->morphMany('App\Comment', 'commentable', 'commentable_type', 'commentable_id', 'custom_id');
}
}
Post model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function comments()
{
return $this->morphMany('App\Comment', 'commentable', 'commentable_type', 'commentable_id', 'custom_id');
}
}
User model:
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function comments()
{
return $this->hasMany('App\Comment');
}
}
TestController:
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function test()
{
$user = User::first();
$comments = $user -> comments;
foreach ($comments as $comment)
{
return $comment -> commentable;
}
}
}
And it still throws query exception Unknown column posts.id
Please explain. Thanks.
You can set the custom id field name in the relationship.
public function comments()
{
return $this->morphMany('App\Comment', 'commentable', 'commentable_type', 'commentable_id', 'custom_id');
}
There are the parameters
public function morphMany($related, $name, $type = null, $id = null, $localKey = null);
Edit : After going through the whole thing i've found out that using custom id only works from parent fetches the child relationship. But fails when trying to get the parent using the children. So fetching Post or Video from the comment would fail.
There's an active proposal on laravel internals to add the feature to allow specifying the custom id to make this work. https://github.com/laravel/internals/issues/587
I am using Laravel 5.2.
I have a model as below:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
class ZoomMeeting extends BaseModel {
public $timestamps=true;
protected $table = 'zoom_meetings';
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $fillable = ['user_id', 'uuid', 'meeting_id', 'host_id', 'topic', 'status', 'type', 'start_url', 'join_url', 'created_at'];
public function users() {
return $this->belongsTo('App\Models\User');
}
}
And the base model is as below:
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Auth;
use Carbon\Carbon;
class BaseModel extends Model {
public $timestamps = false;
protected static function boot()
{
//parent::boot();
static::creating(function($model) {
if(empty($model->created_at))
{
$model->created_at = date('Y-m-d H:i:s');
}
return true;
});
static::updating(function($model) {
$model->updated_at = date('Y-m-d H:i:s');
return true;
});
}
}
I am using softdeletetrait in ZoomMeeting model, and soft deleting is working fine.
However, if I fetch records from the same model using eloquent, it returns the soft deleted records too. I am using code below to get the records:
$record = ZoomMeeting::where("user_id", $user_id)->where("meeting_id", $meeting_id)->orderBy("id", "DESC")->first();
The eloquent is building the query as:
select * from `zoom_meetings` where `user_id` = 3 and `meeting_id` = 707070707 order by `id` desc limit 1
See, there is no deleted at is null set in where statement. It is not preventing the deleted records.
I am not sure where am I making mistake?
It looks like you are overriding the boot method, but you aren't ever actually calling the parent boot method (it's commented out), so the trait is never getting initialized correctly. I believe that also means the data you have been deleting is actually being deleted from the database.
Is there a reason you need to override the boot method? What you are adding is already done handled by the framework, so it doesn't appear to be necessary.
Ill have a problem because my mutators never get called when ill use an constructor:
Like this:
function __construct() {
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // Never gets called
}
Ill already found out, that the mutators would ne be called when ill use an constructor, so i should use:
public function __construct(array $attributes = array()){
parent::__construct($attributes);
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // now its getting called
}
But so ill get the following error:
array_key_exists() expects parameter 2 to be array, null given
But i dont know where? Can anyone help me out how to create a default value (like a UUID) for a specific column, and use mutators in the same class?
Edit: Thanks Martin Bean for your help, but i am now getting the following error:
Cannot declare class App\Uuid because the name is already in use
I have tried:
Creating a File called "Uuid.php" in /app/ -> /app/Uuid.php
With this content:
<?php namespace App;
use Webpatser\Uuid\Uuid;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = Uuid::generate(4)->string();
});
}
}
Changed my Model to:
<?php namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Task extends Model {
use \App\Uuid;
Thank you very much!
Edit 2:
Ill tried it this way:
class Task extends Model {
protected $table = 'tasks';
protected $fillable = ['..... 'date', 'guid'];
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
TaskController:
public function store() {
$input = Request::all();
$input['guid'] = true;
Task::create($input);
return redirect('/');
}
Works fine, but when ill use:
public function setDateAttribute(){
$this->attributes['date'] = date('Y-m-d', $date);
}
In Task.php ill get:
Undefined variable: date
EDITED:
based on your comment:
i would like to set a field on first insert
use Uuid; //please reference the correct namespace to Uuid
class User extends Model{
protected $fillable = [
'first_name',
'email',
'guid' //add guid to list of your fillables
]
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
}
Later:
$user = User::create([
'guid' => true, //setAttribute will handle this
'first_name' => 'Digitlimit',
'email" => my#email.com
]);
dd($user->guid);
NB: Remove the __construct() method from your model
Mutators are called when you try and set a property on the model—they’re invoked via the __get magic method. If you manually assign a property in a method or constructor, then no mutators will ever be called.
Regardless, you should not be creating constructors on Eloquent model classes. This could interfere with how Eloquent models are “booted”.
If you need to set an UUID on a model then I’d suggest using a trait that has its own boot method:
namespace App;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = \Vendor\Uuid::generate(4)->string();
});
}
}
You apply the trait to your model…
class SomeModel extends Model
{
use \App\Uuid;
}
…and now each time a model is created, a UUID will be generated and stored in the database with your model.