Creating and dropping a table during Laravel test - php

I've created a trait using in my Laravel 5.6 app. The trait is called Projectable. This trait is meant to be used by Eloquent models. In order to test the trait, I wanted to create a ProjectableStub model to use in the tests. However, since this is an Eloquent model, it requires a table.
I wanted to simply create and drop a table just for testing. However, when I do this, it seems like something breaks regarding the RefreshDatabase functionality. To demonstrate, I am simply running two tests, both of which try to create a Product model with id = 1. Since the RefreshDatabase trait is being used, this should work fine. And, in the example below, it does:
<?php
namespace Tests\Feature;
use App\Product;
use Tests\TestCase;
use App\Concerns\Projectable;
use Illuminate\Support\Facades\Schema;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Database\Eloquent\Model as Eloquent;
class ProjectableTest extends TestCase
{
use RefreshDatabase;
public function setUp()
{
parent::setUp();
//$this->createStubTable();
}
/**
* #test
*/
public function example_first_test()
{
factory(Product::class)->create(['id' => 1]);
}
/**
* #test
*/
public function example_second_test()
{
factory(Product::class)->create(['id' => 1]);
}
public function tearDown()
{
//$this->dropStubTable();
parent::tearDown();
}
private function createStubTable()
{
Schema::create('stubs', function ($table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
private function dropStubTable()
{
Schema::dropIfExists('stubs');
}
}
class ProjectableStub extends Eloquent
{
use Projectable;
protected $table = 'stubs';
protected $guarded = [];
}
However, as soon as I uncomment the two lines so that the stubs table is created and dropped, I get a SQL error that a duplicate ID is being used:
<?php
namespace Tests\Feature;
use App\Product;
use Tests\TestCase;
use App\Concerns\Projectable;
use Illuminate\Support\Facades\Schema;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Database\Eloquent\Model as Eloquent;
class ProjectableTest extends TestCase
{
use RefreshDatabase;
public function setUp()
{
parent::setUp();
$this->createStubTable();
}
/**
* #test
*/
public function example_first_test()
{
factory(Product::class)->create(['id' => 1]);
}
/**
* #test
*/
public function example_second_test()
{
factory(Product::class)->create(['id' => 1]);
}
public function tearDown()
{
$this->dropStubTable();
parent::tearDown();
}
private function createStubTable()
{
Schema::create('stubs', function ($table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
private function dropStubTable()
{
Schema::dropIfExists('stubs');
}
}
class ProjectableStub extends Eloquent
{
use Projectable;
protected $table = 'stubs';
protected $guarded = [];
}
1) Tests\Feature\ProjectableTest::example_second_test
Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity
constraint violation: 1062 Duplicate entry '1' for key 'PRIMARY'
Does anyone know why creating and dropping a table within the test is causing this issue? Is there a better way to go about this? Maybe some way to add a migration at runtime for this new table?

I think this might be the answer:
https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html
Creating a table is causing the active transaction created by RefreshDatabase to be automatically committed.
Making the stubs table temporary did the trick. It also means I don't need to drop the table either, since it happens automatically:
Schema::create('stubs', function ($table) {
$table->temporary();
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Seems to be working perfectly so far.

Related

Laravel Sanctum with uuid column in User model doesn't save tokenable_id

I'm try to use Laravel 8.x and Laravel sanctum 2.14.2 to authenticate my API and UUIDs as the primary key for my User model.
My custom PersonalAccessToken model
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
use HasFactory;
protected $table = 'personal_access_tokens';
public function tokenable()
{
return $this->morphTo('tokenable', "tokenable_type", "tokenable_id", "uuid");
}
}
My personal_access_tokens migration schema
...
public function up()
{
Schema::dropIfExists('personal_access_tokens');
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->uuidMorphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamps();
});
}
...
My AppServiceProvider
...
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
Sanctum::ignoreMigrations();
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
if($this->app->environment('production')) {
URL::forceScheme('https');
}
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
}
When I try to get the token with $user->createToken($user->email)->plainTextToken, I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tokenable_id' cannot be null (SQL: insert into `personal_access_tokens` (`name`, `token`, `abilities`, `tokenable_id`, `tokenable_type`, `updated_at`, `created_at`) values (admin#gmail.com, 85dbe44c32a999a01f4a97d9c9eab0710125a6ac5f861ab546a5822f61015b23, [\"*\"], ?, App\\Models\\User, 2022-03-20 19:16:43, 2022-03-20 19:16:43))
I think the cause of the error is that I am using uuid as the primary key in the users table
Schema::create('users', function (Blueprint $table) {
$table->uuid('uuid')->primary();
...
});
UPDATE
My User Model
...
class User extends Authenticatable
{
use HasUUID;
use HasApiTokens;
use HasFactory;
use Notifiable;
use HasRoles;
...
public function tokens()
{
return $this->morphMany(Sanctum::$personalAccessTokenModel, 'tokenable', "tokenable_type", "tokenable_id");
}
...
}
Any help would be appreciated.
Is there any particular reason for you to create custom PersonalAccessToken model?
If it's just UUID that you want for the primary key of your User model, you can achieve it without creating the custom PersonalAccessToken model.
Your personal_access_tokens migration schema seems fine.
I think the cause of the error is that I am using uuid as the primary
key in the users table
Schema::create('users', function (Blueprint $table) {
$table->uuid('uuid')->primary();
...
});
This could be the issue. Try changing the column name to just id from uuid and see if it works
$table->uuid('id')->primary();
If you must use the column name as uuid for primary key, then try adding the following to your User model
protected $primaryKey='uuid'
By default eloquent assumes the name of the primary key column as 'id'. This will let eloquent know to look for 'uuid' as primary key column for User model.
Also since you are not using the default integer data type for primary key make sure you have the following in your User model
public $incrementing=false
protected $keyType='string'
You can refer to Laravel Documentation for
Primary Keys
Sorry for late reply. I answer with the solution for anyone who is having the same problem as above.
The problem is in my UUId Traits. We should use boot magic method as Laravel suggested when we want to create our own Traits.
Solution:
Using App\Traits\HasUUID with the correct code
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
trait HasUUID
{
/**
* Boot functions from Laravel.
*/
// protected static function boot() <- This line is INCORRECT
protected static function bootHasUUID()
{
static::creating(function (Model $model) {
$model->primaryKey = 'uuid';
$model->keyType = 'string'; // In Laravel 6.0+ make sure to also set $keyType
$model->incrementing = false;
if (empty($model->{$model->getKeyName()})) {
$model->{$model->getKeyName()} = Str::uuid()->toString();
}
});
}
/**
* Get the value indicating whether the IDs are incrementing.
*
* #return bool
*/
public function getIncrementing()
{
return false;
}
/**
* Get the auto-incrementing key type.
*
* #return string
*/
public function getKeyType()
{
return 'string';
}
}
And finally, add the App\Traits\HasUUID in User Model.
...
use App\Traits\HasUUID;
...
class User extends Authenticatable
{
use HasUUID;
...
}
No need to customize Sanctum's Model. Thank you so much #Hussain, #Dharman

How to use 'get' method with the where eloquent in laravel

I was trying to get all the elements sharing a common row data
but I keep getting this error I do not know why
Call to a member function get() on null
Here is the code I used, I have imported the App\User and everything but still not getting it and I have a user in my table with that role_id
My controller
<?php
namespace App\Http\Controllers;
use App\Animal;
use App\Clinic;
use App\Role;
use App\Slaughter;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ClinicController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
$farms = User::where('role_id', 3);
$user = Auth::user();
$animal = Animal::all();
return view('clinic.index', compact('user', 'animal', 'farms'));
}
My user table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->unsignedBigInteger('address_id')->index();
$table->unsignedInteger('role_id');
$table->string('description')->nullable();
$table->timestamps();
$table->foreign('address_id')->references('id')->on('addresses');
});
}
But I am keep getting that error bellow I do not know the why
use App/User;
$farm = User::where('role_id', 3)->get();
Try below code for getting the record
$farm = User::select(['id','name'])->where('role_id', 3)->get();
In case the User class extends Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{}
calling
$builder = User::where('role_id', 3);
always returns an object of type Illuminate\Database\Eloquent\Builder, therefore, calling
$user= User::where('role_id', 3)->get();
always returns a collection (which might be empty when there is no user with role_id = 3). Check the type of your User class.
I think there are some missed steps that you didn't take.
The User does not refer to the table name but to the model class name. So first things first you need to create a model.
In order to do that you use the command:
php artisan make:model User
If you want a dedicated Models folder(i always use one) you use
php artisan make:model Models/User
By that time you should have a model created but you need to set it up.
Going inside your User.php file you should have something like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes that are NOT mass assignable.
*
* #var array
*/
protected $guarded = [
'created_at', 'updated_at', 'password,
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
//
];
}
Now by including your model at the top of your file you can use your Model class.

Can I use where in eloquent while giving it an object?

I'm new to Laravel & Eloquent, I'm coming from Django.
In Django I can create a filter on my results and use an object to filter the results...
I'm trying to figure out if I could do this in Laravel/Eloquent also...
Here's what I'm trying to do....
create_messages_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMessagesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->integer('user_id');
$table->string('message');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('messages');
}
}
Message.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
MessageController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Message;
use Illuminate\Support\Facades\Auth;
class MessageController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$messages = Message::where("user", Auth::user())->get();
dd($messages);
return $messages;
}
}
The problem is that when the controller index() function is routed too... I get an error saying "user" column does not exist... and it doesn't but I assumed the models user() function would take care of that and that I would be able to compare it's returning object with Auth::user().
Am I wrong, or am I just doing it wrong?
In the "where" query, the first parameter must be a column in the database. If you want to make use of a "user" column, I'll suggest you add a user column to your table.
I think the auth:user() is to authenticate the current user.
So laravel eloquent is only alerting you that the "user" column does not exist. I will suggest you create a user column, and see what happens next.

Undefined table: 7 ERROR: relation "expenses" does not exist

Since i am a spanish speaker, i wrote the controllers and models of income and expense in spanish; while all the rest were on english..
I renamed and changed manually Routes, Controllers, Migrations and even Models.
And when i run php artisan migrate:reset this is my error.
Undefined table: 7 ERROR: relation "expenses" does not exist (SQL: alter table "expenses" drop column "location_id")**
I use psgql and laravel 5.3
This is my code:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Expense extends Model
{
protected $fillable = ['id', 'description', 'quantity'];
public function locations()
{
return $this->hasMany('App\Location');
}
public function icons()
{
return $this->hasMany('App\Icon');
}
public function types()
{
return $this->hasMany('App\Type');
}
public function stores()
{
return $this->hasMany('App\Store');
}
}
Migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateExpensesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('expenses', function (Blueprint $table) {
$table->increments('id');
$table->float('quantity');
$table->string('description');
$table->integer('location_id')->unsigned();
$table->integer('icon_id')->unsigned();
$table->integer('type_id')->unsigned();
$table->integer('store_id')->unsigned();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('expenses');
}
}
Location Migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLocationsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('locations', function (Blueprint $table) {
$table->increments('id');
$table->string('address');
$table->float('lat');
$table->float('long');
$table->integer('store_id')->unsigned();
$table->timestamps();
$table->foreign('store_id')->references('id')->on('stores')->onDelete('cascade');
});
Schema::table('expenses', function (Blueprint $table) {
$table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('expenses', function (Blueprint $table) {
$table->dropColumn('location_id');
});
Schema::dropIfExists('locations');
}
}
Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
protected $fillable = ['id', 'address', 'lat', 'long'];
public function expenses()
{
return $this->belongsTo('App\Expense');
}
public function stores()
{
return $this->hasOne('App\Store');
}
}
Hope you can help me.
When it says
relation "expenses" does not exist
It usually happens when your expenses table must have been dropped before migration:reset rolled back CreateLocationsTable.
Check the order of execution of your migrations.
As you were trying to reset, to fix it now, open your database, drop all tables manually and execute migrate again.
Schema::table('expenses', function (Blueprint $table) {
$table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade');
});
change this to
Schema::alter('expenses', function (Blueprint $table) {
$table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade');
});
I had this error. I had to resolve it by adding the prefix of the database to my model. There is a way to change this in the database, and it is supposed to be changed. it's something, like $user, public. It's the schema profile. Mine is changed when I login, but for some reason the model is not binding to schema. So I had to specify it in the model. Instead of
protected $table = 'table_name';
I had to do
protected $table = 'schema_name.table_name';
Note: By schema_name or table_name, I'm not referring for you to put schema_ then the name. That is just so it's easier to read.
The model is located in the model folder under the App/Models folder depending on which version of Laravel you using and how your Models are organized. If your model name is not the same as the table name, then you will need to put the protected $table. But if the schema is not there, then you will need to add that.
I do have this set in my DB_DATABASE_SECOND= in .env file. But it somehow still doesn't pick up the prefix.
But yeah. Pretty much I couldn't find the answer anywhere, but I
Solution found in:
Laravel assumes the database table is the plural form of the model name
https://laravel.com/docs/8.x/eloquent#table-names
you need to declare the singularity name of your table.
add this line
protected $table = 'expense'
at the end of your model file
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Expense extends Model
{
protected $fillable = ['id', 'description', 'quantity'];
public function locations()
{
return $this->hasMany('App\Location');
}
public function icons()
{
return $this->hasMany('App\Icon');
}
public function types()
{
return $this->hasMany('App\Type');
}
public function stores()
{
return $this->hasMany('App\Store');
}
protected $table = 'expense';
}

Call to undefined method App\Profile::create()

This is my migration table
create_profile_table.php:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateProfileTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('profile', function (Blueprint $table) {
$table->integer('userid')->unsigned()->default(0);
$table->string('profilePic')->default('http://b2.com/Images/anup.jpg');
$table->string('about',255);
$table->foreign('userid')->references('id')->on('users')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('profile');
}
}
This is my seeder file ProfileSeeder.php:
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use App\Profile;
class ProfileSeeder extends Seeder
{
public function run()
{
Profile::create(array('userid'=>1,'about'=>'Hello World'));
Profile::create(array('userid'=>2,'about'=>'Hello World'));
Profile::create(array('userid'=>3,'about'=>'Hello World'));
Profile::create(array('userid'=>4,'about'=>'Hello World'));
Profile::create(array('userid'=>5,'about'=>'Hello World'));
}
}
This is my model php file Model Profile.php:
namespace App;
class Profile
{
protected $table='profile';
protected $fillable=['userid','about'];
}
shows the error:
[Symfony\Component\Debug\Exception\FatalErrorException] Call to undefined method App\Profile::create()
I am a new laravel5 Learner.
Don't know why this error is showing.
Any kind of help in this issue will be highly appreciated.
Your Profile class needs to extend the Model class if you want to be able to use the eloquent methods, like create(), find() and such.
You should use php artisan to create your models, migrations, seeders and any other Laravel "component", they will work out of the box with minimal effort.
namespace App;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model
{
protected $table='profile';
protected $fillable=['userid','about'];
}

Categories