Trying to get referenced Laravel Eloquent data from another table - laravel

I'm just learning Laravel, and this is probably super easy for someone experienced with the framework, but I'm trying to get data from a different table and not sure what I'm doing wrong.
I have two tables, Users, which has fields first_name, last_name, and pair_id. The pair_id column is programmatically restricted to either null or an integer in two rows that designates a "pair". The there's the confirmed_pair table that has one pair per row and has a unique pair_id column, that refers to the one in the Users table.
In the ConfirmedPair model, I did this:
class ConfirmedPair extends Model
{
public function pair() {
return $this->hasMany(User::class, 'pair_id');
}
public function pair_names() { // returns an array of names of the players in the confirmed pair
$names = User::where("pair_id", $this->get(["pair_id"])[0]->pair_id)->get(['first_name','last_name']);
if (count($names) == 2) { // if this pair exists
return [
$names[0]['first_name']." ".$names[0]['last_name'],
$names[1]['first_name']." ".$names[1]['last_name']
];
}
return null;
}
}
In the controller, I did this:
class PageController extends Controller
{
public function index()
{
$confirmed_pair = new ConfirmedPair;
return view('page', compact('confirmed_pair'));
}
}
And in the blade view if I do this:
#for ($i = 1; $i < 10; $i++)
{{ $confirmed_pair->find($i)->pair_names()[0] }}<br>
#endfor
No matter what $i's value is, it returns the values of confirmed_pair id #1.
What am I doing wrong? Thanks!

If pair_id is assigned to 2 users
class ConfirmedPair extends Model {
public function pairs() {
return $this->hasMany(User::class, 'pair_id', 'pair_id');
}
public function pair_names() {
if ($this->pairs->count() != 2) return null;
// $this->pairs returns you a Collection of Users
// try to dd($this->pairs) to see in action
$pairs = $this->pairs->toArray();
return [
$pairs[0]['first_name']." ".$pairs[0]['last_name'],
$pairs[1]['first_name']." ".$pairs[1]['last_name']
];
}
}
Can be done better but for your case this should work.

Related

laravel many to many with chaining where clause

I have a given table :
tools toolparts parts part_details
----- --------- ----- ------------
id* id* id* id*
name tool_id name part_id
part_id total (int)
----- --------- ----- ------------
the relation between Tools and Parts is ManyToMany. and the relation between parts and part_details is one to many.
with Laravel model, how can I get tool with part that has the biggest part_details.total ??
//tool model
public function parts()
{
return $this->belongsToMany('App\Part', 'tool_part');
}
//part model
public function tools()
{
return $this->belongsToMany('App\Tool', 'tool_part')
}
public function details(){
return $this->hasMany('App\Part_detail');
}
//partDetail model
public function part(){
return $this->belongsTo('App\Part');
}
Controller
public function index()
{
$tools = Tool::with('parts', 'parts.details')->has('parts')->get();
return $tools;
}
what I expected is something like :
Controller
public function index()
{
$tool = Tool::with('SinglePartThatHasHigestTotalInPartDetail');
}
Any Idea ??
You can use Laravel aggregates for querying to get the desired result,
In your code use max() function,
public function index()
{
$tool = Tool::with(['parts.part_details' => function ($query) {
$max = $query->max('total');
$query->where('total',$max);
})->first();
}
I haven't tested this but you can do like this.
Comment if you will get any errors.
I Manage my problem with "hacky" ways. if someone have a better and more elegant solution, please tell me.
//tool model
public function partWithHighestTotalDelivery($trans_date = null){
if (is_null($trans_date)) {
$trans_date = date('Y-m-d');
}
$parts = $this->parts;
$highest_total_delivery = 0;
foreach ($parts as $key => $part) {
$part->detail;
$total_delivery = $part->first_value;
if (isset( $part->detail->total_delivery )) {
$total_delivery += $part->detail->total_delivery;
}
if ($highest_total_delivery < $total_delivery ) {
$highest_total_delivery = $total_delivery;
$part->total_delivery = $highest_total_delivery;
$result = $part;
}
}
if (!isset($result)) {
$result = null;
}
$this->part = $result;
}
In controller i have :
public function index(Request $request){
$tools = Tool::has('parts')
->get();
$tools->each(function($tool){
$tool->partWithHighestTotalDelivery();
});
return $tools;
}
with this, I need to run tool->partWithHighestTotalDelivery() tools.count times. which is take noticeable process if the tools is many.
and also, the code I post and the question I ask has a slightly difference.that's for a simplicity sake's
Use the the hasManyThrough Relationship to get the all part details related to tool and then you can check the one by one record and get the highest total of the tool part.
// Tool Model
public function partsdetails()
{
return $this->hasManyThrough('App\PartDetail', 'App\Part','tool_id','part_id');
}
In Your controller
$data = Tool::all();
$array = [];
if(isset($data) && !empty($data)) {
foreach ($data as $key => $value) {
$array[$value->id] = Tool::find($value->id)->partsdetails()->max('total');
}
}
if(is_array($array) && !empty($array)) {
$maxs = array_keys($array, max($array));
print_r($maxs);// This array return the max total of the tool related parts
}
else{
echo "No Data Available";
}
You can start with the part detail and get the tool(s) from there:
$maxPartDetail = Part_detail::orderByDesc('total')->first();
$tools = $maxPartDetail->part->tools;

ManyToOne form creating linked object

Imagine these 2 entities:
Article
title
description
category <- ManyToOne
Category
name
Say we need to manage an article with a form and the category attached.
$builder
->add('name')
->add('description')
->add('category')
This one will allow me to select from existing categories.
Now I'd like to be able to create categories if needed.
Title [ ]
Description
[ ]
[ ]
Category [ ]
The category field would be a free text box.
If the text corresponds to no category, a new one would be created and attached.
I tried with some DataTransformer with no luck
I need a reusable solution to manage that because I'll need to embed it especially in another form as a collection.
How can I do it reusable ?
Suggest that you do an "if" on your category.
When you check your form submission is valid and submitted, get the 'category' data, and if not exists persist it as a new category then query for the article after.
The code might be something like this:
if ($form-isSubmitted() && $form->isValid()){
$catName = $form->get('category')->getData(); // Get category.
// Query if it exists.
$qb = $em->createQueryBuilder();
$qb->select('c')
->from('AppBundle:Category', 'c')
->where('c.name = :catName')
->setParameter('catName', $catName);
$cat_results = $qb->getQuery()->setMaxResults(1)->getOneOrNullResult();
if ($cat_results == null){
$newCat = new Category();
$newCat()->setname($catName);
$em->persist($newCat);
$em->flush();
}
else{
...
\\ Render your form etc...
}
Hopefully you get the idea. The above code is simpler, because then you create the category in the same form.
Here is what I ended up with using a DataTransformer
My ArticleType:
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('description')
->add('category', CategoryType::class)
//...
A new CategoryType class:
class CategoryType extends AbstractType
{
protected $myService;
public function __construct(MyService $myService)
{
$this->myService = $myService;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new CategoryTransformer($this->myService);
$builder->addModelTransformer($transformer);
}
}
Registered as service
app.form.category_type:
class: AppBundle\Form\CategoryType
arguments: ['#app.my.service']
tags:
- { name: form.type }
And finally the transformer:
class CategoryTransformer implements DataTransformerInterface
{
// ...MyService initialization...
public function transform($category)
{
if (null === $category) {
return '';
}
return $category->getName();
}
public function reverseTransform($categoryName)
{
if (!$categoryName) {
return null;
}
$category = $this->myService->getOrCreateCategoryFromName($categoryName);
if (null === $category) {
throw new TransformationFailedException();
}
return $category;
}
}
MyService is responsible to get or create a category with the given name using the entity manager.
Quite some lines of code but once this is done, wherever I'll use my form, it can be dealt in the easy standard way:
$articleForm = $this->createForm(ArticleType::class, $article);
$articleForm->handleRequest($request);
if ($articleForm->isValid()) {
$em->persist($article);
$em->flush();
//...

Adding custom eloquent attribute in Laravel 5

I have a function named siblings which fetches all siblings of a user.
select siblings(id) as `siblings` from users where id = 1
I can access the function in Eloquent as
User::where('id', 1)->first([DB::raw(siblings(id) as `siblings`)]->siblings;
I want to make the siblings available via custom attribute.
I added siblings to $appends array
I also created getSiblingsAttribute method in my User model as
public function getSiblingsAttribute()
{
if (!$this->exists()) {
return [];
}
$siblings = User::where('idd', $this->id)
->first([DB::raw('siblings(id) AS `siblings`')])
->siblings;
return explode(',', $siblings);
}
But this is not working as $this->id returns null
My table schema is users(id, username,...), so clearly id is present.
Is there a way by which I can bind the siblings function while querying db and then returning something like $this->siblings from getSiblingsAttribute. If I can bind siblings(id) as siblings with query select globally as we do for scopes using global scope.
That way my code can be simply
public function getSiblingsAttribute()
{
return $this->siblings;
}
The simplest way is to create a view in your database and use that as a table:
protected $table = 'user_view';
Otherwise I need more information about your id == null problem.
If you can fix this by your own in the next step it is important that you use an other column name by selecting as in your accessor otherwise you run in an infinite loop.
public function getSiblingsAttribute()
{
if (!$this->exists()) {
return [];
}
$siblings = User::where('id', $this->id)
->first([DB::raw('siblings(id) AS `siblings_value`')])
->siblings_value;
return explode(',', $siblings);
}
EDIT
Sadly there is no simple way to archieve this.
But after a little bit tinkering I have found a (not very nice) solution.
Give it a try.
You have to add the following class and trait to your app.
app/Classes/AdditionalColumnsTrait.php (additional column trait)
namespace App\Classes;
trait AdditionalColumnsTrait {
public function newEloquentBuilder($query) {
$builder = new EloquentBuilder($query);
$builder->additionalColumns = $this->getAdditionalColumns();
return $builder;
}
protected function getAdditionalColumns() {
return [];
}
}
app/Classes/EloquentBuilder.php (extended EloquentBuilder)
namespace App\Classes;
use Illuminate\Database\Eloquent\Builder;
class EloquentBuilder extends Builder {
public $additionalColumns = [];
public function getModels($columns = ['*']) {
$oldColumns = is_null($this->query->columns) ? [] : $this->query->columns;
$withTablePrefix = $this->getModel()->getTable() . '.*';
if (in_array('*', $columns) && !in_array($withTablePrefix, $oldColumns)) {
$this->query->addSelect(array_merge($columns, array_values($this->additionalColumns)));
} elseif (in_array($withTablePrefix, $oldColumns)) {
$this->query->addSelect(array_values($this->additionalColumns));
} else {
foreach ($this->additionalColumns as $name => $additionalColumn) {
if (!is_string($name)) {
$name = $additionalColumn;
}
if (in_array($name, $columns)) {
if (($key = array_search($name, $columns)) !== false) {
unset($columns[$key]);
}
$this->query->addSelect($additionalColumn);
}
}
if (is_null($oldColumns)) {
$this->query->addSelect($columns);
}
}
return parent::getModels($columns);
}
}
after that you can edit your model like this:
class User extends Model {
...
use App\Classes\AdditionalColumnsTrait;
protected function getAdditionalColumns() {
return [
'siblings' => DB::raw(siblings(id) as siblings)),
];
}
...
}
now your siblings column will be selected by default.
Also you have the option to select only specific columns.
If you don't want to select the additional columns you can use: User::find(['users.*']).
Perhaps it is a solution for you.

Retrieve parent class within morph relationship

I have this code
//ImageableTrait
trait ImageableTrait
{
public function images()
{
return $this->morphMany(Image::class, 'imageable')
->orderBy('order', 'ASC');
}
}
//User
class User extend Model
{
use ImageableTrait;
}
//Post
class Post extend Model
{
use ImageableTrait;
}
class ImageCollection extends Collection
{
public function firstOrDefault()
{
if ($this->count() === 0) {
$image = new Image();
$image->id = 'default';
$image->imageable_type = '/* I need the parent className here */';
$image->imageable_id = '.';
}
return $this->first();
}
}
//Image
class Image extend Model
{
public function imageable
{
return $this->morphTo();
}
public function newCollection(array $models = [])
{
return new ImageCollection($models);
}
public function path($size)
{
//Here, there is some logic to build the image path and it needs
//the imageable_type attribute no matter if there is
//an image record in the database or not
return;
}
}
I want to be able to do so
$path = User::find($id)->images->firstOrDefault()->path('large');
But I can't figure out how to get the parent class name to build the path properly...
I tried with $morphClass or getMorphClass() but can't figure out how to use it properly or if it is even the right way to do it.
Any thoughts on that?
I think you can keep it simple and drop the ImageCollection class because there is already a firstOrNew method that seems to be what you're looking for.
The firstOrNew method accepts an array of attributes that you want to match. If you don't care about the attributes, you can pass an empty array. If there are no matches in the database, it'll make a new instance with the proper parent type.
$path = User::find($id)->images()->firstOrNew([])->path('large');
Note: I am calling the images() method to get the MorphMany object so that I can call the firstOrNew method. In other words, you need to add the parentheses. Otherwise, you get a Collection.
Edit: If you want to make things a bit simpler by automatically setting some default attributes, you can add this to your ImageableTrait:
public function imagesOrDefault()
{
$defaultAttributes = ['id' => 'default'];
return $this->images()->firstOrNew($defaultAttributes);
}
Then, you can do something like this: $path = User::find($id)->imagesOrDefault()->path('large');
Note that your default attributes must be fillable for this to work. Also, imageable_id and imageable_type will automatically be set to your parent's id and type.
If you want to set the default value for imageable_id to a period and not the parent's id, then you have to alter it a bit, and it will look a lot like your original code except this will go inside your ImageableTrait.
public function imagesOrDefault()
{
// First only gets one image.
// If you want to get all images, then change it to get.
// But if you do that, change the conditional check to a count.
$image = $this->images()->first();
if (is_null($image))
{
$image = new Image();
$image->id = 'default';
$image->imageable_type = $this->getMorphClass();
$image->imageable_id = '.';
}
return $image;
}
Ok guys I've found something that seems to work pretty good for now so I'll stick with that.
In the Image model, I've added some code when I make the new collection:
public function newCollection(array $models = [])
{
$morphClass = '';
$parent = debug_backtrace(false, 2)[1];
if (isset($parent['function']) AND $parent['function'] === 'initRelation') {
if (isset($parent['args'][0][0])) {
$morphClass = get_class($parent['args'][0][0]);
}
}
return new ImageCollection($models, $morphClass);
}
I then simply retrieve the morphClass through the constructor of the ImageCollection
private $morphClass;
public function __construct($items = [], $morphClass)
{
parent::__construct($items);
$this->morphClass = $morphClass;
}
public function firstOrDefault()
{
if ($this->count() === 0) {
$image = new Image();
$image->id = 'default';
$image->imageable_type = $this->morphClass;
$image->imageable_id = '.';
}
return $this->first();
}
This way, I can simply call the method like that
User::with('images')->get()->images->firstOrDefault()
This seems to work really great in many cases, if I have some issues at some times, I'll update this post.
i may be late for the party, but i kinda did a small trick for morph relationships where i had "media" as morph, i get the parent since "model_type" has the full string parent class string.
$model = new $media->model_type;
$media->model = $model->findOrFail($media->model_id);

Controller inserting only last data in array into database

Have asked this question before but it wasn't answered.So am asking again.I have a table subjects with columns:
id->primary key,auto-increment;
class_id->foreign key->refrences id on classes table;
subject;
and a users table:
id->primary key,auto-increment;
username;
password;
and a classes table:
id->primary,autoincrement;
class;
term;
and a pivot table student_user:
id->primary;
user_id->refrences id on users table;
subject_id->refrences id on subject table;
class_id->foreign,refrences id on classes table;
And i have my model to define there relationship thus
//User Model
//User.php
public function subjects()
{
return $this->belongsToMany('Subject');
}
public function myclass()
{
return $this->belongsTo('SchoolClass');
}
//Class Model
//SchoolClass.php
public function subjects()
{
return $this->hasMany('Subject','class_id');
}
//Subject Model
//Subject.php
public function term()
{
return $this->belongsTo('SchoolClass');
}
public function student()
{
return $this->belongsToMany('User');
}
Now here comes the problem.When ever i try to insert data into the pivot table subject_user,only the the last data in the array of values that i want to insert gets saved into the database.I dont know if its the foreign constraint keys that's only updating the row instead of inserting a new one when looping through data.I cant figure out what is wrong.And below is my controller to see how my data is been inserted.Am not very conversant with relationships in laravel
//Save Registered Courses
public function post_proceed_registration()
{
$PageTitle = 'Register Subjects';
$id = Auth::user()->id;
$User = User::find($id);
$class_id = $User->profile->class_id;
$subjects = Input::get('subjects');
$registration = RegistrationCheck::where('class_id',$class_id)->where('user_id',$id)->pluck('status');
if(count(Input::get('students')) != 0)
{
if($registration == 'true')
{
PivotSubject::where('user_id',$id)->where('class_id',$class_id)->delete();
foreach($subjects as $sub_id)
{
$subject = new PivotSubject;
$subject->user_id = $id;
$subject->subject_id = $sub_id;
$subject->class_id = $class_id;
$subject->save();
}
RegistrationCheck::where('class_id',$class_id)->where('user_id',$id)->update(['status'=>'false']);
return Response::Json('Success: Subject/Courses Registered');
}
return Response::Json('Error: You already registered for this Term/Semester.Please Contact ICT for help');
}
return Response::Json('Error:You did not choose any course to register');
}
Am stucked here.Any help would be appriciated

Resources