Laravel 5 Global Mutator to escape all html chars? - laravel

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.

Related

I want make array for eloquent relationship 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

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.

How to use laravel accessor in trait?

I would like to put accessor in trait, and for some reason this is not working (I have current applocale in session):
Trait:
namespace App\Traits;
trait TranslateEntities
{
public function getNameAttribute($value)
{
if (session('applocale')=='en')
{
return $value;
} else {
return trans("entities.".$this->code);
}
}
}
Model:
namespace App\Models;
use App\Traits\TranslateEntities;
class Repairstatus extends \Eloquent {
use TranslateEntities;
(...)
}
This way I'm not getting translated entity, but if I put this public function getNameAttribute($value) inside model, it works ok.
Any idea?
Okay, I found a solution here:
Laravel pluck but combining first name + last name for select
Problem is in "pluck" method, not having "code" attribute...

Laravel 4 : Where to put model helper methods?

Where is the best place to put helper methods for Laravel Eloquent models?
At the moment I'm creating a BaseModel that extends Eloquent and putting extra functionality in there. Then all my models extend BaseModel. It works.. but it doesn't feel right.
For Example is my BaseModel at the moment...
class BaseModel extends Eloquent {
protected static function getEnumValues($table, $field)
{
$test=DB::select(DB::raw("show columns from {$table} where field = '{$field}'"));
preg_match('/^enum\((.*)\)$/', $test[0]->Type, $matches);
foreach( explode(',', $matches[1]) as $value )
{
$enum[] = trim( $value, "'" );
}
return $enum;
}
protected static function convertDate($date)
{
if(!isset($date)) return;
$new_date = DateTime::createFromFormat('d/m/Y', $date);
$formated_date = $new_date->format('Y-m-d');
return $formated_date;
}
}
And I use them in my models like...
public static function boot()
{
parent::boot();
static::creating(function($campaign)
{
$campaign->sale_date = static::convertDate($campaign->sale_date);
$campaign->sold_date = static::convertDate($campaign->sold_date);
});
}
Where is the best place to put model helper methods like this?
Feedback appreciated, thanks!
You can write a static helper class then call it from your model.
You can refer to my blog post about how to add custom class. Or you can always utilize composer autoload using psr-0 or psr-4
What I did is...I created a Helper Directory in and inside that I created a GlobalHelper.php File.
app/Helper/GlobalHelper.php
In GlobalHelper.php
namespace App\Helper;
use App\Command;
class GlobalHelper{
public static function getCommands(){
$getCommands = Command::all();
return $getCommands;
}
}
Than in controller or anywhere call like this:
use App\Helper\GlobalHelper;
$getCommands = GlobalHelper::getCommands();

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