I'm new to PHP programing and Symfony. English isn't my native language so sorry if it sounds strange how I write...
I have two entities: Article and Category, where each Article has a Category. And I need to show how many articles I have for a given category:
Category ------------------ N°
Vehicles -------------------- 4
Electronics ---------------- 20
Food ----------------------- 15
Furniture ------------------ 8
With Doctrine I've made the CRUD files for both entities.
php app/console doctrine:generate:crud
Like I said, the problem is that I want to show a table with properties of a Category (name, description, etc) and how many articles of it are in the inventory.
The SQL query is very simple:
SELECT count(*) FROM Articles a WHERE a.id_category = $id_category
Where to put that?! I'm very confused and don't want to brake best practice rules.
Doctrine generated lots of files: ArticleController.php, ArticleType.php (Form), and all the .twig for the views. Same for Category.
In the spirit of giving a direct answer to your question: what you need is a custom Doctrine Repository, in this case an ArticleRepository.
e.g.
ArticleRepository.php
namespace ACME\DemoBundle\Article;
use Doctrine\ORM\EntityRepository;
class ArticleRepository extends EntityRepository
{
public function countForCategory($id)
{
$result= $this->createQueryBuilder('a')
->join('a.category', 'c')
->where('c.category_id = :id')
->setParameter('id', $id)
->select('count(a')
->getQuery()
->getSingleScalarResult();
return $result;
}
}
And then you set your Article entity to use that ArticleRepository
Article.php
/**
* #ORM\Entity
* #ORM\Table(name="article")
* #ORM\Entity(repositoryClass="ACME\DemoBundle\Entity\ArticleRepository")
*/
class Article
{
/// etc...
You can then get the Repository for the Entity in e.g. your Controller (although as suggested elsewhere you can hide this away in some kind of service to avoid filling your Controller with business logic; it just needs to be reachable somehow from inside your Controller), and run the query:
ArticleController
class ArticleController extends Controller
{
public function countAction($id)
{
$articleRepo = $this->getDoctrine()->getRepository('DemoBundle:Article');
$count = $articleRepo->countForCategory();
//etc...
NB If you really want exactly the output in your example, it may be more efficient to do one query for the whole table, probably in a CategoryRepository, performing a count and grouping by Category. The query would return an array containing Category name and count.
When using Symfony2 + Doctrine2,
don't think (so much) about the database. Think of persistent data objects, so called entities, and let Doctrine handle them. Spend some time on cleanly defining the relations between those objects, though.
forget about the term “MVC”. MVC is a rather abstract concept, things are more complex in reality. Fabian (lead dev of SF) has a nice write-up about this topic: http://fabien.potencier.org/article/49/what-is-symfony2
If you wonder where to put what in your Symfony bundles, read this Cookbook article: http://symfony.com/doc/current/cookbook/bundles/best_practices.html
Create a CategoryManager class in your service layer, and handle any business logic there. You can pass the router to it through dependency injection.
For your example, CategoryManager would have a getUrl(Article $article) method which would use the router instance (that you either injected through __construct or a separate setter method) to generate the Url based on properties of $article, and return it.
This method will ensure that your business logic doesn't pollute the view or controller layers.
Check this link Services Symfony
Related
So my products (books) are filtered into Fiction / Non Fiction, and into subcategories for each, i've made all the logic for filtering and displaying the products but what i'm struggling with is naming the routes and controllers for it.
I thought of using one resource BookController with the standard 7 methods, and somehow passing a criterium, fiction/nonfiction or specific subcategory id into the index method, that then calls my BookService class which returns the proper collection.
I don't think posting my code will be very helpfull but just in case, these are my BookService methods
/*
Returns a collection of n random recommended books
n = $quantity
*/
public function getRecommended(int $quantity){
return Book::with('authors')->get()->random($quantity);
}
/*
Returns a collection of books by passed categoryId
*/
public function getByCategory(int $categoryId){
return Book::with('authors', 'category')->category($categoryId)->get();
}
/*
Returns a collection of fiction books
*/
public function getFiction(){
return Book::with('authors', 'category')->nonFiction()->get();
}
/*
Returns a collection of non fiction books
*/
public function getNonFiction(){
return Book::with('authors', 'category')->fiction()->get();
}
fiction(), nonfiction(), category($categoryId) are local scopes in my Book Eloquent Model.
My question is
What should I name controllers for displaying All books and Filtered books, and what should i call their methods?
Should i maybe use one BookController resource controller and somehow pass it's index method the criterium for filtering, if that's the proper way, can somebody help me with how to actually pass the criterum from my view with Blade Templates and how to set it up using Route::resource(..).
Thanks in advance.
the laravel generated resources for controllers represents the CRUD functions (create, read, update, delete) if you treat your eloquent models in your application as a resource.
i'm assuming that your problems are more about diplaying the books for your clients (visitors) a READ action.
Laravel doesn't force you to user the default generated crud actions, if you need to create a method that is not in context of model only, for exemple getting the mode data for your book by calling an external ISBN api, it's totally olowed.
So i recommend keeping all your actions in "BookController"
if you want to keep the default actions you can do your filtering inside the "index" method.
if you want to add a "filter" method and add it in your routes, nothing in laravel is preventring you.
NOTE : for the "getRecommended" method, it's not optimized, you can use Book::with("authrs")->inRandomOrder()->take($quantity)->get();
I'm currently working a tournament organization project, I would like to what's the best technique to have different implementation for the same model, for example I have model called matches, than retrieves data related to matches, but these matches may have different types depending on match_type field specified in matches model, what I did is creating parent class /Match , and having different types of matches extend this parent class. Ex:
Class SinglElimination/Match extends /Match{}
Class GroupStage/Match extends /Match{}
so with this design I have to get the parent match first to get the match_type then re-run the query to get a match with the needed child model
$match=Match::find($id);
$type = $match->match_type;
$childMatch = new $match_type.'/Match'();
$match = $match->where('_id', $this->_id)->first();
which I know is nowhere near clean, so how would you implement this ?
If I were you I would separate those 3 classes and exclude any extending. Take a look at Polymorphic relationships in Laravel, here is the quick link. It will be a cleaner approach and in my opinion it would be the best approach here, all you'll have to do is design the tables properly and do relationships properly too.
The main model is BaseMonster, with all the basic informations of a monster (stats, monster type, etc) that will be inherited by child classes.
At the moment, I have two child classes named WildMonster and PlayerMonster, related to BaseMonster with a one to many relationship (from BaseMonster side).
My main goal is to be able to inherit attributes like stats, names and images and all the related methods from BaseMonster to other child monster classes.
This way i can avoid db redundancy for common attributes and at the same time adjust various aspect of certain child monsters just editing theirs parent BaseMonster record.
Example: "Kraken" monster has too high attack stat value. With a simple change to its BaseMonster record, all WildMonster and PlayerMonster instances related to "Kraken" are affected.
I want to avoid code redundancy too, for example making one rating() method in BaseMonster that will use all the actual stats (along with stat bonuses and levels) of the monster instance and that can be used by any of the monster child classes.
I tried accessing parent properties with Laravel accessors, and setting them to the Eloquent base_monster relationship stat, but this way i can't work with children attributes, like level or health_bonus to adjust values accordingly. (or at least i didn't find the right way).
I also tried setting accessors from child side with half a success, but anyway this mean i have to write the same accessors in all child classes and to make a huge mess if i want to change something.
How can I achieve something like this in laravel 5.4?
Extract of attributes
BaseMonster is the parent of the other monster classes and contains all the common attributes and methods needed in most of the occasions. BaseMonsters are created by Game Admin to manage things like stats and images and to generate new WildMonster to populate various Dungeons.
BaseMonster (extends Model)
name
image
health
attack
defense
monster_type_id (relationship with the MonsterType model)
WildMonster is the monster encountered in dungeons or in other ways. Can "transform" to PlayerMonster if recruited.
WildMonster (extends BaseMonster)
name (to customize name for special encounters)
level (to adjust stats accordingly)
health_bonus (there is a chance a monster has various stats bonuses)
attack_bonus
defense_bonus
base_monster_id (relationship with the BaseMonster model)
PlayerMonster is the monster recruited by the player after a battle.
PlayerMonster (extends BaseMonster)
name (eventually customized by the player)
level
health_bonus
attack_bonus
defense_bonus
base_monster_id (relationship with the BaseMonster model)
What you are looking for is called Polymorphic Relations in Laravel. This way you can define a base model BaseMonster as requested. Following the example on the site:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseMonster extends Model
{
public function monster_wild_or_player()
{
return $this->morphTo();
}
public function monster_type()
{
return $this->hasOne('App\MonsterType');
}
}
class PlayerMonster extends Model
{
public function base_info()
{
return $this->morphMany('App\BaseMonster', 'monster_wild_or_player');
}
}
class WildMonster extends Model
{
public function base_info()
{
return $this->morphMany('App\BaseMonster', 'monster_wild_or_player');
}
}
I'm trying to get 'related' linked models by querying a link table, named company_projects which holds (as you expect) the id's of companies and projects (projects are kind of product-categories).
In this case, the used flow to determine a related project is:
Get companies who are in the same project ('product category') as you
Find the other project id's which are linked to those companies
Get the info of the linked projects fetched by last step
What i'm trying to do is already functional in the following raw query:
SELECT
*
FROM
projects
WHERE
projects.id IN
(
SELECT cp1.project_id
FROM company_projects cp1
WHERE cp1.company_id IN
(
SELECT cp1.company_id
FROM projects p
LEFT JOIN company_projects cp2 ON cp2.project_id = p.id
WHERE p.id = X AND cp2.company_id != Y
)
)
AND projects.id != X
X = ID of current project ('product category')
Y = ID of current 'user' (company)
But my real question is, how to do this elegantly in Laravel Eloquent (currently v4.2). I tried it, but I have no luck so far...
Update:
I should note that I do have experience using Eloquent and Models through multiple projects, but for some reason I just fail with this specific query. So was hoping to see an explained solution. It is a possibility that I'm thinking in the wrong way and that the answer is relatively easy.
You will need to utilize Eloquent relationships in order to achieve this. (Note that I am linking to the 4.2 docs as that is what you are using, but I would highly suggest upgrading Laravel to 5.1)
I am assuming you have a 'Company' and 'Project' model already. Inside each of those models, you need to a define a method that references its relationship to the other model. Based on your description, it sounds like the two have a Many to Many relationship, meaning that a company can have many projects and a project can also belong to many companies. You already have a database table linking the two. In the Eloquent ORM this linking table is called a pivot table. When you define your relationships in the model, you will need to pass the name of that pivot table as your second argument. Here's how it could look for you.
Company model:
class Company extends Model
{
/**
* Get the projects attached to a Comapny. Many To Many relationship.
*/
public function projects()
{
return $this->belongsToMany('Project','company_projects');
}
}
Project model:
class Project extends Model
{
/**
* Get the companies this project belongs to. Many To Many relationship.
*/
public function companies()
{
return $this->belongsToMany('Company','company_projects');
}
}
If your models have these relationships defined, then you can easily reference them in your views and elsewhere. For example, if you wanted to find all of the projects that belong to a company with an ID of 1234, you could do this:
$company = Company::find(1234);
$projects = $company->projects;
Even better, you can utilize something called eager loading, which will reduce your data lookup to a single line (this is mainly useful when passing data to views and you will be looping over related models in the view). So those statements above could be rewritten as:
$company = Company::with('projects')->find(123);
This will return your Company model with all its related products as a property. Note that eager loading can even be nested with a dot notation. This means that you can find all the models that link to your main model, and then all the models for those models, and so on and so forth.
With all of this in mind, let's look at what you specifically want to accomplish.
Let us assume that this method occurs in a Controller that is being passed a project id from the route.
public function showCompaniesAndProjects($id)
{
//Get all companies in the same project as you
//Use eager loading to grab the projects of all THOSE companies
//Result will be a Project Object with all Companies
//(and those projects) as Eloquent Collection
$companies = Project::with('companies.projects')->find($id);
//do something with that data, maybe pass it to a view
return view('companiesView')->with('companies',$companies);
}
After defining your relations in your models, you can accomplish that whole query in a single line.
I have a situation like this. There are authors and books. books belong to authors and authors have many books.
There is also another table publish_year where different years have primary keys cast against them. i.e. year 2005 has primary key 1, year 2006 has primary key 2 etc.
Now, on the books table, the year_id is the foreign key that refers to the primary key of the publish_year table. Now I want to eager load all the authors with their books for a specific year (which is made active from the admin panel).
I have developed the following eloquent query. Models are Author, Book and PublishYear. There are some additional where conditions performed on the query.
return Author::with(['books' => function($query) {
$query->where('year_id', '=', PublishYear::where('is_active', '=', 1)->pluck('id'))
->where('is_available', '=', 't')
....;
}])->get();
It works. Now is there any way to use a scope inside the query like this:
return Author::with(['books' => function($query) {
$query->scope();
}
So, that I can abstract away the implementation detail from the controller.
We use a repository design pattern for this.
The repository mediates between the data source layer and the business
layers of the application. It queries the data source for the data,
maps the data from the data source to a business entity, and persists
changes in the business entity to the data source. A repository
separates the business logic from the interactions with the underlying
data source or Web service.
Laravel has great support for repository design using interfaces. In our current webhosting control panel it's widely used to facilitate our complex data structures with multiple relations.
Ryan Tablada has a nice article describing the exact benefits: http://ryantablada.com/post/two-design-patterns-that-will-make-your-applications-better
If you need some more code examples see this heera.it article that goes more into detail about how to implement it: http://heera.it/laravel-repository-pattern
I you declare a scope function in your Book model, you should be able do use it in your query.
Book.php
public function scopePublishingYear($query, $year)
{
return $query->where('year_id', $year)->where('is_available','t');
}
And in your controller:
$year = PublishYear::where('is_active', '=', 1)->pluck('id');
return Author::with(['books' => function($query) use($year) {
$query->publishingYear($year);
}
But, as Luceos said, if you want to keep your controller ever cleaner, you can use a repository pattern and be able to write something like this:
return $this->authorRepository->getWithBooks()