I am working over a project. I have required some basic values in different routes. e.g. show the list of users, locations, products in different pages/routes. So I wanted to create a common method for these like where i want to show list of users I will call the method for user and pass this list to view.
I've tried the following method:
$usersArr = User::where('loc_id',session('organization'))
->where('status', '!=', 'Deleted')
->orderBy('id', 'desc')
->lists('username', 'id');
So i wanted to know which is best place to define these type of methods. Should i define this in Model,Controller or create some type of common functions?
Create CommonModel , and define function there ..and cal that model
That is a common question. I think that the most popular approach is to create a Repository class for that (for example with a name UserRepository). You can define a the repo method like this:
class UserRepository{
public static function getOrganizationActiveUsers($organization){
return User::where('loc_id', $organization)
->where('status', '!=', 'Deleted')
->orderBy('id', 'desc')
->lists('username', 'id');
}
}
and then use this method in other classes like:
$variable = UserRepository::getOrganizationActiveUsers(session('organization'));
This way your class would not have to know that some kind of session exists. That is usefull if you cannot access session when accessing specific API for example.
Also you can inject your repository in your controller (or any other class) this way:
<?php
use Namespace/SubnameSpace/UserRepository; // this is very important, to specify the correct full classname with namespace;
class SomeController
{
__construct(UserRepository $userRepo)
{
$this->userRepo = $userRepo;
}
public function showUserOrganizationUsers()
{
$users = $this->userRepo->getOrganizationActiveUsers(session('organization'));
return $users;
}
}
This is also convenient, in case you for example want to inject a completely different class but with the same methods. (if you want to dive deeper in dependency injection read about interfaces and Laravel DI container, which provide a lot of convenience when injecting dependencies. What I have shown above is just a straightforward way).
By the way, you can check some sample codebases with different approaches here:
Awesome Laravel on GitHub
Related
I'd really like some pointers on the best practice to follow when it comes to putting DB and business login into the model and having the controllers call the relevant methods.
I have a model and a controller for 'articles', see below. This code works fine but the way I have it working concerns me. I seem to be passing object all over the place (needlessly?) and there is lack of uniform to my method calls.
Hopefully the code is self explanatory and one of you geniuses can tell me everything I'm doing wrong and send back an example of how it should be done right!
Thanks for any tips!
FYI - The $article->tags field is comma-delimited 'keywords,like,this'
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\Article;
use App\Models\Category;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ArticleController extends Controller
{
private $article;
public function __construct()
{
}
// SHOW ALL ARTICLES
public function index(){
// Fetch articles and paginate
$articles = Article::getPublicArticles()->latest()->paginate(9);
// Explode each article's tags to arrays
$articles = Article::tagsToArrayFromMany($articles);
return view('articles.index', [
'articles' => $articles
]);
}
// SHOW SINGLE ARTICLE
public function show(Article $article, $slug){
// Increment the number of views
$article->addView($article);
// Explode this article's tags to an array
$article->tagsToArrayFromOne($article);
// Fetch other articles
$other_articles = $article->getOtherPublicArticles($article->hex)->take(3)->get();
// Explode each article's tags to arrays
$other_articles = Article::tagsToArrayFromMany($other_articles);
// Load the view
return view('articles.show', [
'article' => $article,
'other_articles' => $other_articles
]);
}
And here's the model:
<?php
namespace App\Models;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Article extends Model
{
use HasFactory;
// Set route key name
public function getRouteKeyName(){
return 'hex';
}
// Accessor for retrieving and formatting 'created_at'
public function getCreatedAtAttribute($value){
return Carbon::parse($this->attributes['created_at'])->format('d/m/Y');
}
// Relationship to user
public function user(){
return $this->belongsTo(User::class, 'user_id');
}
// Get all public articls
public static function getPublicArticles(){
return Article::where('status', 'public');
}
// Explode tags to arrays for all articles
public static function tagsToArrayFromMany($articles = []){
foreach($articles as $key => $article){
if($article->tags){
$articles[$key]['tags'] = explode(',', $article->tags);
}
}
return $articles;
}
// Explode tags to arrays for one article
public static function tagsToArrayFromOne($article){
if($article->tags){
$article['tags'] = explode(',', $article->tags);
}
return $article;
}
// Add view
public static function addView(object $article){
$article->views = ($article->views + 1);
$article->save();
}
// Get other articles
public static function getOtherPublicArticles(string $hex){
return self::getPublicArticles()->where('hex', '!=' , $hex)->orderByRaw('RAND()');
}
This question would most likely be more suited on CodeReview but that aside.
My response is more of a comment than an answer, however, comments have character limits so leaving this here. This is also my opinion, there is a balance to be struck between perfectionism and pragmatism and developers have different opinions on where on the scale you should be in different situations.
There are a couple of books I would recommend for reference to guide you in future situations:
Clean Code by Uncle Bob
The Pragmatic Programmer
My reasons for recommending the above are that Clean Code provides goals to strive towards whilst The Pragmatic Programmer highlights that goals are great in isolation but not always productive.
We call ourselves programmers but we're more than that, we're problem solvers that just use code as our tool to do so. Our true goal is not to write beautiful code but to solve a problem. If we can simultaneously do both then fantastic but, a client doesn't care about the code we write they just want a solution.
My point being, aim to follow "best practices" but don't get too hung up on them and fall into the trap of analysis paralysis and bike-shedding. Get something that works and then refactor where required later.
Anyway ...
Ideally a controller acts soley as a conductor between a request and response. It should determine where data should be forwarded and therefore which models or services to use. Business logic, shouldn't bleed into a controller.
Your ArticleController isn't horrific, however, it could probably be 'improved' a little. Both of your actions request data from your Article model then use the response to make another request to the model. Instead just combine those requests into a single request.
So for example refactoring:
$articles = Article::getPublicArticles()->latest()->paginate(9);
// Explode each article's tags to arrays
$articles = Article::tagsToArrayFromMany($articles);
To:
$articles = Article::publishedWithTags()->latest()->paginate(9);
You could do similar with your show method. Again this is just my opinion and you could 'optimise' it further by combining the latest call into your method if you wanted.
I am not sure why you have choosen to define all your methods as static, that doesn't seem necessary in my opinion. For querying your models, consider using query scopes.
Additionally, rather than defining and calling an addView() method, consider using a model event.
I don't know where you are in your journey, however, I suggest and recommend watching the Laravel from Scratch series. It covers a lot of common situations and suggested best practices when it comes to using the Laravel framework.
I'm new in Laravel, I want to know where is the correct place where define functions that query table from DB. In Model or in Controller?
Example:
public function insertUser($firstname, $lastname, $email) {
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->save();
return $user;
}
The function above where I should declare? Models or Controllers?
Edit:
For example: I need to create a function that return male authors that live in USA and their books. I define AuthorController that use Author (Model). What's the right way to define this function? I write a function in my controller that accept gender and nation as arguments, like:
public function getAuthoursByGenderAndNation($gender, $nation) {
$authors = Author::with("books")->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return $authors;
}
Or I define a generic function that returns all authors with their books and then apply where clause on function that call this generic function? Like:
public function showAuthors(Request $request) {
$gender = $request->get("gender");
$nation = $request->get("nation");
$authors = $this->getAuthors()->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return view("authors", ["authors" => $authors]);
}
public function getAuthors() {
$authors = Author::with("books");
return $authors;
}
keep in mind that all application logics should be in controller, and all data operations should be in model. in your question insert user is a application logic, so you should place that on controller, but if you want to define how data is managed, place that method in model. For example, you want a model has ability to retrieve a collection with some condition, may be a user with female gender only so you can Access it via Modell::getFemale()
The function you mention, should be used within a controller. I would recommend that you get a grasp on how MVC works before you dive in Laravel.
Reading that may be useful to you
MVC Concept
Laravel Docs
PHP MVC Tutorial
As according to MCV recommendations.
M (model) should be fat and C (controller) should be thin.
you should write your all database transaction related code in model. Even you can create repositories for database queries.
Your controller should be thin, so you should write only logical code there, like calling model function.
Example:
UserController.php
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Request;
class UserController extent Controller {
use App\User;
protected $_user;
public function __construct(User $user) {
$this->_user= $user;
}
function saveUser(Request $request) {
$user->fill($request->all());
$user->save();
// or you can directly save by $user->create($request->all());
}
}
This is how you can directly fill data to your User model with $fillable attribute defined there as
$fillable= ['name','email','password'];
If you define your model under the conventions of Eloquent you can simply use the built in Eloquent methods to insert your user as demonstrated in the documentation.
https://laravel.com/docs/5.3/eloquent#inserting-and-updating-models
In the wider scope of your question: 'where to define functions that query the DB table'.
I would suggest typically defining these on the model and looking to make use of the structures provided by Eloquent, for example defining scoped queries on your model.
The code in your controller would then call methods on your model eg.
Model::create();
It also appears you are trying to insert users. I would strongly suggest you look into using Laravel's built in Authentication structures. You'll find these very powerful.
https://laravel.com/docs/5.3/authentication
Hope this helps get you started.
I successfully created a global scope in Laravel and I want to query a relation model in the global scope. I have a Video model, a Category mode, and a VideoCategory pivot model and I want to access the category model using video model in the global scope, such as:
<?php
use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;
class DefaultScope implements ScopeInterface{
public function apply(Builder $builder)
{
$model = $builder->getModel();
$builder->whereHas('categories', function( $q ){
$q->where('language', 2);
});
}
public function remove(Builder $builder)
{
}
}
Is that a possible thing to do?
Short answer: don't do that.
How to? Use manual joins, but again, don't.
It will lead to errors, unexpected behaviour, and you will quit it as soon as you got it work.
Eloquent creates new query for a model in so many places, that using has is impossible, what you have already noticed, I suppose. Using manual joins would let you do that for given model, but would also break 90% of relation based features of Eloquent, ie. you couldn't use has or with towards this model etc.
I have News model, when i query news, i want it brings news where status = 1 as default.
News::all(); // select * from news where status = 1
News::where('anotherColumn',2)->get(); // select * from news where status = 1 and where category = 2
Is this possible? What i want is so similar to soft delete feature (it gets where deleted_at is not null and if all data is wanted withTrashed function can be used).
I looked docs but i couldn't find anything helpful. Also, i tried to handle it in construct at News model but it didn't worked either.
Thanks.
I normally override newQuery() for this. newQuery() is the method that Eloquent use to construct a new query.
class News extends Eloquent {
public function newQuery($excludeDeleted = true) {
return parent::newQuery($excludeDeleted)
->where(status, '=', 1);
}
}
Now your News::all() will only output your news with status = 1.
It's been already mentioned but here is a quick example using global scope which might be the best current solution since you wont have to override Eloquent methods and would result into the same behavior but with more control of your model.
Just add this to your model :
protected static function boot()
{
parent::boot();
static::addGlobalScope('exclude_deleted', function (Builder $builder) {
$builder->whereNull('deleted_at');
});
}
You can also create a child Scope class and reuse it for multiple Models.
For more information, Laravel doc explained pretty much everything about it:
https://laravel.com/docs/5.6/eloquent#global-scopes
I think the closes you'll get, without actually going in to change some core files...
is Query Scope...
Scopes allow you to easily re-use query logic in your models. To define a scope, simply prefix a model method with scope:
class News extends Eloquent {
public function scopeStatus($query)
{
return $query->where('status', '=', 1);
}
}
Utilizing that scope
$news = News::status()->get();
$news2 = News::status()->where('anotherColumn',2)->get();
Its not quite what you wanted...but its definitely a little shorter than typing
News::where('status','=',1)->get();
over and over
I have a controller/model for projects. so this controls the projects model, etc, etc. I have a homepage which is being controlled by the pages_controller. I want to show a list of projects on the homepage. Is it as easy as doing:
function index() {
$this->set('projects', $this->Project->find('all'));
}
I'm guessing not as I'm getting:
Undefined property: PagesController::$Project
Can someone steer me in the right direction please,
Jonesy
You must load every model in the controller class by variable $uses, for example:
var $uses = array('Project');
or in action use method
$this->loadModel('Project');
In my opinion the proper way to do this is add a function to your current model which instantiates the other model and returns the needed data.
Here's an example which returns data from the Project model in a model called Example and calls the data in the Example controller:
Using Project Model inside Example Model:
<?php
/* Example Model */
App::uses('Project', 'Model');
class Example extends AppModel {
public function allProjects() {
$projectModel = new Project();
$projects = $projectModel->find('all');
return $projects;
}
}
Returning that data in Example Controller
// once inside your correct view function just do:
$projects = $this->Example->allProjects();
$this->set('projects', $projects);
In the Example view
<?php
// Now assuming you're in the .ctp template associated with
// your view function which used: $projects = $this->Example->allProjects();
// you should be able to access the var: $projects
// For example:
print_r($projects['Project']);
Why is this "better" practice than loading both models into your controller? Well, the Project model is inherited by the Example model, so Project data now becomes part of the Example model scope. (What this means on the database side of things is the 2 tables are joined using SQL JOIN clauses).
Or as the manual says:
One of the most powerful features of CakePHP is the ability to link relational mapping provided by the model. In CakePHP, the links between models are handled through associations.
Defining relations between different objects in your application should be a natural process. For example: in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have many recipes. Defining the way these relations work allows you to access your data in an intuitive and powerful way. (source)
For me it's more reasonable to use requestAction. This way the logic is wrapped in the controller.
In example:
//in your controller Projects:
class ProjectsController extends AppController {
function dashboard(){
$this->set('projects', $this->Project->find('all'));
}
$this->render('dashboard');
}
Bear in mind that you need to create dashboard.ctp in /app/views/projects of course.
In the Page's dashboard view (probably /app/views/pages/dashboard.ctp) add:
echo $this->requestAction(array('controller'=>'projects', 'action'=>'dashboard'));
This way the logic will remain in the project's controller. Of course you can request /projects/index, but the handling of the pagination will be more complicated.
more about requestAction(). but bear in mind that you need to use it carefully. It could slow down your application.