I'm having troubles to setup the right Eloquent relationships (belongsTo, hasMany, ...) for a pivot table.
I will abbreviate code for clarity.
I have two important tables: 'parties' and 'p2p_relations'.
This is the migration for parties
public function up()
{
Schema::create('parties', function ($table) {
$table->increments('id');
$table->string('name');
$table->unsignedInteger('kind');
$table->timestamps();
$table->softDeletes();
$table->foreign('kind')->references('id')->on('kinds');
});
}
This is the migration for p2p_relations (party to party relations)
public function up()
{
Schema::create('p2p_relations', function ($table) {
$table->bigIncrements('id');
$table->unsignedInteger('context');
$table->unsignedInteger('reference');
$table->datetime('start');
$table->datetime('end')->nullable();
$table->unsignedInteger('kind')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreign('context')->references('id')->on('parties');
$table->foreign('reference')->references('id')->on('parties');
$table->foreign('kind')->references('id')->on('kinds');
});
}
The model for Party
class Party extends Ardent
{
use SoftDeletingTrait;
protected $softDelete = true;
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $table = 'parties';
public static $rules = array(
'name' => 'required',
'kind' => 'required|numeric'
);
}
The model for Relation
class Relation extends Ardent
{
use SoftDeletingTrait;
protected $softDelete = true;
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $table = 'p2p_relations';
public static $rules = array(
'context' => 'required|numeric',
'reference' => 'required|numeric',
'kind' => 'required|numeric',
'start' => 'required|date',
'end' => 'date'
);
}
How can I set relationships so I can associate parties as context or reference in a relationship.
I thought belongsTo will help like so in class Relation
public function context() {
return $this->belongsTo('Party', 'context', 'id');
}
public function reference() {
return $this->belongsTo('Party', 'reference', 'id');
}
But when I run this unit-test I get an error: Undefined property: Relation::$context
$context = new Party();
$context->name = 'Person A';
$context->kind = 1;
$context->save();
$ref = new Party();
$ref->name = 'Company B';
$ref->kind = 2;
$ref->save();
$relation = new Relation();
$relation->start = new DateTime();
$relation->context()->associate($context);
$relation->reference()->associate($ref);
$relation->kind = 3;
$relation->save();
Any thoughts? I'm really a newbie to this framework.
Thanks to the comments provided I've learned a lot :-)
Updated my Party Model:
public function references() {
return $this->belongsToMany('Party', 'p2p_relations', 'context', 'reference')
->withPivot('reference', 'start', 'kind')
->withTimestamps() ;
}
No Relation model needed.
The pivot table works perfectly.
Thanks
Related
trucks table have brand_id. When I create a truck I want to save the brand_id.
Truck one to one Brand.
Migration Brands
public function up()
{
Schema::create('brands', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
Migration Trucks
public function up()
{
Schema::create('trucks', function (Blueprint $table) {
$table->id();
$table->integer('brand_id');
$table->integer('year');
$table->string('owner_full_name');
$table->integer('number_of_owners')->nullable();
$table->text('comment')->nullable();
$table->timestamps();
});
}
Model Brand
class Brand extends Model
{
protected $table = 'brands';
protected $fillable = ['name'];
public function truck()
{
return $this->belongsTo(Truck::class);
}
}
Model Truck
class Truck extends Model
{
protected $table = 'trucks';
protected $fillable = [
'brand_id',
'year',
'owner_full_name',
'owner_numbers',
'comment'
];
public function brand()
{
return $this->hasOne(Brand::class);
}
}
Truck Form (I am using https://kristijanhusak.github.io/laravel-form-builder/) Form Builder
class TruckForm extends Form
{
public function buildForm()
{
$this
->add('brand_id', Field::SELECT, [
'empty_value' => 'Select Brand',
'choices' => Brand::pluck('name')->all()
])
->add('year', Field::NUMBER, [
'rules' => ['required', 'gt:1900', 'lte:' . Carbon::now()->year]
])
->add('owner_full_name', Field::TEXT, [
'rules' => ['required', new MinWordsRule(2)]
])
->add('number_of_owners', Field::NUMBER, ['rules' => 'nullable'])
->add('comment', Field::TEXTAREA, ['rules' => 'nullable'])
->add('Save or Create', Field::BUTTON_SUBMIT, [
'attr' => ['class' => 'btn btn-success']
]);
}
}
My need when create truck brand_id save being his brand
Truck Controller
public function create(FormBuilder $formBuilder)
{
$brandForm = $formBuilder->create(TruckForm::class, [
'method' => 'POST',
'url' => route('trucks.store')
]);
return view('landing.trucks.create', compact('brandForm'));
}
public function store(FormBuilder $formBuilder, Request $request)
{
$brandForm = $formBuilder->create(TruckForm::class);
$brandForm->redirectIfNotValid();
$object = new Truck();
$object->fill($request->all());
$object->save();
$object->brand()->save($request->input('brand_id'));
return redirect()->route('trucks.index');
}
First, make sure to validate your user inputs even if they came from an HTML select tag
//...
'brand_id' => 'required|exists:brands,id' // make sure your brand_id is a valid ID
//...
Then
Keep your controller simple
public function store(FormBuilder $formBuilder, Request $request)
{
$brandForm = $formBuilder->create(TruckForm::class);
$brandForm->redirectIfNotValid();
$object = new Truck();
// this line will map the brand_id attr to $obj->brand_id and link the 2 models to each others ...
$object->fill($request->all());
$object->save();
return redirect()->route('trucks.index');
}
Your relationships are switched around, as you can see here, since the Truck has the brand_id it should have the belongsTo and Brand should have the hasOne, like this:
Brand model:
class Brand extends Model
{
public function truck()
{
return $this->hasOne(Truck::class);
}
}
Truck model:
class Truck extends Model
{
public function brand()
{
return $this->belongsTo(Brand::class);
}
}
And as you can see here, you can simplify your store method to something like this:
public function store(FormBuilder $formBuilder, Request $request)
{
$form = $formBuilder->create(TruckForm::class);
$form->redirectIfNotValid();
Truck::create($form->getFieldValues());
return redirect()->route('trucks.index');
}
Or using the FormBuilderTrait:
use Kris\LaravelFormBuilder\FormBuilderTrait;
use App\Forms\TruckForm;
class TruckController extends Controller
{
use FormBuilderTrait;
public function store(Request $request)
{
$form = $this->form(TruckForm::class);
$form->redirectIfNotValid();
Truck::create($form->getFieldValues());
return redirect()->route('trucks.index');
}
}
Using Lumen 5.1, I'd like to know how to to CRUD on a translatable field, in my case it's name.
This is migration file
class CreateDomainHolidays extends Migration
{
protected $table = 'domain_holidays';
protected $app_table = true;
public function up()
{
Schema::create($this->getTable(), function (Blueprint $table) {
$table->increments('id');
$table->integer('domain_id')->unsigned()->nullable();
$table->foreign('domain_id')->references('id')
->on(self::getTableName('domains'))->onDelete('cascade');
$table->string('name')->nullable()->default('')->translatable = true;
$table->dateTime('start_date');
$table->dateTime('end_date');
$table->tinyInteger('half_day_start')->default(0);
$table->tinyInteger('half_day_end')->default(0);
$this->parenttable = $table;
});
$this->createTableWithTranslation($this->parenttable);
}
public function down()
{
$this->dropTranslationTable($this->getTable());
Schema::drop($this->getTable());
}
}
This is my model
class Holiday extends BaseModel
{
protected $table = 'domain_holidays';
protected $app_table = true;
public $timestamps = false;
protected $translation = true;
public function translations()
{
return $this->hasMany('App\Models\General\HolidayTranslations', 'parent_id');
}
}
class HolidayTranslations extends BaseTranslationModel
{
protected $table = 'domain_holidays_i18n';
protected $primaryKey = 'translation_id';
}
}
domain_holidays contains
id
domain_id
start_date
end_date
half_day_start
half_day_end
domain_holidays_i18n contains
translation_id
parent_id
lang
name
Something like this is not working
public static function setHoliday($domainId, $name, $startDate, $endDate, $halfDayStart, $halfDayEnd)
{
Holiday::unguard();
return Holiday::create([
'domain_id' => $domainId,
'name' => $name,
'start_date' => $startDate,
'end_date' => $endDate,
'half_day_start' => $halfDayStart,
'half_day_end' => $halfDayEnd
]);
}
Postman would return an error
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'name' in 'field list'
As you can see on the following image my laravel relation between shoporder and shoporderroutingstepplans is not as it has to be.
I have no idea what I exactly did wrong so I hope someone can help me out. In the code beneath I have left some fields out of the code to make it more legible.
class shoporder extends Model
{
protected $primaryKey = 'ID';
protected $fillable = [
'CADDrawingURL',
'ID',
'Costcenter',
'CostcenterDescription',
'Costunit',
'CostunitDescription',
'Created',
'Creator',
'CreatorFullName',
'Description',
'ShopOrderParent',
'ShopOrderParentNumber',
'ShopOrderRoutingStepPlanCount',
'Status',
'SubShopOrderCount',
];
public function shopOrderRoutingStepPlans() {
return $this->hasMany('App\shopOrderRoutingStepPlan', 'ShopOrder', 'ID');
}
}
class ShopOrderRoutingStepPlan extends Model
{
protected $primaryKey = 'ID';
public $table = "shoporderroutingstepplans";
protected $fillable = [
'Account',
'ID',
'AccountName',
'AccountNumber',
'AttendedPercentage',
'Backflush',
'Created',
'Creator',
'CreatorFullName',
'Description',
'ShopOrder',
];
public function shopOrder() {
return $this->belongsTo('App\shopOrder', 'ShopOrder', 'ID');
}
}
This is the code Im executing to get the relations of 1 shoporder in the controller.
$orders = shopOrder::find('0600959e-6b92-4135-8ea8-1fa2fd92a916')->shopOrderRoutingStepPlans()->get();
In the shoporder migration I defined the primary key:
$table->string('ID')->unique();
$table->primary('ID');
In the shoporderroutingstepplans migration I defined the foreign key as followed.
$table->string('ID')->unique();
$table->primary('ID');
$table->foreign('ShopOrder')
->references('ID')
->on('shoporders');
You must switch the order of the last two parameters:
From
return $this->hasMany('App\shopOrderRoutingStepPlan', 'ShopOrder', 'ID');
To
return $this->hasMany('App\shopOrderRoutingStepPlan', 'ID', 'ShopOrder');
The parameters are
model,
name of column in the linked model,
name of column in this model.
TL;DR
Trying to get three models to interact using eloquent for a rest api.
User - belongsToMany(pulls)
Pull - belongsToMany(user) && belongsToMany(boxes)
Box - belongsToMany(pulls)
The pull_user table is working perfectly, I can just attach a user after I save a pull. Saving a box works fine but the attach doesn't work/enter anything into the pivot table (I get no errors though).
The Problem
I can't get a pivot table that associates two of my models together to attach() after a save. I have the three models listed above, the pivot is working for pull_user but not for pull_box even though the save for box is working perfectly. I am able to save a box without an error but the association just never occurs (no error).
The Code
pull_box.php
class PullBox extends Migration
{
public function up()
{
Schema::create('pull_box', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->integer('pull_id');
$table->integer('box_id');
});
}
public function down()
{
Schema::dropIfExists('pull_box');
}
}
Pull.php
class Pull extends Model
{
protected $fillable = ['from', 'to', 'runit_id', 'start_time', 'end_time', 'box_count', 'pull_status', 'audit_status', 'status', 'total_quantity', 'accuracy'];
public function users(){
return $this->belongsToMany('App\User');
}
public function boxes(){
return $this->belongsToMany('App\Box');
}
}
Box.php
class Box extends Model
{
protected $fillable = ['user_id','from', 'to', 'runit_id', 'start_time', 'end_time', 'pull_id', 'total_quantity', 'status', 'accuracy'];
public function pulls(){
return $this->belongsToMany('App\Pull');
}
}
BoxController.php
public function store(Request $request)
{
$this->validate($request, [
'user_id' => 'required|integer',
...
]);
$user_id = $request->input('user_id');
...
$box = new Box([
'user_id' => $user_id,
...
]);
$pull = Pull::whereId($pull_id)->first();
if($box->save()){
$pull->boxes()->attach($box->id);
$box->view_box = [
'href' => 'api/v1/box/' . $box->id,
'method' => 'GET'
];
$message = [
'msg' => 'Box created',
'box' => $box,
'pull' => $pull_id
];
return response()->json($message, 201);
}
$response = [
'msg' => 'Box creation error, contact supervisor',
];
return response()->json($response, 404);
}
The Solution
I need to know how I can get this association working. I am going to need to add a new layer in under the pull for Item, but I don't want to move one before I solve this. I think that my problem has to stem from a syntactical/logical error on my part but I can't see it. There are a bunch of questions on SO that are very close to giving me a solution, but after reading them I wasn't able to solve my problem.
Any help is appreciated.
Try renaming your pull_box table to box_pull, pivot tables on laravel must be in alphabetical order. If you want to use custom name on pivot table you have to extends your pivot, for example:
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class PullBox extends Pivot
{
protected $table = 'pull_box';
}
And your many to many relationships:
class Pull extends Model
{
protected $fillable = ['from', 'to', 'runit_id', 'start_time', 'end_time', 'box_count', 'pull_status', 'audit_status', 'status', 'total_quantity', 'accuracy'];
public function users(){
return $this->belongsToMany('App\User');
}
public function boxes(){
return $this->belongsToMany('App\Box')->using('App\PullBox');
}
}
class Box extends Model
{
protected $fillable = ['user_id','from', 'to', 'runit_id', 'start_time', 'end_time', 'pull_id', 'total_quantity', 'status', 'accuracy'];
public function pulls(){
return $this->belongsToMany('App\Pull')->using('App\PullBox');
}
}
I have 3 models: Priority, Task and User
Priority
can be assigned to many tasks
belongs to one user
Code ...
class Priority extends Model
{
protected $fillable = ['name', 'hexcolorcode'];
protected $casts = [
'user_id' => 'int',
];
public function task()
{
return $this->hasMany(Task::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Task
has one priority assigned
belongs to one user
Code ...
class Task extends Model
{
protected $fillable = ['name'];
protected $casts = [
'user_id' => 'int',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function priority()
{
return $this->hasOne(Priority::class);
}
}
User
can have many tasks
can have many priorities
Code ...
class User extends Authenticatable
{
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
public function tasks()
{
return $this->hasMany(Task::class);
}
public function priorities()
{
return $this->hasMany(Priority::class);
}
}
TaskController
class TaskController extends Controller
{
protected $tasks;
private $priorities;
public function __construct(TaskRepository $tasks, PriorityRepository $priorities)
{
$this->middleware('auth');
$this->tasks = $tasks;
$this->priorities = $priorities;
}
public function index(Request $request)
{
return view('tasks.index', [
'tasks' => $this->tasks->forUser($request->user()),
'priorities' => $this->priorities->forUser($request->user())
]);
}
public function store(Request $request)
{
$this->validate($request, [
'name' => 'required|max:255',
'description' => 'required',
'priority' => 'required'
]);
$request->user()->tasks()->create([
'name' => $request->name,
'description' => $request->description,
'priority_id' => $request->priority
]);
return redirect('/tasks');
}
public function edit(Task $task)
{
$this->authorize('edit', $task);
return view('tasks.edit', compact('task'));
}
public function update(Request $request, Task $task)
{
$this->validate($request, [
'name' => 'required|max:255',
]);
$task->update($request->all());
return redirect('/tasks');
}
public function destroy(Request $request, Task $task)
{
$this->authorize('destroy', $task);
$task->delete();
return redirect('/tasks');
}
}
Error
Now when I want to store a task it gives me following error:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or
update a child row: a foreign key constraint fails (tasks.tasks,
CONSTRAINT tasks_priority_id_foreign FOREIGN KEY (priority_id)
REFERENCES priorities (id)) (SQL: insert into tasks (name,
user_id, updated_at, created_at) values (test task, 1,
2016-05-01 14:11:21, 2016-05-01 14:11:21))
Is it possible with this construct or do I have to use Polymorphic Relations between priority table and tasks table?
My Tables
Database Model Picture: http://picpaste.de/mysql-kBH4tO5T.PNG
class CreatePrioritiesTable extends Migration
{
public function up()
{
Schema::create('priorities', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->char('name');
$table->char('hexcolorcode', 7);
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
}
}
class CreateTasksTable extends Migration
{
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('name');
$table->text('description');
$table->integer('priority_id')->unsigned();
$table->timestamps();
$table->foreign('priority_id')->references('id')->on('priorities');
$table->foreign('user_id')->references('id')->on('users');
});
}
}
UPDATE
PriorityController
class PriorityController extends Controller
{
protected $priorities;
public function __construct(PriorityRepository $priorities)
{
$this->middleware('auth');
$this->priorities = $priorities;
}
public function index(Request $request)
{
return view('priority.index', [
'priorities' => $this->priorities->forUser($request->user()),
]);
}
public function store(Request $request)
{
$this->validate($request, [
'name' => 'required|max:255',
'hexcolorcode' => 'required|max:7'
]);
$request->user()->priorities()->create($request->all());
return redirect('/priorities');
}
public function edit(Priority $priority)
{
$this->authorize('edit', $priority);
return view('priority.edit', compact('priority'));
}
public function update(Request $request, Priority $priority)
{
$this->validate($request, [
'name' => 'required|max:255',
'hexcolorcode' => 'required|max:7'
]);
$priority->update($request->all());
return redirect('/priorities');
}
public function destroy(Request $request, Priority $priority)
{
$this->authorize('destroy', $priority);
$priority->delete();
return redirect('/priorities');
}
}
In your store method. The create method it says :
user->tasks(). This bit will only inserting data in user and tasks table. Thats why you have foreign key error. As you are not updating one of your tables(priority) and also you have added foreign keys in tables as i csn see from migration.
What you are trying to achieve is right if you had a pivot table called task_user where you had userid, taskid and a priority_id. That would be perfect as you would simply update the priority column with userid and taskid. Thats one way but it will require few changes OR you can insert data into user and tasks table seperately for which you will need to just change the query. What do you prefer? I can show you an example for which you like.
//for Priority
// store
$priorities = new Priority;
$priorities->name = Input::get('priority_name');
$priorities->hexcolorcode = Input::get('hexcolorcode');
$priorities->save();
$priority = Priority::select('id')->orderBy('created_at', 'desc')->first();
//for Task
$tasks = new Task;
$tasks->name = Input::get('task_name');
$tasks->priority_id = Input::get('priority');
$priorities->save();
// redirect
return Redirect::to('some view please change this');
Please explain views or show screenshots, how are you handling it. For now, I have given you the solution with one controller handling priorities and tasks.
SOLUTION
I added the priority_id attribute to the fillable array inside the task model.
protected $fillable = ['name', 'description', 'priority_id'];
I had an understanding problem, because user_id is assigned automatically from the framework, but it isn't specified in fillable array. That's because I say $request->user->tasks->create(); it is assigned from the framework, but priority_id is assigned through the user, so it is mass assigned and need to be specified in the fillable array.
#Murlidhar Fichadia