Multiple tables in one model - Laravel - php

My Index page uses 3 tables in the database:
index_slider
index_feature
footer_boxes
I use one controller (IndexController.php) and call the three models like so:
public function index() {
return View::make('index')
->with('index_slider', IndexSlider::all())
->with('index_feature', IndexFeature::all())
->with('footer_boxes', FooterBoxes::all());
}
The three models above need ::all() data, so they are all setup like this:
class IndexSlider extends Eloquent {
public $table ='index_slider';
}
note: class name changes for each model
Seeing as my index page requires these 3 tables and the fact I am repeating the syntax in each model then should I be using polymorphic relations or setting this up differently? ORM from what I have read should have 1 model for each table, but I can't help but feel this would be silly in my situation and many others. DRY (don't repeat yourself) looses meaning in a sense.
What would be the best approach to take here or am I on the right track?

Firstly I should say each model is written for a specific table, you can't squeeze three tables into one model unless they are related. See Here
There are two ways I would go about making your code more DRY.
Instead of passing your data in a chain of withs I would pass it as the second parameter in your make:
public function index() {
$data = array(
'index_slider' => IndexSlider::all(),
'index_feature' => IndexFeature::all(),
'footer_boxes' => FooterBoxes::all(),
);
return View::make('index', $data);
}
Passing data as the second parameter. See here
The other way I would go about it, and this is a better solution if your application is going to grow large, is to create a service (another model class but not hooked up to eloquent) that when you call will return the necessary data. I would definitely do it this way if you are returning the above data in multiple views.
An example of using a service would look something like this:
<?php
// app/models/services/indexService.php
namespace Services;
use IndexSlider;
use IndexFeature;
use FooterBoxes;
class IndexService
{
public function indexData()
{
$data = array(
'index_slider' => IndexSlider::all(),
'index_feature' => IndexFeature::all(),
'footer_boxes' => FooterBoxes::all(),
);
return $data;
}
}
and your controller:
<?php
// app/controllers/IndexController.php
use Services/IndexService;
class IndexController extends BaseController
{
public function index() {
return View::make('index', with(new IndexService())->indexData());
}
}
This service can be expanded with a lot less specific methods and you should definitely change the naming (from IndexService and indexData to more specific class/method names).
If you want more information on using Services I wrote a cool article about it here
Hope this helps!

Related

A few questions about Skinny Controller, Fat Model best practice - How to load methods and pass data from controller to model

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.

Laravel Eloquent - Loading query scopes eagerly

I have a data structure in which I need objects to be aware of their needed dependencies for loading.
What I can do
Currently, I can do this to load the first layer of relationships, this is obviously a very basic model:
class Ticket {
public function notes(){}
public function events(){}
public function tags(){}
public function scopeWithAll($query)
{
$query->with('notes', 'events', 'tags');
}
}
// Loads Ticket with all 3 relationships
$ticket = Ticket::withAll();
This works great! The problem being, I need to chain this functionality down to 3-5 levels of dependent relationships. Each of the 3 loaded models is going to have n relationships of its own.
I know I can do this through eager loading if I specify all of the relationship names, as follows:
public function scopeWithAll($query)
{
$query->with('notes.attachments', 'notes.colors', 'events', 'tags', 'tags.colors.', 'tags.users.email');
}
This works great too. But I need my code to be smarter than that.
What I need to do
Statically defining the scope of each object load is not desirable at this point in my project. I need to be able to load a Ticket, and the Ticket load all of its relationships, and each of those relationships load all of their relationships.
The only way I can think to do this is find some way to eagerly load a query scope for each relationship on the class. Something like
public function scopeWithAll($query)
{
$query->with('notes.withAll()', 'events.withAll()', 'tags.withAll()');
}
Is there currently a way to do this within Eloquent?
Maybe you can try something like this:
User::withRelatives()->find(1);
Okay, that's an idea and how to implement that? For example, if you have some related methods for your User model such as 'posts', 'roles' etc then keep all the related methods (methods that make relationship) in a separate trait, for example:
trait UserRelatives {
public function posts()
{
// ...
}
public function roles()
{
// ...
}
}
Now, in the User model you may create a scopeMethod like withAll and inside there you may try something like this:
public function scopeWithAll($query)
{
// Get all the related methods defined in the trait
$relatives = get_class_methods(UserRelatives::class);
return $query->with($relatives);
}
So, if you do something like this:
$user = User::withAll()->find(1);
You'll be able to load all related models. Btw, get_class_methods(UserRelatives::class) will give you an array of all methods defined in that trait which may look something like this:
['posts', 'roles']
So, User::withAll() will load all the related models and then run the query.So, as a result the scope will do something like this:
$query->with(['posts', 'roles']);
Well, this is an abstract idea but hope you got it. Share your idea if you found something better.
Update:
According to your Model and related methods, this may look something like this:
class Ticket {
use TicketRelativesTrait;
public function scopeWithAll($query)
{
$relatives = get_class_methods(TicketRelativesTrait::class);
return $query->with($relatives);
}
}
Trait:
trait TicketRelativesTrait {
public function notes(){}
public function events(){}
public function tags(){}
}
// Loads Ticket with all relationships
$ticket = Ticket::withAll()->find(1);
This is more dynamic, no need to mention the related methods and whenever you add a new relationship method in the trait, that will also be loaded.

What comes in model...?

Hi I am trying to build a to do list in Laravel. It's my first time with a framework. I have actually read a lot of articles about MVC but I don't understand the meaning of model enough, and I want to learn this the correct way. So I have this code and I didn't know where to place it, in a controller or in a model?
public function getTask()
{
$tasks = DB::table('tasks')->get();
foreach ($tasks as $task) {
var_dump($task->name);
var_dump($task->description);
}
}
public function deleteTask($id)
{
DB::table('tasks')->where('id',$id)->delete();
}
public function updateTask($id)
{
DB::table('tasks')
->where('id',$id)
->update(['votes' => 1]);
}
public function createTask($name,$slug,$description)
{
DB::table('tasks')->insert(
['name' => $name],
['slug' => $slug],
['description' => $description]
);
}
I am a very new with frameworks so please be patient with my question.
the main role of models is to abstract the database layer to the framework (it is the class that talks to the database), so theoretically speaking, you would only use models to query the database (with active record class or native SQL) and return the result to the controller and the controller will do all the logic work (if...else...etc.) then send it to the view or call a model again to talk to the database ,BUT it is not uncommon in real life to see some logic code in the model althought it is considered as a bad practice in MVC world .you can read more about models for laravel here http://laravel.com/docs/5.1/eloquent
The code your showing goes in a controller.
Simply said: A model represents your table in your Database and could look as simple as this (User.php);
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//
}
The controller has the logic implementation necessary to read and modify the view through the model.
The model represents all the information that the user can control so it enables the controller access to the view and the view access to the controller, which is called as data-binding.

Doctrine, MVC, Symfony: where may I use Doctrine? May I use it in controller?

Lets see my architect:
Model:
// links table: (ID, LINKNAME)
Class Link extends Link_base
{
}
Controller:
public function index()
{
$this->links = new Doctrine - here I build the query, SELECT, ORDER BY, etc
}
in this example, the model can be remain empty (no serious logic), all I need is a select with an order by. Im not sure I can use Doctrine in controller though - should I remake it like this?
Class Link extends Link_base
{
public function getLinks()
{
return new Doctrine - here I build the query, SELECT, ORDER BY, etc;
}
}
Controller:
public function index()
{
$this->links = Links::getLinks();
}
Im not sure which way seems to be OK. Of course, when selecting needs a more complex, formatting todo-s, it goes to the model or helper - but I feel like I just made a new (unnecessary) layer. This getLinks() used only once. In other words: Doctrine may be only used in model, or can it be used in controllers too?
Your entities (or models if you prefer that name) should not know how they are saved to / retrieved from the database. They should just be simple PHP objects, only containing a number of properties (corresponding to the database columns) and their getters and setters.
(If you are interested, read a bit about the single responsibility principle which states that every class should have one, and only one responsibility. If you make your entities both responsible for storing data and knowing how to save that data in the database, you will have a greater chance of introducing bugs when one of those things changes.)
You can fetch entities from inside your controller:
<?php
namespace Your\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class LinkController extends Controller
{
public function fooAction()
{
$links = $this->getDoctrine()
->getRepository('YourBundle:Link')
->findAll();
// do something with the result, like passing it to a template
}
}
However, you might need a more complex query (that includes sorting and filtering) and you might need to run that query from multiple controllers. In that case, you don't want to duplicate that logic to multiple controllers, you want to keep that logic in one central place.
To do so, create a repository:
<?php
namespace Your\Bundle\Repository;
use Doctrine\ORM\EntityRepository;
class LinkRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(
'SELECT l FROM YourBundle:Link l ORDER BY l.name ASC'
)
->getResult();
}
}
And add the repository class to your mapping:
Your\Bundle\Entity\Link:
type: entity
repositoryClass: Your\Bundle\Repository\LinkRepository
(check the Symfony's documentation about custom repositories if you're using XML or annotations instead of Yaml)
Now in your controller, you can simply update your fooAction method so it uses your custom repository method:
public function fooAction()
{
$links = $this->getDoctrine()
->getRepository('YourBundle:Link')
->findAllOrderedByName();
}
For more information, Symfony's documentation includes a great article about Doctrine. If you haven't done so already, I'd definately recommend reading it.

CakePHP: using models in different controllers

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.

Categories