I want make array for eloquent relationship laravel - laravel

in Course model this relation are include
public function course_modules()
{
return $this->hasMany(CourseModule::class, 'course_id');
}
public function course_lessons()
{
return $this->hasMany(CourseLesson::class, 'course_id');
}
public function course_contents()
{
return $this->hasMany(CourseContent::class, 'course_id');
}
i want to make a array for hasMany relation like
$hasMany=[
CourseModule::class,
CourseLesson::class
]

I wanted to do this for fun, turned out pretty difficult, but here you go, there are some requirements you need to make sure of, but it gets the job done, I will be using a mix of PHP & Laravel to accomplish this.
Step 1: Make sure your main class has proper return method types. So in your case.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Course extends Model
{
public function course_modules() : HasMany
{
return $this->hasMany(CourseModule::class, 'course_id');
}
public function course_lessons() : HasMany
{
return $this->hasMany(CourseLesson::class, 'course_id');
}
public function course_contents() : HasMany
{
return $this->hasMany(CourseContent::class, 'course_id');
}
}
Step 2: In your controller, you need to use ReflectionClass, would love if someone actually can improve this for learning purposes.
<?php
namespace App\Http\Controllers;
use ReflectionClass;
class CourseController extends Controller
{
public function test(){
//We will build a hasMany array
$hasMany = [];
//here we will use ReflectionClass on our primary class that we want to use.
$reflection = new ReflectionClass(new \App\Models\Course);
//Lets loop thru the methods available (300+ i don't like this part)
foreach($reflection->getMethods() as $method){
//if the method return type is HasMany
if($method->getReturnType() != null && $method->getReturnType()->getName() == 'Illuminate\Database\Eloquent\Relations\HasMany'){
//we grab the method name
$methodName = $method->getName();
//then we finally check for the relatedClass name and add to the array
array_push($hasMany, get_class(($instance = new Course)->$methodName()->getRelated()));
}
}
//lets dump to see the results
dd($hasMany);
}
Results: an array of the classes :D
array:2 [▼
0 => "App\Models\ProgramTest",
1 => "App\Models\ProgramAnotherTest"
]

According to syntax, we are not able do this in Laravel. However, you can use an model mutors to solve this issue.
public function getCourseDetailsAttribute(){
$arr=[
"course_modules"=>$this->course_modules(),
"course_lessions"=>$this->course_lessons(),
];
return $arr;
}
In the Controller you can write like this,
$course=Course::find(1)->course_details;
For more details t;
https://laravel.com/docs/5.7/eloquent-mutators

Related

How to disable loading of relationships when not needed in Laravel

is it possible to disable the loading of relationships, but only in some cases?
Here are my models:
class League extends Model
{
...
public function country()
{
return $this->belongsTo(Country::class)->with('translations');
}
}
class Country extends Model
{
...
public function translations()
{
return $this->hasMany(CountryTranslation::class, 'country_id');
}
}
class CountryTranslation extends Model
{
...
}
In many places, I need to load the translations relationship for Country, but on some pages, I want to display information about the League and its Country only. There I don't want to show the CountryTranslation collection.
Here is the code for that page:
$country = $league->country;
Is it possible only for this line to disable the relations?
So, you're currently finding out one of the reasons for not defining the eager loading inside of the relationship. The first suggestion would be to remove the with() from the relationship definition, and add it in where needed. If desired, you can create another relationship that has the eager loading enabled, and it can use the base relationship to keep it DRY:
public function country()
{
return $this->belongsTo(Country::class);
}
public function countryWithTranslations()
{
return $this->country()->with('translations');
}
If this code change is not feasible, you will need to change how you're accessing the country relationship. When you access the relationship attribute, it lazy loads the relationship, and you don't have the ability to modify the relationship query. So, instead of accessing the relationship attribute, you'd need to call the relationship query so you can modify it.
Therefore, you won't be able to do $country = $league->country;, but you can do:
$country = $league->country()->without('translations')->first();
he with() simply eager loads the translations to avoid additional queries, but you should be able to load the translations with and without it, without with( adds additional queries. https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
You will want to change:
public function country()
{
return $this->belongsTo(Country::class)->with('translations');
}
to
public function country()
{
return $this->belongsTo(Country::class);
}
If you want to load translations, you can do it in the controllers
// if you want translations at some point do this:
$league = League::with('country.translations')
$country = $league->country->translations
// if you do not want translations
$league = League::with('country')
$country = $league->country;
If you do not want to touch:
public function country()
{
return $this->belongsTo(Country::class)->with('translations');
}
you can create another method
public function countryClean()
{
return $this->belongsTo(Country::class);
}
$country = $league->countryClean;

Laravel: immutable Trait

I need that when a document is considered closed, you won't be able to modify, update or delete it anymore.
I was thinking to use a trait like an "ImmutableTrait".
I've done this:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
trait ImmutableTrait
{
protected $isImmutable = false;
public function setAttribute($key, $value)
{
if ($this->isImmutable) {
return $this;
}
return parent::setAttribute($key, $value);
}
}
Then in my model:
use Illuminate\Database\Eloquent\Model;
use App\Traits\ImmutableTrait;
class MedicalRecord extends Model
{
use ImmutableTrait;
public function closeDocument()
{
$this->isImmutable = true;
}
}
Finally the controller:
public function closeDocument(Document $document)
{
.....
$document->closeDocument();
$document->saveorfail();
}
Then, if I try to retrieve the closed model and update a field, I shouldn't be able to do it:
Route::put('{document}/updateStatus', 'DocumentController#updateStatus');
class DocumentController extends Controller
{
....
public function updateStatus(Document $document)
{
$document->status= "TEST";
$document->saveorfail();
}
}
Calling the API with the id of a closed document, should fail the update, but this is not happening. The field is updated normally.
Obviously I'm missing something. But what?
Thank you all!
Just for reference if anyone needs this.
I ended up creating the following trait:
<?php
namespace App\Traits;
use App\Exceptions\ImmutableModelException;
trait ImmutableModelTrait {
public function __set($key, $value)
{
if ($this->isClosed)
{
throw new ImmutableModelException();
}
else {
//do what Laravel normally does
$this->setAttribute($key, $value);
}
}
}
The problem with my first solution, as #mrhn stated in one comment, was that I was searching for the "isImmutable" variable on a new model instance but I wasn't persisting that variable in the DB table.
So now my "Document" table has a field "isClosed" that becomes true when the document is considered closed.

Laravel 5.7 getting all the siblings for each child

I wonder how to get all the sibling that a child have in laravel model ?
I know i can use this $siblings = \App\Child::all()->where('parent_id', $parent_id)->where('id', $id); to get all the child siblings, but I want to know if I can do the other way or more cleanest way?
So you can call this in the blade view $child->siblings->full_name something like that.
But I wonder how to use it something like this in the model.php
public function parent()
{
return $this->belongsTo(User::class);
}
with only using the belongsTo or hasMany function maybe if it's possible?
Sorry I'm not good with english so I don't know what it's called so I can search it on google.
Edit:: Adding Child.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
class Child extends Model
{
protected $guarded = [];
protected $dates = [
'birthdate',
'father_birthdate',
'mother_birthdate',
'guardian_birthdate',
];
public function parent()
{
return $this->belongsTo(User::class);
}
// I want to call this function only using $child->siblings (this one will show all the siblings)
// without passing the parent id
// to make it cleaner
public function siblings($parent_id)
{
return $this->all()->where('parent_id', $parent_id);
}
public function getAgeAttribute()
{
return Carbon::parse($this->attributes['birthdate'])->age;
}
public function getFullNameAttribute()
{
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
}
There are 2 ways you could do this.
1 creating the below method:
public function sibling()
{
return $this->belongsTo(User::class)->where('parent_id', $this->parent_id);
}
2 using a single Query:
$siblings = \App\Child::where('parent_id', $parent_id)->get();
Hope this helps
Update
A third way is:
public function scopeSiblings($query, $parent_id) {
return $query->where('parent_id', $parent_id)->get();
}

Laravel 5 Global Mutator to escape all html chars?

Building an API but because I am dynamically creating tables etc in Vue.js from the API response I can't make use of blades html escaping.
I know in my model I can use a mutator:
public function getNameAttribute($value) {
return strtolower($value); // example
}
But we have a lot fields that can be edited across many models. Is there a way I can automatically return all values with htmlspecialchars()?
Or is the only option to change the API responses to run htmlspecialchars() on every field?
Thanks.
EDIT: Using Laravel Spark. Suggested answer was to create a new model and extend that on our models but the Spark models already have a long list of extended classes.
You can create a class which extends Model class and make all you models extend this class instead of Model. In the class override getAttributeValue method:
protected function getAttributeValue($key)
{
$value = $this->getAttributeFromArray($key);
if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
}
if ($this->hasCast($key)) {
return $this->castAttribute($key, $value);
}
if (in_array($key, $this->getDates()) && ! is_null($value)) {
return $this->asDateTime($value);
}
return is_string($value) ? htmlspecialchars($value) : $value;
}
Apart from extending classes. Alternate solution is to use traits.
Create a new trait
namespace App\Traits;
trait ExtendedModel {
public function getNameAttribute($value)
{
return strtolower($value); // example
}
}
Use traits in required models:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\ExtendedModel;
class MyTable extends Model
{
use ExtendedModel;
}
This is how I would do this.
I would make a trait and then attach this trait to all models where you want it.
The trait would overrider getAttributeValue($key) method with your own logic. Like this:
trait CastAttributes {
public function getAttributeValue($value)
{
$value = $this->getAttributeValue($value);
return is_string($value) ? strtolower($this->getAttributeValue($value)) : $val;
}
}
That beign said, you will most likely not want to cast absolutely everything. In that case I would do this:
trait CastAttributes {
protected $toLower = [];
public function getAttributeValue($key)
{
$value = $this->getAttributeValue($key);
return in_array($key, $this->toLower) ? strtolower($value) : $value;
}
}
And then override the $toLower array with all the attributes that you actually want cast to lower case.

Laravel 4 - Model properties' names different than database columns

I have one question, that seems to be logical, but I can't find answer for it.
Let's say I have Model Task:
class Task extends Eloquent {
protected $fillable = array('is_done');
}
So, I have one property is_done, but when working on frontend and backend part of application, I would like to have isDone as model property.
Is there a way to say it to framework, to somehow repack it for me? So that I am able to use isDone, throughout application, and that Model takes care of converting it to is_done, when it comes to saving/updating.
This would help me, so I don't have to think about names specified in database (like when using alias in traditional SQL clauses).
Is this possible at all? Does it make sense?
To prevent writing a getter/setter methods for every single attribute of the model, you can override the magic methods from the Eloquent class to access them in camelCase style:
class Model extends Eloquent {
public function __get($key)
{
$snake_key = snake_case($key);
return parent::__get($snake_key);
}
public function __set($key, $value)
{
$snake_key = snake_case($key);
parent::__set($snake_key, $value);
}
public function __isset($key)
{
$snake_key = snake_case($key);
return parent::__isset($snake_key);
}
public function __unset($key)
{
$snake_key = snake_case($key);
parent::__unset($snake_key);
}
}
Would a getter method for your attribute help you? If yes:
<?php
class Task extends Eloquent {
public function isDone()
{
return $this->getAttribute('is_done');
}
}
If not, and you really need to access $Task->isDone: try to overwrite the $key in magic _get() method for $key == 'isDone' (and maybe other attributes) and return the parent::_get() with $key:
<?php
class Task extends Eloquent {
public function __get($key)
{
if($key == 'isDone')
$key = 'is_done';
return parent::__get($key);
}
}
And perhaps, your Eloquent needs an attribute mapper for the attribute magic methods ;)

Resources