Refactor my Code implementing Repositories in Laravel 5 - php

I'm refactoring all my DB Access related to my User Model in my Laravel app implementing Repositories.
So, now, all my DB Access ( I use Eloquent ) go through my UserRepository.
But I still have some eloquent operations in my model User:
class User extends Model{
....
public static function boot()
{
parent::boot();
static::creating(function ($user) {
$softDeletedUser = User::onlyTrashed()->where('email', '=', $user->email)->first();
if ($softDeletedUser != null) {
$softDeletedUser->restore();
return false;
} else {
$user->token = str_random(30);
if ($user->country_id == 0) {
$user->addGeoData();
}
}
return true;
});
I don't know what to do with that because:
It's about my User Model, not my Controller
It is a static method, so I don't know how to pass $this with (use)
I guess there is something wrong with that....
So... What should I do in this situation?

I don't think that you really need to move that query into a repository but it's up to you to decide. If you really need to reuse that code, add the query into a repository class and use it. Using the same query on 2 places isn't really a big deal but it might become a problem if you have more than 2 places.

Related

Always eager load relation when using a trait in eloquent model

I'm providing a HasTranslation-Trait for any of my eloquent models. All models using this trait will receive a one-to-many-relation like this (where you can see my basic Model to ModelLanguages relations):
public function languages()
{
return $this->hasMany(get_class($this).'Lang', 'master_id', 'id');
}
What I want to do is:
Always eager load a "hasOne"-relationship with the translation of current user's language. So whenever the user is logged in, my models should have something like $model->userLanguage being eager loaded and is of type ModelLang.
This looks like this and works great:
public function userLanguage()
{
$user = \Auth::user();
if (!$user)
{
throw new \Exception(__CLASS__.': userLanguage not available because there is no session');
}
return $this->hasOne(get_class($this).'Lang', 'master_id', 'id')->where('language_id', $user->language_id);
}
I'm struggling with the possibility to automatically (eager) load this relations for all models by just including this trait.
What I've tried so far
Use a constructor within the trait: Can work, but no good idea, because this can collide with other trait's contructor. Basically I'd confirm the statement: Do not use the constructor in any trait.
Use your boot-Trait method (bootHasTranslation) but there I do not have the concrete object to call load or with method on. I did not find any hook into the instantiated eloquent model where I want to add my relation to eager load
Any ideas? Is there something obvious I've overlooked here?
You can create a middleware for the logged in user language.
public function handle($request, Closure $next)
{
if (!Auth::check()) {
return redirect('/')->with('Success','You have successfully logged out');
}
$user = \Auth::user();
$language = $user->languages()->where('language_id', $user->language_id);
$request->attributes->add([
'user_language' => $language
]);
$next($request);
}
You can get this data after middleware like
$request->attributes->get('user_language');

Laravel: One Controller for multiple Models

I'm currently rebuilding my vanilla-PHP-App with Laravel and I have the following problem.
I have multiple database-tables, that represent word categories (noun, verb, adverb, ...). For each table I created a separate Model, a route::resource and a separate resource-Controller. For example:
NomenController.php
public function show($id)
{
$vocab = Nomen::find($id);
return view('glossarium.vocab_update', compact('vocab'));
}
and
VerbController.php
public function show($id)
{
$vocab = Verb::find($id);
return view('glossarium.vocab_update', compact('vocab'));
}
...which are essentially the same except the Model class.
I don't want to create a separate Controller for each model, that does exactly the same. What would be the most simple and elegant way to solve this?
Should I just create a VocabController.php and add a parameter for the Model-name like:
Route::resource('/vocab/{category}', 'VocabController');
and then add a constructor method in this controller like
public function __construct ($category) {
if ($category == 'nomen') {
$this->vocab = App\Nomen;
}
else if ($category == 'verb') {
$this->vocab = App\Verb;
}
}
I wonder if there is a simpler method to do that. Can I somehow do this with Route Model Binding?
Thanks in advance
Simply create a trait like this in App\Traits, (you can name it anything... Don't go with mine though... I feel its pretty lame... :P)
namespace App\Traits;
trait CommonControllerFunctions {
public function show($id) {
$modelObject = $this->model;
$model = $modelObject::find($id);
return view('glossarium.vocab_update', compact('model'));
}
}
and in your NomenController and VerbController, do this:
use App\Traits\CommonControllerFunctions;
class NomenController {
use CommonControllerFunctions;
protected $model = Nomen::class;
}
and
use App\Traits\CommonControllerFunctions;
class VerbController {
use CommonControllerFunctions;
protected $model = Verb::class;
}
Note: Please note that this example is just a work-around for your particular situation only... Everyone practices code differently, so this method might not be approved by all...
I think the simpliest way it to create only one controller, eg VocabController with methods nomen, verb and whatever you want.
Routes:
Route::get('/vocab/nomen/{nomen}', 'VocabController#item');
Route::get('/vocab/verb/{verb}', 'VocabController#item');
And the model binding:
Route::model('nomen', 'App\Nomen');
Route::model('verb', 'App\Varb');
Then your method shoud look like that:
public function item($item)
{
return view('glossarium.vocab_update', $item);
}
Keep in mind, that $item is already fetched model from the database.

Where do we have to write queries in Laravel: in model, controller or routes?

I'm little confused with the database queries in the laravel, where do we have to write our queries: in controllers, models or routes ?
I've been through many tutorials and I see so much difference. Just creating confusion.
Kindly explain somebody
It depends on different factors, but in short you can write them both in Model, Controllers or Repositories
If you're writing a controller action and you need a query that you'll use only once, it's perfectly fine to write the query directly in the controller (or even in the route's closure ).
For example, if you want to get all users of type admin:
$admins = User::where('type', 'admin')->get();
Now, suppose you need to get the admins in more than one controller method; instead of rewriting the same query, you can create a Repository class to wrap the access to the users' Model and write the query inside the Repository:
class UserRepository
{
public function getAllAdmins()
{
return User::where('type', 'admin')->get();
}
}
Now in your controllers you can inject the Repository and use the same method of the Repository to get the admin users: this will keep your code DRY as you don't have to repeat the same query among the controllers' actions
Controller
public function __construct(UserRepository $userRepo)
{
$this->userRepo = $userRepo;
}
//controller action
public function index()
{
$admins = $this->userRepo->getAllAdmins();
}
Finally, let's suppose you need a query to count the number of the admin users. You could write this query in the UserRepository:
public function getAdminNum()
{
return User::where('type', 'admin')->count();
}
And it would be ok, but we can note that the User::where('type', 'admin') fragment of the query is shared with the query in getAllAdmins So we can improve this by using query scopes :
User Model
public function scopeAdmins($query)
{
return $query->where('type', 'admin');
}
by this, in the UserRepository methods we can rewrite our previous queries as:
public function getAllAdmins()
{
return User::admins()->get();
}
public function getAdminNum()
{
return User::admins()->count();
}
And i've just showed you a case in which a query would be writed inside a Model
You do not write any query in Model. Model is just for mapping the class that you are going to use for a table like User Model will be mapped to users (plural of model name).
You do not write queries in the routes closures like this
Route::get('/', ['as' => 'home', function(){
$totalProblems = Problem::count();
$solvedProblems = Problem::where('solved', 1)->get()->count();
$unsolvedProblems = Problem::where('solved', 0)->get()->count();
return view('Pages.index', ['totalProblems' => $totalProblems, 'solvedProblems' => $solvedProblems, 'unsolvedProblems' => $unsolvedProblems]);
}]);
This is considered as bad practice, its just for testing purposes.
You always write your queries in the controller method associated with your route like this
Route::get('test', 'HomeController#test');
and in your HomeController
<?php namespace App\Http\Controllers;
use App\Problem;
class HomeController extends Controller {
public function test(){
$user = User::all();
return view('Pages.test')->withUser($user); //or
return view('Pages.test')->with('user' , $user);
}
}

Load all relationships for a model

Usually to eager load a relationship I would do something like this:
Model::with('foo', 'bar', 'baz')...
A solution might be to set $with = ['foo','bar','baz'] however that will always load these three relations whenever I call Model
Is it possible to do something like this: Model::with('*')?
No it's not, at least not without some additional work, because your model doesn't know which relations it supports until they are actually loaded.
I had this problem in one of my own Laravel packages. There is no way to get a list of the relations of a model with Laravel. It's pretty obvious though if you look at how they are defined. Simple functions which return a Relation object. You can't even get the return type of a function with php's reflection classes, so there is no way to distinguish between a relation function and any other function.
What you can do to make it easier is defining a function that adds all the relationships.
To do this you can use eloquents query scopes (Thanks to Jarek Tkaczyk for mentioning it in the comments).
public function scopeWithAll($query)
{
$query->with('foo', 'bar', 'baz');
}
Using scopes instead of static functions allows you to not only use your function directly on the model but for example also when chaining query builder methods like where in any order:
Model::where('something', 'Lorem ipsum dolor')->withAll()->where('somethingelse', '>', 10)->get();
Alternatives to get supported relations
Although Laravel does not support something like that out of the box you can allways add it yourself.
Annotations
I used annotations to determine if a function is a relation or not in my package mentioned above. Annotations are not officially part of php but a lot of people use doc blocks to simulate them.
Laravel 5 is going to use annotations in its route definitions too so I figuered it not to be bad practice in this case. The advantage is, that you don't need to maintain a seperate list of supported relations.
Add an annotation to each of your relations:
/**
* #Relation
*/
public function foo()
{
return $this->belongsTo('Foo');
}
And write a function that parses the doc blocks of all methods in the model and returns the name. You can do this in a model or in a parent class:
public static function getSupportedRelations()
{
$relations = [];
$reflextionClass = new ReflectionClass(get_called_class());
foreach($reflextionClass->getMethods() as $method)
{
$doc = $method->getDocComment();
if($doc && strpos($doc, '#Relation') !== false)
{
$relations[] = $method->getName();
}
}
return $relations;
}
And then just use them in your withAll function:
public function scopeWithAll($query)
{
$query->with($this->getSupportedRelations());
}
Some like annotations in php and some don't. I like it for this simple use case.
Array of supported relations
You can also maintain an array of all the supported relations. This however needs you to always sync it with the available relations which, especially if there are multiple developers involved, is not allways that easy.
protected $supportedRelations = ['foo','bar', 'baz'];
And then just use them in your withAll function:
public function scopeWithAll($query)
{
return $query->with($this->supportedRelations);
}
You can of course also override with like lukasgeiter mentioned in his answer. This seems cleaner than using withAll. If you use annotations or a config array however is a matter of opinion.
There's no way to know what all the relations are without specifying them yourself. How the other answers posted are good, but I wanted to add a few things.
Base Model
I kind of have the feeling that you want to do this in multiple models, so at first I'd create a BaseModel if you haven't already.
class BaseModel extends Eloquent {
public $allRelations = array();
}
"Config" array
Instead of hard coding the relationships into a method I suggest you use a member variable. As you can see above I already added $allRelations. Be aware that you can't name it $relations since Laravel already uses that internally.
Override with()
Since you wanted with(*) you can do that too. Add this to the BaseModel
public static function with($relations){
$instance = new static;
if($relations == '*'){
$relations = $instance->allRelations;
}
else if(is_string($relations)){
$relations = func_get_args();
}
return $instance->newQuery()->with($relations);
}
(By the way, some parts of this function come from the original Model class)
Usage
class MyModel extends BaseModel {
public $allRelations = array('foo', 'bar');
}
MyModel::with('*')->get();
I wouldn't use static methods like suggested since... it's Eloquent ;)
Just leverage what it already offers - a scope.
Of course it won't do it for you (the main question), however this is definitely the way to go:
// SomeModel
public function scopeWithAll($query)
{
$query->with([ ... all relations here ... ]);
// or store them in protected variable - whatever you prefer
// the latter would be the way if you want to have the method
// in your BaseModel. Then simply define it as [] there and use:
// $query->with($this->allRelations);
}
This way you're free to use this as you like:
// static-like
SomeModel::withAll()->get();
// dynamically on the eloquent Builder
SomeModel::query()->withAll()->get();
SomeModel::where('something', 'some value')->withAll()->get();
Also, in fact you can let Eloquent do it for you, just like Doctrine does - using doctrine/annotations and DocBlocks. You could do something like this:
// SomeModel
/**
* #Eloquent\Relation
*/
public function someRelation()
{
return $this->hasMany(..);
}
It's a bit too long story to include it here, so learn how it works: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html
Since i've met with a similar problem, and found a good solution that isn't described here and doesn't require filling some custom arrays or whatever, i'll post it for the future.
What i do, is first create a trait, called RelationsManager:
trait RelationsManager
{
protected static $relationsList = [];
protected static $relationsInitialized = false;
protected static $relationClasses = [
HasOne::class,
HasMany::class,
BelongsTo::class,
BelongsToMany::class
];
public static function getAllRelations($type = null) : array
{
if (!self::$relationsInitialized) {
self::initAllRelations();
}
return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
}
protected static function initAllRelations()
{
self::$relationsInitialized = true;
$reflect = new ReflectionClass(static::class);
foreach($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
/** #var ReflectionMethod $method */
if ($method->hasReturnType() && in_array((string)$method->getReturnType(), self::$relationClasses)) {
self::$relationsList[(string)$method->getReturnType()][] = $method->getName();
}
}
}
public static function withAll() : Builder
{
$relations = array_flatten(static::getAllRelations());
return $relations ? self::with($relations) : self::query();
}
}
Now you can use it with any class, like -
class Project extends Model
{
use RelationsManager;
//... some relations
}
and then when you need to fetch them from the database:
$projects = Project::withAll()->get();
Some notes - my example relation classes list doesn't include morph relations, so if you want to get them as well - you need to add them to $relationClasses variable. Also, this solution only works with PHP 7.
You could attempt to detect the methods specific to your model using reflection, such as:
$base_methods = get_class_methods('Illuminate\Database\Eloquent\Model');
$model_methods = get_class_methods(get_class($entry));
$maybe_relations = array_diff($model_methods, $base_methods);
dd($maybe_relations);
Then attempt to load each in a well-controlled try/catch. The Model class of Laravel has a load and a loadMissing methods for eager loading.
See the api reference.
You can create method in your Model
public static function withAllRelations() {
return static::with('foo', 'bar', 'baz');
}
And call Model::withAllRelations()
Or
$instance->withAllRelations()->first(); // or ->get()
You can't have a dynamic loading of relationships for a certain model. you need to tell the model which relations to support.
composer require adideas/laravel-get-relationship-eloquent-model
https://packagist.org/packages/adideas/laravel-get-relationship-eloquent-model
Laravel get relationship all eloquent models!
You don't need to know the names of the methods in the model to do this. Having one or many Eloquent models, thanks to this package, you can get all of its relationships and their type at runtime
The Best Solution
first create a trait, called RelationsManager:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use ReflectionClass;
use ReflectionMethod;
trait RelationsManager
{
protected static $relationsList = [];
protected static $relationsInitialized = false;
protected static $relationClasses = [
HasOne::class,
HasMany::class,
BelongsTo::class,
BelongsToMany::class,
HasOneThrough::class,
HasManyThrough::class,
MorphTo::class,
MorphOne::class,
MorphMany::class,
MorphToMany::class,
];
public static function getAllRelations($type = null): array
{
if (!self::$relationsInitialized) {
self::initAllRelations();
}
return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
}
protected static function initAllRelations()
{
self::$relationsInitialized = true;
$reflect = new ReflectionClass(static::class);
foreach ($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
/** #var ReflectionMethod $method */
if ($method->hasReturnType() && in_array((string) $method->getReturnType(), self::$relationClasses)) {
self::$relationsList[(string) $method->getReturnType()][] = $method->getName();
}
}
}
public static function withAll(): Builder
{
$relations = array_flatten(static::getAllRelations());
return $relations ? self::with($relations) : self::query();
}
}
Now you can use it with any class, like -
class Company extends Model
{
use RelationsManager;
//... some relations
}
and then when you need to fetch them from the database:
$companies = Company::withAll()->get();
this solution only works with PHP 7 Or Higher.
Done

Laravel get data from pivot table

I have a pivot table of users_operators.
I want to grab the operator_id of the user.
This is how i do this now, but its seems like verbose way.
if (Auth::user()->type === 'operator') {
$user = Auth::user();
// There is a better way to do this?
$operator_id = $user->operator[0]['pivot']['operator_id'];
Session::put('operatorId', $operator_id);
}
class Operator extends \Eloquent
{
public function users() {
return $this->belongsToMany('User');
}
}
class User extends \Eloquent
{
public function operator() {
return $this->belongsToMany('Operator');
}
}
I'm, battling insomnia and not functioning at 100%, but you should be able to get away with $user->operator->id based on what I'm interpreting your models to be (it looks like you had a typo when you copied them into the question).
If that doesn't work, you might want to check out the "Dynamic Properties" section in the Eloquent docs for more info, if you haven't already.

Categories