Saving multiple IDs in a HasMany Eloquent relationship - php

I have a model Foo, which has many Bars:
class Foo extends Model
{
public function bars()
{
return $this->hasMany('App\Bar');
}
}
class Bar extends Model
{
public function foo()
{
return $this->belongsTo('App\Foo');
}
}
When saving a new Foo, the request payload comes with an array of Bar ids. I want to save these at the same time. This works:
public function store(StoreFoo $request)
{
$foo = Foo::create($request->validated());
foreach ($request->barIds as $barId) {
$foo->bars()->create(['bar_id' => $barId]);
}
}
My question is: is there a way to do this without a loop? I've tried sync and attach but these aren't applicable in this case.

The only way I can think of that you can achieve this without writing a loop yourself is by using the saveMany method on the HasMany relation. You can create instances of your Bar model and pass them all as an array to the saveMany method and that will save all of them and return an array of the created entities in response.
$foo->bars()->saveMany([new Bar(['id' => 1]), new Bar(['id' => 2])]);
That being said, Laravel uses a loop to save these models one by one under the hood so it doesn't really do much different to what you're doing now.
Similarly, there's also a createMany method that you can use in the same way as saveMany but instead of providing newly created models, you can provide arrays of attributes instead.

migration table sample
Schema::create('logs', function(Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->default(0)->index();
$table->string('type', 10)->index(); // add, update, delete
$table->string('table', 50)->index();
$table->unsignedBigInteger('row');
$table->dateTime('created_at');
});
Schema::create('log_fields', function(Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('log_id')->index();
$table->string('field', 50)->index();
$table->longText('old');
$table->longText('new');
});
model Log.php file
class Log extends Model
{
const UPDATED_AT = null;
protected $fillable = [
'user_id',
'type',
'table',
'row'
];
public function logFields()
{
return $this->hasMany(LogField::class);
}
}
model LogField.php file
class LogField extends Model
{
public $timestamps = false;
protected $fillable = [
'field',
'old',
'new'
];
public function log()
{
return $this->belongsTo(Log::class);
}
}
boot function for another model for save change in database.
hook created, updating and deleting for answer your question
public static function boot()
{
parent::boot();
static::created(function($resorce) {
$_log = new Log;
$_log->create([
'user_id' => session('uid', 0),
'type' => 'add',
'table' => $resorce->getTable(),
'row' => $resorce->fresh()->toArray()['id']
]);
return true;
});
static::updating(function($resorce) {
$_log = new Log;
$log = $_log->create([
'user_id' => session('uid', 0),
'type' => 'update',
'table' => $resorce->getTable(),
'row' => $resorce->fresh()->toArray()['id']
]);
foreach($resorce->getDirty() as $field => $new) {
$log->logFields()->create([
'field' => $field,
'old' => $resorce->fresh()->toArray()[$field],
'new' => $new
]);
}
return true;
});
static::deleting(function($resorce) {
$_log = new Log;
$log = $_log->create([
'user_id' => session('uid', 0),
'type' => 'delete',
'table' => $resorce->getTable(),
'row' => $resorce->id,
]);
foreach($resorce->fresh()->toArray() as $field => $value) {
$log->logFields()->create([
'field' => $field,
'old' => '',
'new' => $value
]);
}
return true;
});
}
Hope I have helped you to understand this.

Related

Laravel BelongsTo returning empty object

I have a table called messages and users. the users is the default users table generated by laravel auth. for some reason in my postMessage function in my controller when i try the return
return response()->json(['s' => $broadcastMessage, 'r' => $broadcastMessage->MessageOwner()]);
the relationship ship returns an empty object. i know that the MessageOwner relationship works because i use it in a different function and it works fine but i can't figure out why it wont work here? I made sure and it stores the user_id and it's the correct id.
Note that it does return the message.
here is migration up function
Schema::create('messages', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users');
$table->longText('message');
$table->timestamps();
});
here is my message model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
protected $table = 'messages';
protected $fillable = [
'user_id', 'message'
];
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function MessageOwner()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function job()
{
return $this->belongsToMany(Job::class);
}
}
and here is my controller function
public function postMessage(Request $request)
{
try {
$message = Message::create([
'user_id' => $request->input('user_id'),
'message' => $request->input('message')
]);
$job = Job::find($request->input('job_id'));
$job->messages()->attach($message);
$broadcastMessage = Message::find($message->id);
return response()->json(['s' => $broadcastMessage, 'r' => $broadcastMessage->MessageOwner()]);
event(new MessagePushed($broadcastMessage));
return response()->json([
'success' => true,
'message' => 'success',
]);
} catch (\Exception $exception) {
return response()->json([
'success' => false,
'error' => true,
'message' => $exception->getMessage()
], 500);
}
}
You are calling the relationship method, not the it's value.
Try without the ():
return response()->json(['s' => $broadcastMessage, 'r' => $broadcastMessage->MessageOwner]);
Also you should consider using lower_snake_case for all your relationship methods and name the foreign key by relationshio_name_id, so you don't need to manually define the foreign key name, Laravel will automatically guess it.

How to store the relation between tables

I created a table in relation to two other tables, however I do not know how to relate them when creating new projects, how could I do that?
I'll create the categories in a separated page in my admin, and when the user create's a new project he will be able to select an array of categories coming from the table.
My question is, how can I store the relation when POST the data? I've never done this before.
Project model
class Project extends Model
{
protected $table = 'projects';
protected $fillable = [
'name',
'slug',
'header',
'desc',
'about',
'url',
'status'
];
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function category()
{
return $this->belongsToMany(Category::class);
}
public function categories()
{
return $this->hasMany(Category::class);
}
}
Category model
class Category extends Model
{
protected $table = 'categories';
protected $fillable = [
'name',
'status'
];
public function subCategory()
{
return $this->hasMany(SubCategory::class);
}
public function projects()
{
return $this->belongsToMany(Project::class);
}
}
My actual Post create
public function postCreate(ProjectCreateRequest $request, Customer $customer)
{
//Array
$categories = $request->categories;
$customer->projects()->create([
'name' => $request->name,
'header' => $request->header,
'desc' => $request->desc,
'about' => $request->about,
'url' => $request->url,
]);
//How do I store the relation?
return redirect('admin/clientes/editar/' . $customer->id);
}
use attach or sync for many to many relationships
reference : https://laravel.com/docs/5.1/eloquent-relationships#inserting-related-models
public function postCreate(ProjectCreateRequest $request, Customer $customer)
{
//Array
$categories = $request->categories;
$projects = $customer->projects()->create([
'name' => $request->name,
'header' => $request->header,
'desc' => $request->desc,
'about' => $request->about,
'url' => $request->url,
]);
//suppose here the $categories is an array of ids or a single integer variable id of the category that are related
$projects->categories()->attach($categories);
return redirect('admin/clientes/editar/' . $customer->id);
}
First: Make sure you have the following table in the database in order for the many to many relationship to work
table name : category_project
columns: category_id, project_id
Second: class Project needs only the following function for categories relation to work:
public function categories()
{
return $this->belongsToMany(Category::class);
}
Third: class Category needs only the following function for projects relation to work:
public function projects()
{
return $this->belongsToMany(Project::class);
}
Fourth: postCreate function should be as follow:
public function postCreate(ProjectCreateRequest $request, Customer $customer)
{
//should be an array of categories ids
$categories = $request->categories;
$project = $customer->projects()->create([
'name' => $request->name,
'header' => $request->header,
'desc' => $request->desc,
'about' => $request->about,
'url' => $request->url,
]);
//How do I store the relation? you can use attach or sync
$project->categories()->sync($categories);
return redirect('admin/clientes/editar/' . $customer->id);
}

Laravel call to member function create() on a non-object

I'm trying to seed a database using some model factories but I'm getting error call to member function create() on a non-object
Below are my model factories:
$factory->define(App\Organisation::class, function ($faker) {
return [
'name' => $faker->company,
];
});
$factory->define(App\Department::class, function ($faker) {
return [
'name' => $faker->catchPhrase,
'organisation_id' => factory(App\Organisation::class)->make()->id,
];
});
$factory->define(App\User::class, function ($faker) {
return [
'email' => $faker->email,
'password' => str_random(10),
'organisation_id' => factory(App\Organisation::class)->make()->id,
'remember_token' => str_random(10),
];
});
In my seeder I'm using the following to create 2 organizations and a associate a user and a department to each organization and then to make a user the manager of that department:
factory(App\Organisation::class, 2)
->create()
->each(function ($o)
{
$user = $o->users()->save(factory(App\User::class)->make());
$department = $o->departments()->save(factory(App\Department::class)->make());
$department->managedDepartment()->create([
'organisation_id' => $o->id,
'manager_id' => $user->id,
]);
});
However I'm getting fatalerrorexception call to member function create() on a non-object
I thought $department is an object?
My department model is as follows:
class Department extends Model
{
protected $fillable = ['name','organisation_id'];
public function organisation()
{
return $this->belongsTo('App\Organisation');
}
/* a department is managed by a user */
public function managedDepartment()
{
$this->hasOne('App\ManagedDepartment');
}
}
And my managedDepartment model is as follows:
class ManagedDepartment extends Model
{
protected $table = 'managed_departments';
protected $fillable = ['organisation_id', 'department_id', 'manager_id',];
public function department()
{
$this->belongsTo('App\Department');
}
public function manager()
{
return $this->belongsTo('App\User');
}
}
Can anyone help?
Try to return your relation
public function department()
{
return $this->belongsTo('App\Department');
}
And here
/* a department is managed by a user */
public function managedDepartment()
{
return $this->hasOne('App\ManagedDepartment');
}
I think it will resolve your problem.
Firstly, do not make foreign keys fillable!
Secondly, where is your organisation function in ManagedDepartment? You should create one, otherwise the following will not work, because association is not possible.
Thirdly, I think you should change make() to create() in the following
$factory->define(App\Organisation::class, function ($faker) {
return [
'name' => $faker->company,
];
});
$factory->define(App\Department::class, function ($faker) {
return [
'name' => $faker->catchPhrase,
'organisation_id' => factory(App\Organisation::class)->create()->id,
];
});
$factory->define(App\User::class, function ($faker) {
return [
'email' => $faker->email,
'password' => str_random(10),
'organisation_id' => factory(App\Organisation::class)->create()->id,
'remember_token' => str_random(10),
];
});
Furthermore:
factory(App\Organisation::class, 2)
->create()
->each(function ($o)
{
$user = factory(App\User::class)->create();
$o->users()->attach($user->id);
$department = factory(App\Department::class)->create();
$o->departments()->attach($department);
$managedDep = new ManagedDepartment();
$managedDep->associate($o);
$managedDep->associate($user);
$managedDep->associate($department);
$managedDep->save();
});

Laravel Validation With Multi Select

I'm making a multi select form element for updating schools and specialties pivot table school_specialty. The problem is that when I change only something in multi select not other inputs or textareas, I can't listen model events so I can't sync school_specialty table. But when I fill in any other input it's works perfect. Here's my multi select from blade:
{{Form::select('specialties[]', $specialties_data, $school->specialties, array('multiple' => 'true', 'id' => 'multi-select'))}}
This is my update method from school controller:
public function update($id)
{
$data = Input::only('name', 'type_id', 'description', 'info_specialties', 'contacts', 'specialties', 'financing_id', 'district_id', 'city_id');
$school = School::find($id);
$school->name = $data['name'];
$school->type_id = $data['type_id'];
$school->description = $data['description'];
$school->info_specialties = $data['info_specialties'];
$school->contacts = $data['contacts'];
$school->cover_photo = Input::file('cover_photo');
$school->set_specialties = $data['specialties'];
$school->financing_id = $data['financing_id'];
$school->set_district_id = $data['district_id'];
$school->city_id = $data['city_id'];
try {
$school->save();
} catch (ValidationException $errors) {
return Redirect::route('admin.schools.edit', array($id))
->withErrors($errors->getErrors())
->withInput();
}
return Redirect::route('admin.schools.edit', array($id))
->withErrors(array('mainSuccess' => 'School was created.'));
}
And here's my example school model:
<?php
class School extends Eloquent {
protected $table = 'schools';
protected $fillable = array('name', 'type_id', 'description', 'city');
protected $guarded = array('id');
protected $appends = array('specialties');
public $set_specialties;
public $set_district_id;
protected static function boot()
{
parent::boot();
static::updating(function($model)
{
$data = array(
'name' => $model->name,
'type_id' => $model->type_id,
'description' => $model->description,
'specialties' => $model->set_specialties,
'city_id' => $model->city_id
);
$rules = array(
'name' => 'required|min:3|max:50',
'type_id' => 'required|min:1|max:300000',
'description' => 'required|min:10',
'specialties' => 'required|array',
'city_id' => 'required|min:1|max:300000'
);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new ValidationException(null, null, null, $validator->messages());
} else {
return true;
}
});
static::updated(function($model)
{
if ( $model->set_specialties != null )
{
$model->specialty()->sync($model->set_specialties);
}
});
}
public function specialty()
{
return $this->belongsToMany('Specialty', 'school_specialty');
}
}
?>
When updating only school specialities the School model events aren't triggered because the School model stays the same.
I think the simplest and most elegant solution is to touch the school model instance. This will modify the updated_at field for the School object and thus trigger the model events.
To do this add the following lines before the try/catch block:
if ($school->set_specialties !== null) {
$school->touch();
}
Also, validation shouldn't be handled in the model observers. Check form request validation here: https://laravel.com/docs/5.6/validation#form-request-validation.

How to disable 'create_at' and 'update_at' in Laravel's seed file?

I don't want to use rows 'update_at' and 'create_at', but Laravel's seed file is trying to update it. How can I disable it?
Here is the code that I'm using:
use Illuminate\Database\Migrations\Migration;
class SeedUsersTable extends Seeder {
// $timestamps = false; <=== will return error
// public static $timestamps = false; <=== will return error
public function run()
{
DB::table('users')->delete();
User::create(array(
'id' => 1,
'name' => 'Админ',
'password' => Hash::make('admin'),
'login' => 'admin'
));
}
}
According to the Laravel docs,
... by default, Eloquent will maintain the created_at and updated_at columns on your database table automatically. Simply add these timestamp columns to your table and Eloquent will take care of the rest.
If you do not wish for Eloquent to maintain these columns, In your User model add the following:
class User extends Eloquent {
public $timestamps = false;
}
use Illuminate\Database\Migrations\Migration;
class SeedUsersTable extends Seeder {
public function run()
{
DB::table('users')->delete();
$user = new User(array(
'id' => 1,
'name' => 'Админ',
'password' => Hash::make('admin'),
'login' => 'admin'
));
$user->timestamps = false;
$user->save();
}
}

Categories