Laravel 4 - Model properties' names different than database columns - laravel-4

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 ;)

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

Parent Controller class to call child overridden methods (Laravel)

I know this might seem anti pattern, and a lot will throw stones at me, but please hear me out.
I want to create a generic Controller to support many reference tables (mostly id, label). So I did something like this:
class GenericController extends Controller
{
public function index($modelName)
{
$x = '\\App\\Models\\'.$modelName;
$data = $model->all();
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
And this way my routes in web.php will be reduced to the minimum like this:
//List
Route::get('/{model}', function ($model) {
return App::call('\App\Http\Controllers\GenericController#index', ['modelName' => $model]);
});
It's working very well with simple CRUD actions like store, update, etc.. However I know I am over simplifying the design because sometimes I need to return a field from a joined table in the index list for example. That's where I am heading into a dead end, sort of.
My first thought was to create a controller for each model that inherits from the GenericController like this:
class CategoryController extends GenericController
{
}
And whenever I need to override the GenericController method, I would simply add it to the child class. However how can I do this from inside the GenericController (call a method in a sub class from parent class)? Because otherwise I will have to create routes for every single model which is against my wish.
So basically I am looking for something like this:
class GenericController extends Controller
{
public function index($modelName)
{
$x = '\\App\\Models\\'.$modelName;
//this thing I'm looking for is something like this:
//Check if we have CategoryController and it has a definition for index
//if yes do something like $data = CategoryController->index();
//otherwise just call $data = $model->all();
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
So I know this seems weird and anti-pattern, but other wise how can I create my generic routes and controller actions?
You are right, this is not really what is called "best practice". However, from a POO standpoint, it is an interesting question.
This what you can do:
class GenericController extends Controller
{
protected function getData(string $model)
{
return $model::all();
}
public function index($modelName)
{
$model = '\\App\\Models\\'.$modelName;
$data = $this->getData($model);
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
By default, the data will be retrieved "the simple way", using $data = $this->getData($model);.
However, if you make a CategoryController:
class CategoryController extends GenericController
{
protected function getData(string $model)
{
return Category::query()->with('something')->where('hello','world')->get();
}
}
You will just have to override the getData method inside your CategoryController.
This is the way to go if you want something clean. Of course, your categories routes will have to use this CategoryController instead of the GenericController.

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.

Get primary key name in eloquent model

How to get name of primary key from model in laravel?
like this
dd(User::primaryKeyName()); // -> 'user_id'
I want to sort data on primary if 'order' is empty
$data = User::orderBy(Input::get('order', User::primaryKey()), 'ASC')->get();
but have
Non-static method Illuminate\Database\Eloquent\Model::getKeyName() should not be called statically, assuming $this from incompatible context
That method tries to get the value from a protected class property return $this->primaryKey; and $this needs a context from a class instance. If you really need to get that name dynamically, you could do this:
App::make('User')->getKeyName();
So your code would look something like this:
$data = User::orderBy(Input::get('order', App::make('User')->getKeyName()), 'ASC')->get();
The fastest way to do this is probably like so:
(new User)->getKeyName();
(new User)->getTable();
However, I think the two approaches below provide a cleaner interface to the method.
The first approach uses a static method on your base model class:
class BaseModel extends Model
{
protected static function KeyName() {
return (new static)->getKeyName();
}
protected static function TableName() {
return (new static)->getTable();
}
}
Then inherit from this class in your model:
class ModelName extends BaseModel
{
...
}
And use like this: ModelName::KeyName();
If you don't have access to a base model class you can also use a trait:
trait EloquentGetTableNameTrait
{
public static function TableName()
{
return (new static)->getTable();
}
}
Which you use like this in your model:
class ModelName
{
use EloquentGetTableNameTrait;
...
}
Unfortunately, all these approaches require instantiating an instance of the model, but there is currently no other way to retrieve this value.
Reference: https://github.com/laravel/framework/issues/1436

How to access model attribute from an overrided eloquent save() method? (converting empty input to null)

After using the code provided in the following comment https://stackoverflow.com/a/13518727/3159370 , I would like to access model attributes and change them before the model gets saved.
If you're curious why I want to do that, I think it's the best way to convert empty(varchar) 0(integer) to null before saving it in the database.
EDIT: What am looking for is a generic method to loop through all the attributes.
You should be able to access it using $this:
class Page extends Eloquent {
public function save()
{
$this->sanitize();
parent::save();
}
public function sanitize()
{
foreach($this->getAttributes() as $key => $value)
{
if ( ! $value)
{
$this->{$key} = null;
}
}
}
}
This is untested code, but should work.

Resources