In my laravel project I use morphToMany relation to connect costs to the orders and products of the orders.
I have this migration:
Schema::create('costables', function (Blueprint $table) {
$table->unsignedBigInteger('cost_id');
$table->morphs('costable');
$table->foreign('cost_id')
->references('id')->on('costs')
->onUpdate('cascade')
->onDelete('cascade');
});
This is the Order model, with costs() method in it:
namespace Domain\Webshop\Models;
use Domain\Customer\Models\Address;
use Domain\Customer\Models\Member;
use Domain\Product\Models\Cost;
use Domain\Webshop\Database\Factories\OrderFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Order extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'member_id',
'shipping_address_id',
'billing_address_id',
'status',
];
public static $rules = [
'member_id' => 'required|integer',
'shipping_address_id' => 'sometimes|integer',
'billing_address_id' => 'nullable|integer',
'status' => 'required|integer',
];
protected $casts = [
'member_id' => 'integer',
'shipping_address_id' => 'integer',
'billing_address_id' => 'integer',
];
protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
/**
* Create a new factory instance for the model.
*
* #return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory(): OrderFactory
{
return OrderFactory::new();
}
public function member(): BelongsTo
{
return $this->belongsTo(Member::class, 'member_id');
}
public function shippingAddress(): BelongsTo
{
return $this->belongsTo(Address::class, 'shipping_address_id');
}
public function costs(): MorphToMany
{
return $this->morphToMany(Cost::class, 'costable');
}
}
And this is my phpunit test, for testing costs on the Order and OrderProduct models. The $this->model is the actual model, I use this as a trait:
/** #test */
public function shouldHasCosts()
{
$item = $this->model::factory()
->hasCosts(1)
->create();
$this->assertInstanceOf(
Cost::class,
$item->first()->costs()->first(),
"It should be defined a relation called costs on $this->model"
);
}
My problem is, the test success in the Order model case, but fails at the OrderProduct. What have I forgotten, or whats wrong in my code?
I found the problem myself: in the test's setup there was a factory create for the OrderProduct, so in that case there was two rows in the database, and the shouldHasCosts() test is examine the first(), so it was wrong. I deleted the unnecessary create, and it has solved the problem.
Related
right now i am stuck in this not really knowing what to do (maybe is really easy, but i don't see how to do it).
I have this model of users:
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
protected $table = 'tcn_user';
// Time for the relations
public function user(){
return $this->morphTo(__FUNCTION__, 'type_user', 'id_user');
}
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'username','permision', 'first_name', 'surname', 'us_password', 'email'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'us_password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
Being the problem (I suppose) in the function user(). I call it but it returns null when doing it, the variables for the childs are there, so I really don't know what is wrong. But for doubts I have here the code for one of them so you can see it.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Teacher extends Model
{
protected $table = 'teacher';
//In first place let's do the relations of the model
public function user(){
return $this->morphOne(User::class, 'user', 'id_user');
}
public function schedule(){
return $this->hasMany(ServiceSchedule::class, 'id_teacher');
}
public function session(){
return $this->hasMany(DaySession::class, 'id_teacher');
}
}
Being the important one the function user().
If you need for information please let me know, is my first time asking here, so i really don't know if I am doing it well.
PD: Okey, sorry for the things i have forgotten to add.
This being to code about how i am calling the Teacher:
public function detail($id){
$user = User::find($id);
if(is_object($user)){
$data = array(
'code' => 200,
'status' => 'success',
'user' => $user,
'subuser' => $user->user
);
}else{
$data = array(
'code' => 404,
'status' => 'error',
'message' => 'El usuario no existe'
);
}
return response()->json($data);
}
And the relation it have, well, we have User, the parent "class" with the information all the users need to have, and after that we have the "childs" like Teacher, Student, Parent, all of then with different information.
And responding to Rwd, no, i don't have a camp in the database with the name user, is more, the table user is named tcn_user (Because user is a keyword in mysql)
So consider the following migration:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAdventureLogs extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('adventure_logs', function (Blueprint $table) {
$table->id();
$table->bigInteger('character_id')->unsigned();
$table->foreign('character_id')
->references('id')->on('characters');
$table->bigInteger('adventure_id')->unsigned();
$table->boolean('in_progress')->nullable()->default(false);
$table->boolean('complete')->nullable()->default(false);
$table->integer('last_completed_level')->nullable();
$table->json('logs')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('adventure_logs');
}
}
Notice the json column I create. $table->json('logs')->nullable();
From here, lets create a model:
use Illuminate\Database\Eloquent\Model;
class AdventureLog extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'character_id',
'adventure_id',
'complete',
'in_progress',
'last_completed_level',
'logs',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'complete' => 'boolean',
'in_progress' => 'boolean',
'last_completed_level' => 'integer',
];
public function getLogsAttribute($value) {
return json_decode(json_decode($value));
}
public function setLogsAttribute($value) {
$this->attributes['logs'] = json_encode($value);
}
public function character() {
return $this->belongsTo(Character::class);
}
public function adventure() {
return $this->hasOne(Adventure::class, 'id', 'adventure_id');
}
}
Notice this method:
public function getLogsAttribute($value) {
return json_decode(json_decode($value));
}
this is wrong. Obviously.
So the json that is stored is:
[{"adventure":"sample"}]
So when I call: $character->adventureLogs()->first()->logs I get: [{"adventure":"sample"}].
But if we change the function to what it's suppose to be:
public function getLogsAttribute($value) {
return json_decode($value);
}
then I get: "[{"adventure":"sample"}]".
I store data by doing:
AdventureLog::create([
// .... Other attributes
'logs' => ['adventure' => 'sample']
]);
So what am I doing wrong where I have to wrap the first json_decode into another one? I should not have to do that.
From the docs (https://laravel.com/docs/7.x/eloquent-mutators#array-and-json-casting):
... if your database has a JSON or TEXT field type that contains serialized JSON, adding the array cast to that attribute will automatically deserialize the attribute to a PHP array when you access it on your Eloquent model
So just add
protected $casts = [
...
'logs' => 'array',
];
I want to retrieve data using eloquent model and return it in the following json response.
user[
transactions[],
clients[
ubications[],
contactos[],
plans[],
]
]
Currently I am receiving user,transcation and clients data but I am not able to retreive the data inside client property e.g ubications[],contactos[] and plans[].So I need help regarding the sub properties as I have already implemented the relationship.
Here are the models I am using
User.php
class User extends Authenticatable
{
use Notifiable,HasApiTokens;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'username', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function token(){
return $this->access_token;
}
public function transaction(){
return $this->hasMany(Transaction::class);
}
public function plan(){
return $this->hasManyThrough(Plan::class,Client::class);
}
public function ubication(){
return $this->hasManyThrough(Ubicacion::class,Client::class);
}
public function contacto(){
return $this->hasManyThrough(Contacto::class,Client::class);
}
public function client(){
return $this->hasMany(Client::class);
}
}
Client.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Client extends Model
{
protected $fillable=[
'nombre',
'ruc',
'status',
'user_id'
];
public function contacto(){
return $this->hasMany(Contacto::class);
}
public function feedback(){
return $this->hasMany(Feedback::class);
}
public function plan(){
return $this->hasMany(Plan::class);
}
public function ubication(){
return $this->hasMany(Ubicacion::class);
}
public function user(){
return $this->belongsTo(User::class);
}
}
You can use nested data Eager Loading:
Laravel Documentation Eager Loading
$user = User::find(1)
->with('clients', 'clients.contacto', 'clients.ubications', 'clients.plans')
->get();
I'm having some trouble getting my Laravel relationships to work out. In my application, there is a one-to-many relationship between users and ideas. (A user may have multiple ideas.) I'm using Ardent.
Here's my User model:
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
use LaravelBook\Ardent\Ardent;
class User extends Ardent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
protected $fillable = array('first_name', 'last_name', 'email', 'password');
public $validation_errors;
public $autoPurgeRedundantAttributes = true;
public $autoHashPasswordAttributes = true;
public $autoHydrateEntityFromInput = true;
public static $passwordAttributes = array('password');
public static $rules = array(
'first_name' => 'required|between:1,16',
'last_name' => 'required|between:1,16',
'email' => 'required|email|unique:users',
'password' => 'required|between:6,100'
);
public function ideas()
{
return $this->hasMany('Idea');
}
}
And here's my Idea model:
use LaravelBook\Ardent\Ardent;
class Idea extends Ardent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'ideas';
protected $fillable = array('title');
public $validation_errors;
public $autoPurgeRedundantAttributes = true;
public $autoHydrateEntityFromInput = true;
public static $rules = array(
'title' => 'required'
);
public function user()
{
return $this->belongsTo('User');
}
}
Finally, here's my controller code:
class IdeasController extends BaseController {
public function postInsert()
{
$idea = new Idea;
$idea->user()->associate(Auth::user());
if($idea->save())
{
return Response::json(array(
'success' => true,
'idea_id' => $idea->id,
'title' => $idea->title),
200
);
}
else
{
return Response::json(array(
'success' => false,
'errors' => json_encode($idea->errors)),
400
);
}
}
}
$idea->save() throws the error:
{
"error": {
"type": "LogicException",
"message": "Relationship method must return an object of type Illuminate\\Database\\Eloquent\\Relations\\Relation",
"file": "\/var\/www\/3os\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Eloquent\/Model.php",
"line": 2498
}
}
At first, I was trying to set the user_id in the Idea like so:
$idea->user_id = Auth::id();
I then changed it to:
$idea->user()->associate(Auth::user());
But the results were the same.
Any suggestions would be much appreciated.
You cannot use associate in that direction, since it can only be used on a belongsTo relationship. In your case, an idea belongs to a user and not the other way around.
I suspect there is an error when saving, as you create an idea without the required title, and you then try to get the errors by calling $idea->errors, while it should be $idea->errors().
associate will work on belognsTo relationship , in your cause what you have to use is Attaching A Related Model. See more about Attaching A Related Mode in documentation.
I am new to TDD, so excuse me if this is a noob question.
I have Users and Projects, related in 2 different ways:
A Project Belongs to a User
Many Users can be authorized to many Projects.
I am following this tutorial http://net.tutsplus.com/tutorials/php/testing-like-a-boss-in-laravel-models/?search_index=16 , so I am using FactoryMuff.
This is my Project class:
<?php
use LaravelBook\Ardent\Ardent;
class Project extends Ardent
{
/**
* Ardent validation rules
*/
public static $rules = array(
'name' => 'required', // Project Title
'user_id' => 'required|numeric', // User owner id
);
/**
* Array used by FactoryMuff to create Test objects
*/
public static $factory = array(
'name' => 'string',
'user_id' => 'factory|User', // Will be the id of an existent User.
);
/**
* Belongs to user
*/
public function owner()
{
return $this->belongsTo( 'User', 'user_id');
}
/**
* Many Users can be authorized
*/
public function authorized()
{
return $this->belongsToMany( 'User', 'project_user', 'project_id', 'user_id')->withTimestamps();
}
}
And this is my User class:
<?php
use Zizaco\Entrust\HasRole;
use Zizaco\Confide\ConfideUser;
class User extends ConfideUser {
/**
* Ardent validation rules
*/
public static $rules = array(
'username' => 'required|min:4|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|between:4,16|confirmed',
'password_confirmation' => 'required|between:4,16',
);
/**
* Array used by FactoryMuff to create Test objects
*/
public static $factory = array(
'username' => 'string',
'email' => 'email',
'password' => '12345',
'password_confirmation' => '12345',
);
(more code)
/**
* Many Users can be authorized on many Projects
*/
public function authorized()
{
return $this->belongsToMany( 'Project', 'project_user', 'user_id', 'project_id')->withTimestamps();
}
/**
* Users can have many Projects
*/
public function projects()
{
return $this->hasMany( 'Project');
}
}
And this is my ProjectTest:
<?php
use Zizaco\FactoryMuff\Facade\FactoryMuff;
use Way\Tests\Factory;
class ProjectTest extends TestCase
{
use Way\Tests\ModelHelpers;
/**
* Test relationship with User
*/
public function testRelationshipWithUser()
{
// Instantiate new Project
$project = FactoryMuff::create('Project');
$this->assertEquals($project->user_id, $project->owner->id);
}
/**
* Test relationship with Authorized Users
*/
public function testRelationshipWithAuthorizedUsers()
{
// Instantiate new Project
$project = FactoryMuff::create('Project');
$project->save();
$project->authorized()->attach($project->user_id);
$this->assertEquals($project->user_id, $project->authorized->first()->id);
}
}
If I run the tests individually (commenting the other) both pass. However, if I run both, I get this error:
Caused by
PDOException: SQLSTATE[HY000]: General error: 1 table users has no column named password_confirmation
Why is it complaining about that column in the second test and not in the first? :S
You need to set public $autoPurgeRedundantAttributes = true; on your User model.
Ardent (which Confide extends from) will automatically purge the _confirmation fields, but by default it is set to false.