Use trait in Laravel database migration - laravel

My superior told me I could create a seeder-like trait which I can then use inside a migration. When the migration is being run on the server the database automatically gets seeded while migrating instead of running a separate seeder after the migration succeeded.
Now I created a trait which I included in the database migration.
<?php
namespace App\Services;
use App\Models\Module\Module;
use App\Models\Plan\Plan;
/**
* PlanAndModuleGenerator class.
*/
trait PlanAndModuleGenerator
{
private static $plans = [
'free',
'basic',
'premium',
];
public function up()
{
foreach ($this->plans as $planName) {
// Get or create Plan.
$plan = Plan::create([
'machine_name' => '',
'name' => $planName
]);
}
}
}
My superior told me they did this before, but I can't find anything like this on the internet. I included my trait like this.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Services\PlanAndModuleGenerator;
class ModulePlan extends Migration
{
use PlanAndModuleGenerator;
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('module_plan', function (Blueprint $table) {
$table->unsignedInteger('plan_id');
$table->unsignedInteger('module_id');
$table->foreign('plan_id')->references('id')->on('plans');
$table->foreign('module_id')->references('id')->on('modules');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('module_plan');
}
}
When I run the migration the up function inside my trait isn't executed. I know this because the Plan table isn't seeded yet. Any ideas on how I might tackle this? As my superior isn't in the office the upcoming days and I can't access the repository where they did this before.
Besides this, can anyone tell me how I can properly debug this trait? The way I am doing this now, just running the migration and wait for errors, seems a bit cumbersome.

I don't see any reason for this trait at all, but if you really want to use it you would need to alias the up method of the trait and then call that in your up method of the migration:
class ModulePlan extends Migration
{
use PlanAndModuleGenerator { up as traitUp; }
public function up()
{
...
$this->traitUp();
}
}
It would be better to just use a different name for the method in the Trait, but there is no reason for this trait in the first place it would seem.

It will definitly not work because you have two up methods, one in your trait and the one in your migration. You need to delete the up in your migration and use the one in your trait as shown below. Here is the trait
<?php
namespace App\Services;
use App\Models\Plan\Plan;
use App\Models\Module\Module;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
/**
* PlanAndModuleGenerator class.
*/
trait PlanAndModuleGenerator
{
private static $plans = [
'free',
'basic',
'premium',
];
public function up()
{
$createmigration = Schema::create('module_plan', function (Blueprint $table) {
$table->unsignedInteger('plan_id');
$table->unsignedInteger('module_id');
$table->foreign('plan_id')->references('id')->on('plans');
$table->foreign('module_id')->references('id')->on('modules');
});
if ($createmigration) {
foreach ($this->plans as $planName) {
// Get or create Plan.
$plan = Plan::create([
'machine_name' => '',
'name' => $planName
]);
}
}
}
}
Confirm first that the migration was created before creating your Plan.
Here is how your migration should look like
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Services\PlanAndModuleGenerator;
class ModulePlan extends Migration
{
use PlanAndModuleGenerator;
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('module_plan');
}
}
Hope this will help

Related

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax error when seeding data to db

I am getting this error when I try to seed data into the database. I have the bookings table and a bookings package table, seeder, factory and models, and also a pivot table that links them.
here is my bookingpackage seeder
<?php
namespace Database\Seeders;
use App\Models\bookingpackage;
use Illuminate\Database\Seeder;
class BookingpackageSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
bookingpackage::create(['package_name'=>'Dj and Full Sound System',
'package_description'=>'Dj and Full Sound System.Dj and Full Sound System',
'package_price'=>'sh 80,000']);
}
}
here's bookingseeder
$fullpackage=bookingpackage::where('package_name','Dj and Full Sound System',
'package_description','Dj and Full Sound System.Dj and Full Sound System',
'package_price','sh 80,000')->first();
$fullsetpackage=Bookings::create([
'full_name'=>'Best Promoters',
'location'=>'Nairobi',
'phone'=>'0792492584',
'is_booking'=>2,
'package_id'=>1,
'email'=>'Werupromoter#admin.com',
'date'=>'07/02/2021',
'event_id'=>'5',
'event_details'=>'we would like to book you for a wedding'
]);
$fullpackage->bookingpack()->attach($fullsetpackage);
my bookingpackages table looks like this
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBookingpackagesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('bookingpackages', function (Blueprint $table) {
$table->id();
$table->string('package_name');
$table->text('package_description');
$table->string('package_price');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('bookingpackages');
}
}
where might have I gone wrong in my code.
The title is very vague but i think error is in this line
$fullpackage->bookingpack()->attach($fullsetpackage);
first check bookingpack() method is exist in Bookings model
and then modify this line with:
$fullpackage->bookingpack()->attach($fullsetpackage->id);
after that replace first query in bookingseeder with:
$fullpackage=bookingpackage::where("package_name", 'Dj and Full Sound System')
->where("package_description", "Dj and Full Sound System.Dj and Full Sound System")
->where("package_price", "sh 80,000")
->first();
or you can write "where" query another way like:
$fullpackage=bookingpackage::where([
['package_name', "=", 'Dj and Full Sound System'],
['package_description', "=", 'Dj and Full Sound System.Dj and Full Sound System'],
['package_price', "=", 'sh 80,000'],
])->first();

Laravel API: How to make a default image in a table instead of NULL

I am currently working on having a default image in a table instead of null. I already have an API that will put an image in that specific column (web_banner_profile) which is a POST method and a DELETE method that will make that column NULL, all of which using postman. I want to know how I can put a default image on all of the webinars table in the web_banner_profile.
This is the Banner Upload Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Banner;
use Illuminate\Http\Request;
// use Illuminate\Support\Facades\Validator;
class BannerUploadController extends Controller
{
public function FileUpload(Request $request, $id)
{
$uploaded_files = $request->file->store('public/uploads/');
$webinar = Banner::find($id);
$webinar->web_banner_profile = $request->file->hashName();
$results = $webinar->save();
if($results){
return ["result"=>"Image Added"];
}else{
return ["result"=>"Image Not Added"];
}
return ["result"=>"$uploaded_files"];
}
public function DeleteBanner($id)
{
$webinar = Banner::find($id);
if(is_null($webinar)){
return response()->json('Record not found!', 401);
}
$webinar->update(['web_banner_profile' => null]);
return response('Banner Deleted', 200);
}
}
This is the webinar table migration:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateWebinarTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('webinar', function (Blueprint $table) {
$table->id();
$table->string('web_title');
$table->text('web_description');
$table->dateTime('web_start_date_time')->nullable();
$table->dateTime('web_end_date_time')->nullable();
$table->string('status')->nullable();
$table->string('remarks')->nullable();
$table->string('web_banner_profile')->nullable();
$table->bigInteger('created_by')->unsigned()->nullable();
$table->bigInteger('updated_by')->unsigned()->nullable();
$table->string('web_link')->nullable();
$table->timestamps();
});
Schema::table('webinar', function(Blueprint $table) {
$table->foreign('created_by')->references('id')->on('admins');
$table->foreign('updated_by')->references('id')->on('admins');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('webinar');
}
}
Any type of help/suggestion would be greatly appreciated. Thank you in advance!
I would consider using one of the Eloquent model events, specifically the created event. Something like the following:
class Webinar extends Model
{
protected static function booted()
{
static::created(function ($webinar) {
$webinar->update(['web_banner_profile' => 'your-image.jpg']);
});
}
}
Then whenever a new Webinar is created, the created event will be triggered and your default web_banner_profile value will be added to that record.
You don't have to hard code the value your-image.jpg, you could obtain it from a config of env file if you didn't want it in your code base to (arguably) make changing the value easier.

Laravel factory creation of Eloquent Models creates models with wrong attributes

I have a seeder in which I try to bind possibilities to questions using factory.
...
$question->possibilities()->saveMany(
factory(Possibility::class, $random_num)
->make()
->each(function ($item, $index) use ($correct_answer) {
if ($index === $correct_answer) {
$item->correct = true;
}
})
);
...
When I use factory the Eloquent model has an attribute of answer despite me removing the answer column from the migrations and removing all occurrences of answer in the Possibility model.
The Possibility Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Possibility extends Model
{
protected $fillable = ['question_id', 'correct'];
public function question()
{
return $this->belongsTo('App\Models\Question');
}
}
The Possibility migration
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePossibilitiesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('possibilities', function (Blueprint $table) {
$table->bigIncrements('id');
// $table->string('answer');
$table->boolean('correct')->default(false);
$table->unsignedBigInteger('question_id');
$table
->foreign('question_id')
->references('id')
->on('questions')
->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('possibilities');
}
}
When I use the Model::create without using factory I get don't have the attribute as expected.
$possibility = Possibility::create([
'question_id' => $question->id,
'correct' => 0
]);
EDIT
Missed something extremely obvious, the PossibilityFactor definition itself. Thanks to #lagbox for pointing it out.
You made a factory for Possibility. In that you are defining the array of attributes to be used. You are returning an array with a key for answer. Remove that.
This is probably what you need
factory(Possibility::class, $random_num)->create([
'question_id' => $question->id,
])->each(function ($item, $index) use ($correct_answer) {
if ($index === $correct_answer) {
$item->correct = true;
} else {
$item->correct = false;
}
})

Extended User Model in a Package

Could you help me understand the right way to extend existing models? I'm developing a package and want to do as much as possible separated from the main application.
I want to have all existing functionality of the User model, but only add a relation to another model. In my package User I can have several (hasMany) Article. Instead of adding a method to \App\User I created a new model in my package class User extends \App\User:
namespace Package\Sample;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
/**
* Class User
* #package Package\Sample
*/
class User extends \App\User
{
use HasApiTokens, Notifiable;
public function articles()
{
return $this->hasMany(Article::class);
}
}
To make it work I add the configuration for my package auth.php:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \Package\Sample\User::class,
],
],
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
My question: Is it a "best practice" to extend functionality of existing models?
I solved it by using the class_alias() php function.
In my package service provider I setup an alias of the model class defined for Authentication in /config/auth.php like this:
public function boot(){
class_alias(config("auth.providers.users.model"), 'ParentModel');
}
then I use ParentModel class where needed:
use ParentModel;
class Agent extends ParentModel {
...
}
Hope It makes sense for someone
that will prevent edits on the \Package\Sample\User::class. any new method needed will result in a package update.
why not declare a trait in your package containing your methods and use them in the App\User::class like what laravel is using.
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
I tried using the class_alias and although it works for basic usage, when things got more complicated my user class couldn't cut it. For example, my notifications where using the local package user type and not showing in app.
After doing more research I found using a trait IS the proper way like others have mentioned.
I found a comprehensive guide here: https://laravelpackage.com/08-models-and-migrations.html#approach-2-using-a-polymorphic-relationship
the gist:
Create a trait in your package:
Important here you can setup whatever the relationship you need depending on you db modeling.
// 'src/Traits/HasPosts.php'
<?php
namespace JohnDoe\BlogPackage\Traits;
use JohnDoe\BlogPackage\Models\Post;
trait HasPosts
{
public function posts()
{
return $this->morphMany(Post::class, 'author');
}
}
Add the use in whatever user classes it applies to in your app
// 'App\Models\User.php'
<?php
namespace App\Models;
use JohnDoe\BlogPackage\Traits\HasPosts;
class User extends Authenticatable
{
use HasPosts;
...
Lastly, you'll need to add an extra field in the db for the user_type where you are using the user_id.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUserTypeToPostTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->tinyText('user_type')
->comment('User class type')
->nullable();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('user_type');
});
}
}
You need to Add config(['auth.providers.users.model' => Myname\Myproject\App\Models\User::class]); to the boot-method inside my package-service-provider.
And Create new Class in your package.
namespace Myname\Myproject\App\Models;
class User extends \App\User
{
public function roles(){
return $this->belongsToMany('Myname\Myproject\App\Models\Role', 'user_role', 'user_id', 'role_id');
}
}

Why the related Answer model doesn't get project_id?

I have the next models:
Project <(Many to Many)> Experiment (One to Many)> Question (One to Many)> Answer
When I try Project::with('experiments.questions.answers')->find(1)
as result, I get answers not only from project with id: 1, but from others too.
Structure Answer model:
<?php
use App\Models\Answer;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAnswersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('answers', function (Blueprint $table) {
$table->increments('id');
$table->string('answer')->nullable();
$table->integer('confidence_score')->unsigned()->default(0);
$table->integer('state')->unsigned()->default(Answer::READY);
$table->integer('element_id')->unsigned();
$table->integer('question_id')->unsigned();
$table->integer('project_id')->unsigned();
$table->timestamps();
$table->foreign('question_id')->references('id')->on('questions')->onDelete('cascade');
$table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('answers', function ($table) {
$table->dropForeign(['question_id']);
$table->dropForeign(['project_id']);
});
Schema::drop('answers');
}
}
If I add the next condition, it is work:
Project::with(['experiments.questions.answers' => function($query) {$query->where('project_id', 1);}])->find(1)
But how can I remove it from the code and to make it global?
I can't figure out why this selection touch other projects, however, if you want to create a handy shortcut to fetch project with relations - create a method in a Model or your Repository:
public function findWithExperiments($id) {
$project = Project::find($id);
$project->load(['experiments.questions.answers' => function ($query) use ($id) {
$query->where('project_id', $id);
}]);
return $project;
}
Try this code
Project::with('experiments.questions.answers')->find([1])

Resources