Laravel Relation Query - laravel

I have two tables course and module where each course belongs to a single module.
What I need is to eager load the module with course
my codes is :
$courses = Course::all();
$module = Coursemdule::all();

You need to create relation between both the table
In the course model
/**
* Get the user that owns the phone.
*/
public function module()
{
return $this->belongsTo('App\Coursemdule');
}
In the Coursemdule model
/**
* Get the Course record associated with the Coursemdule.
*/
public function course()
{
return $this->hasOne('App\Course');
}
To fetch this value
$phone = Course::find(1)->module;
OR
$phone = Course::with('module')->all();
I hope this will help you.

Related

Laravel Query n+1 - Querying custom attribute relationship count

On my User model I have the following...
/**
* #return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function transactions(): HasManyThrough
{
return $this->hasManyThrough(Transaction::class, Product::class);
}
/**
* #return int
*/
public function getNumberOfOrdersInLastSevenDaysAttribute()
{
$weekAgo = Carbon::now()->subWeek();
return $this->transactions()->whereDate('transactions.created_at', '>', $weekAgo)->count();
}
When I get all Users using my custom attribute even eager loading transactions I get an n+1 issue.
What options do I have to omit my n+1 problem?
You're eager loading the transactions relation, but the getNumberOfOrdersInLastSevenDaysAttribute method doesn't actually use the loaded relation. It writes a new query using the transactions() definition as a base. You can update the method to return a relation instead of the count and then use the withCount method to load the count.
public function ordersInLastSevenDays()
{
$weekAgo = Carbon::now()->subWeek();
return $this->transactions()->whereDate('transactions.created_at', '>', $weekAgo);
}
Then to load the count and access it.
$user = User::withCount(['ordersInLastSevenDays'])->first()
$user->orders_in_last_seven_days_count

How do i switch a custom implementation of a interface on a condition in http controller in Laravel

I have a method in my controller which basically is just storing a form to the database.
My form is very big and it has more than 30-40 fields.
So i need to store this information in different 3 tables on a condition.
For example :
foreach($request->all() as $answer):
if($answer->employeeType === 1){
//store data to type_one_table
} else if($answer->employeeType === 2){
//store data to type_two_table
} else if($answer->employeeType === 3){
//store data to type_two_table
} else {
//store data to some_other_table
}
endforeach;
So i was thinking to use some custom StoreInterface which will have some store method.
and extract above logic to its own implementation.
And then in my Controllers constructor receive that interface instance.
But how i can call which implementation do i need for particular condition.
Can i do this a controller ?
Or Should i use different strategy in this case.
please guide me.
Thanks.
You can have a general interface like this:
interface EmployeeStoreContract
{
public function saveAnswer();
}
Then implement this for all of the different ways you can save the answer, e.g.:
class FirstTypeEmployee implements EmployeeStoreContract
{
public function saveAnswer()
{
// do something
}
}
And in your controller to figure out how to save the request data:
public function store()
{
$employeeTypesMap = [
1 => 'FirstTypeEmployee',
2 => 'SecondTypeEmployee',
3 => 'ThirdTypeEmployee',
];
foreach ($request->all() as $answer) {
$employeeType = $answer->employeeType;
if (!array_key_exists($employeeType, $employeeTypesMap)) {
throw new \InvalidArgumentException('Answer type is not available.');
}
$employeeStrategyClass = "App\\Service\\Employee\\{$employeeTypesMap[$employeeType]}";
$employeeStrategyObject = new $employeeStrategyClass;
$employeeStrategyObject->saveAnswer($answer);
}
}
Please note the namespace where you have these classes App\Service\Employee, could be anything you want. Also feel free to make an abstract class or a trait to reuse some of the functionality of each employee class.
There can be many approaches.
I will suggest 2 ways that i would personally use:
1) Create dedicated eloquent models one for each case.
For example:
if($answer->employeeType === 1){
EmployeeOne::create($answer);
}
And your eloquent model can look like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class EmployeeOne extends Model
{
protected $table = 'type_one_table';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
// your db field names
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
public $timestamps = false;
}
Make sure that the array keys of the array you pass to the create function must match your db fields. I just gave you an example of the first if so you can take it from here.
2)
Another approach would be to create a model that accepts as a parameter the table name. So based on your if you are going to pass the table name you want to insert and the data to be inserted.
if($answer->employeeType === 1){
$this->myModel->insertData($answer,'type_one_table');
}
And in your model you are going to have something like:
public function insertData($answer,$table){
$query = DB::table($table)->insertGetId($answer); // returns the id of the new record
return $query;
}
So in every if-else statement you just change the table name passing to your model function and that's it.

Laravel 5.3 return result only if another row in the same table doesn't exist

Basically I have a followers table in which user_id and followable_id is stored. If for example user A follows user B a record is created. And if B follows A then a new record is created. I need to return result only if A is following B, but if B is also following A then do not return anything.
I tried various solutions non of them worked. Maybe someone could point me to the right direction thanks.
EDIT:
This works however it's ugly hacky and I want to avoid this:
$iFollows = Followers::where('user_id', '=', $currentUser->id)->get();
$myFollowers = Followers::where('followable_id', '=', $currentUser->id)->get();
foreach($myFollowers as $key => $value){
foreach($iFollows as $iFollow){
if($value->user_id == $iFollow->followable_id){
$myFollowers->forget($key);
}
}
}
Database Structure:
Is there a way to achieve same thing but with laravel query builder?
User implements trait called Followable
trait Followable
{
/**
* Collection of followers attached to this object
*
* #return Query|Collection
*/
public function followers()
{
return $this->morphToMany(
User::class, // related
'followable', // name
'followers', // table
'followable_id', // foreignKey
'user_id' // otherKey
)->where('status', 1)->withPivot('created_at', 'updated_at');
}
/**
* #return mixed
*/
public function followings()
{
return $this->morphToMany(
User::class, // related
'followable', // name
'followers', // table
'user_id', // foreignKey
'followable_id' // otherKey
)->where('status', 1)->withPivot('created_at', 'updated_at');
}
For me I think your code looks ok, just add comment to the code for the coder to easily understand what is happening.
I have a one query here to get what you're looking for, get all followers who doesn't follow you back:
$myFollowers = Followers::selectRaw('followers.*')
->leftJoin('followers AS followers2', 'followers.user_id', '=', 'followers2.followable_id')
->where('followers.followable_id', '=', $currentUser->id)
->whereNull('followers2.id')
->get();
Ok I have finally solved my issue with this method:
/**
* Class FollowableTrait
* #package App\Traits
*/
trait Followable
{
/**
* #param array $ids
* #return mixed
*/
public function newFollowers($ids)
{
return $this->morphToMany(
User::class, // related
'followable', // name
'followers', // table
'followable_id', // foreignKey
'user_id' // otherKey
)->where('status', 1)->whereNotIn('user_id', $ids)->withPivot('created_at', 'updated_at');;
}
Then in my controller:
$ids = $currentUser->followings()->pluck('followable_id')->toArray();
$newFollowers = $currentUser->newFollowers($ids)->get();

Relationships in InfyOm Generator

I have News and NewsCategories models which I have generated CRUD for using the relationship option.
I now need to generate a select list for the News model to select the NewsCategory it belongs to.
I know how to do this in the model but no idea how to do it using the repository pattern.
I can't see any examples in the docs so any help with this would be appreciated.
Thanks
NewsRepository
/**
* Configure the Model
**/
public function model()
{
return News::class;
}
News Model
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
**/
public function newsCategory()
{
return $this->belongsTo(NewsCategory::class);
}
News Controller
/**
* Show the form for creating a new News.
*
* #return Response
*/
public function create()
{
return view('news.create');
}
/**
* Store a newly created News in storage.
*
* #param CreateNewsRequest $request
*
* #return Response
*/
public function store(CreateNewsRequest $request)
{
$input = $request->all();
$news = $this->newsRepository->create($input);
Flash::success('News saved successfully.');
return redirect(route('news.index'));
}
If your repository extends InfyOm\Generator\Common\BaseRepository. The repository should update the model relations by it self. Just pass the relation values alongside the other inputs with the correct keys.
However, for deleting and reading (let's call them actions), you will need to query your data.
You can do that using repository methods, scope queries, or criteria classes.
(and call those filters).
Repository Methods:
// inside your controller
// some repository filtering method
$this->repository->whereHas('newsGroup', function($query){...});
$this->repository->hidden(['field_to_hide']);
...
// some action: delete, all or findWhere...
$this->repository->delete();
Scope Queries are callbacks that apply some queries on the model eloquent and return it.(unlike Eloquent scopes which accept and return Database\Eloquent\Builder)
$this->repository->scopeQuery(
function ($model){ return $model->where(...);
});
Or your
// some action: delete, update or findWhere...
$this->repository->delete();
The Criteria Way: you will create a class responsible on querying. It is an overkill for the simple use-cases.
// inside the controller
$this->repository->pushCriteria(new NewsBelongingToCategory ($group_id));
// App\Criteria\NewsBelongingToCategory.php
class NewsBelongingToCategory implements CriteriaInterface {
private $group_id;
public function __construct($group_id){
$this->group_id = $group_id;
}
public function apply($model, NewsRepositoryInterface $repository)
{
$group_id = $this->group_id;
$model = $model->whereHas('newsCategory',
function ($query) use ($group_id){
$query->where('group_id', '=', $group_id);
});
return $model;
}
}
// in your controller
$this->repository->delete();
Note that some actions ignore specific filters. For example, delete(id) and update($attributes, $id) does not use criteria, in the other hand lists($column, $key) does not use scopes.

Clone an Eloquent object including all relationships?

Is there any way to easily clone an Eloquent object, including all of its relationships?
For example, if I had these tables:
users ( id, name, email )
roles ( id, name )
user_roles ( user_id, role_id )
In addition to creating a new row in the users table, with all columns being the same except id, it should also create a new row in the user_roles table, assigning the same role to the new user.
Something like this:
$user = User::find(1);
$new_user = $user->clone();
Where the User model has
class User extends Eloquent {
public function roles() {
return $this->hasMany('Role', 'user_roles');
}
}
tested in laravel 4.2 for belongsToMany relationships
if you're in the model:
//copy attributes
$new = $this->replicate();
//save model before you recreate relations (so it has an id)
$new->push();
//reset relations on EXISTING MODEL (this way you can control which ones will be loaded
$this->relations = [];
//load relations on EXISTING MODEL
$this->load('relation1','relation2');
//re-sync everything
foreach ($this->relations as $relationName => $values){
$new->{$relationName}()->sync($values);
}
You may also try the replicate function provided by eloquent:
http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Model.html#method_replicate
$user = User::find(1);
$new_user = $user->replicate();
$new_user->push();
For Laravel 5. Tested with hasMany relation.
$model = User::find($id);
$model->load('invoices');
$newModel = $model->replicate();
$newModel->push();
foreach($model->getRelations() as $relation => $items){
foreach($items as $item){
unset($item->id);
$newModel->{$relation}()->create($item->toArray());
}
}
You may try this (Object Cloning):
$user = User::find(1);
$new_user = clone $user;
Since clone doesn't deep copy so child objects won't be copied if there is any child object available and in this case you need to copy the child object using clone manually. For example:
$user = User::with('role')->find(1);
$new_user = clone $user; // copy the $user
$new_user->role = clone $user->role; // copy the $user->role
In your case roles will be a collection of Role objects so each Role object in the collection needs to be copied manually using clone.
Also, you need to be aware of that, if you don't load the roles using with then those will be not loaded or won't be available in the $user and when you'll call $user->roles then those objects will be loaded at run time after that call of $user->roles and until this, those roles are not loaded.
Update:
This answer was for Larave-4 and now Laravel offers replicate() method, for example:
$user = User::find(1);
$newUser = $user->replicate();
// ...
Here is an updated version of the solution from #sabrina-gelbart that will clone all hasMany relationships instead of just the belongsToMany as she posted:
//copy attributes from original model
$newRecord = $original->replicate();
// Reset any fields needed to connect to another parent, etc
$newRecord->some_id = $otherParent->id;
//save model before you recreate relations (so it has an id)
$newRecord->push();
//reset relations on EXISTING MODEL (this way you can control which ones will be loaded
$original->relations = [];
//load relations on EXISTING MODEL
$original->load('somerelationship', 'anotherrelationship');
//re-sync the child relationships
$relations = $original->getRelations();
foreach ($relations as $relation) {
foreach ($relation as $relationRecord) {
$newRelationship = $relationRecord->replicate();
$newRelationship->some_parent_id = $newRecord->id;
$newRelationship->push();
}
}
This is in laravel 5.8, havent tried in older version
//# this will clone $eloquent and asign all $eloquent->$withoutProperties = null
$cloned = $eloquent->cloneWithout(Array $withoutProperties)
edit, just today 7 April 2019 laravel 5.8.10 launched
can use replicate now
$post = Post::find(1);
$newPost = $post->replicate();
$newPost->save();
When you fetch an object by any relation you want, and replicate after that, all relations you retrieved are also replicated. for example:
$oldUser = User::with('roles')->find(1);
$newUser = $oldUser->replicate();
If you have a collection named $user, using the code bellow, it creates a new Collection identical from the old one, including all the relations:
$new_user = new \Illuminate\Database\Eloquent\Collection ( $user->all() );
this code is for laravel 5.
Here is a trait that will recursively duplicate all the loaded relationships on an object. You could easily expand this for other relationship types like Sabrina's example for belongsToMany.
trait DuplicateRelations
{
public static function duplicateRelations($from, $to)
{
foreach ($from->relations as $relationName => $object){
if($object !== null) {
if ($object instanceof Collection) {
foreach ($object as $relation) {
self::replication($relationName, $relation, $to);
}
} else {
self::replication($relationName, $object, $to);
}
}
}
}
private static function replication($name, $relation, $to)
{
$newRelation = $relation->replicate();
$to->{$name}()->create($newRelation->toArray());
if($relation->relations !== null) {
self::duplicateRelations($relation, $to->{$name});
}
}
}
Usage:
//copy attributes
$new = $this->replicate();
//save model before you recreate relations (so it has an id)
$new->push();
//reset relations on EXISTING MODEL (this way you can control which ones will be loaded
$this->relations = [];
//load relations on EXISTING MODEL
$this->load('relation1','relation2.nested_relation');
// duplication all LOADED relations including nested.
self::duplicateRelations($this, $new);
I added this function in BaseModel to duplicate data with relations. It works in Laravel 9.
public function replicateWithRelationsAttributes(): static
{
$model = clone $this->replicate();
foreach ($this->getRelations() as $key => $relation) {
$model->setAttribute($key, clone $relation);
}
return $model;
}
Here's another way to do it if the other solutions don't appease you:
<?php
/** #var \App\Models\Booking $booking */
$booking = Booking::query()->with('segments.stops','billingItems','invoiceItems.applyTo')->findOrFail($id);
$booking->id = null;
$booking->exists = false;
$booking->number = null;
$booking->confirmed_date_utc = null;
$booking->save();
$now = CarbonDate::now($booking->company->timezone);
foreach($booking->segments as $seg) {
$seg->id = null;
$seg->exists = false;
$seg->booking_id = $booking->id;
$seg->save();
foreach($seg->stops as $stop) {
$stop->id = null;
$stop->exists = false;
$stop->segment_id = $seg->id;
$stop->save();
}
}
foreach($booking->billingItems as $bi) {
$bi->id = null;
$bi->exists = false;
$bi->booking_id = $booking->id;
$bi->save();
}
$iiMap = [];
foreach($booking->invoiceItems as $ii) {
$oldId = $ii->id;
$ii->id = null;
$ii->exists = false;
$ii->booking_id = $booking->id;
$ii->save();
$iiMap[$oldId] = $ii->id;
}
foreach($booking->invoiceItems as $ii) {
$newIds = [];
foreach($ii->applyTo as $at) {
$newIds[] = $iiMap[$at->id];
}
$ii->applyTo()->sync($newIds);
}
The trick is to wipe the id and exists properties so that Laravel will create a new record.
Cloning self-relationships is a little tricky but I've included an example. You just have to create a mapping of old ids to new ids and then re-sync.
In Laravel v5.8.10+ (Currently Laravel v9.x) If you need to work with Laravel replicate() model with relationships this could be a solution. Let's see two simple example.
app/Models/Product.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var array<string>
*/
protected $fillable = [
'name', 'price', 'slug', 'category_id'
];
}
app/Models/Category.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
/**
* Get all the products for the Category.
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function products()
{
return $this->hasMany(Product::class);
}
/**
* Clone the model into a new, non-existing instance with all the products.
*
* #return \App\Models\Category
*/
public function replicateRow()
{
$clon = $this->replicate();
$clon->push();
$this->products->each(
fn ($product) => $clon->products()->create($product->toArray())
);
return $clon;
}
}
Controller Code
<?php
namespace App\Http\Controllers;
use App\Models\Category;
class ReplicateController extends Controller
{
/**
* Handle the incoming request.
*
* #param \App\Models\Category $category
* #return void
*/
public function index(Category $category)
{
$newCategory = $category->replicateRow();
dd($newCategory);
}
}

Resources