Return relationships in Laravel show route - php

When I create a CRUD controller, this is the show route created by default:
/**
* Display the specified resource.
*
* #param \App\Team $team
* #return \Illuminate\Http\Response
*/
public function show(Team $team)
{
//
}
$team is an object here, an instance of Team. If I do this I have the correct object passed to blade:
public function show(Team $team)
{
return view('admin.teams.show', ['team' => $team]);
}
But, Team has a many-to-many relationship with another model called Player, and this relationship is defined as such from the Team side:
public function players() {
return $this->belongsToMany(Player::class);
}
In my show method, I'd like to return the $team with its related players. But since $team is already an object and not a query builder, it's too late to do something like
$team->with('players')
So how do I get the related players here? I know I can do something like:
public function show(Team $team)
{
$team_extended = Team::where('id', $team['id'])->with('players')->first();
return view('admin.teams.show', ['team' => $team_extended]);
}
But it feels like hacking a functionality that should be there by default. Is there a built-in Laravel way to do this or am I just inventing hot water and should take the approach I used in my solution above?

If you've already got your Team model loaded, you can load a relationship without having to completely re-create it using the ->load() method:
public function show(Team $team){
$team->load("players");
return view("admin.teams.show", ["team" => $team]);
}
Note however, this isn't required unless you need to modify the default content of $team->players. When you trying to access $team->players say in your admin.teams.show view, if that property doesn't already exist (as it would using ->with(["players"]) or ->load("players"), Laravel will load it automatically.

Related

Am I doing eager loading correctly? (Eloquent)

I have a method that needs to pull in information from three related models. I have a solution that works but I'm afraid that I'm still running into the N+1 query problem (also looking for solutions on how I can check if I'm eager loading correctly).
The three models are Challenge, Entrant, User.
Challenge Model contains:
/**
* Retrieves the Entrants object associated to the Challenge
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
Entrant Model contains:
/**
* Retrieves the Challenge object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
and User model contains:
/**
* Retrieves the Entrants object associated to the User
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
The method I am trying to use eager loading looks like this:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* #return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
I feel like I followed what the documentation recommended: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
but not to sure how to check to make sure I'm not making N+1 queries opposed to just 2. Any tips or suggestions to the code are welcome, along with methods to check that eager loading is working correctly.
Use Laravel Debugbar to check queries your Laravel application is creating for each request.
Your Eloquent query should generate just 3 raw SQL queries and you need to make sure this line doesn't generate N additional queries:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()
when you do ->with('entrants.user') it loads both the entrants and the user once you get to ->get(). When you do ->load('user') it runs another query to get the user. but you don't need to do this since you already pulled it when you ran ->with('entrants.user').
If you use ->loadMissing('user') instead of ->load('user') it should prevent the redundant call.
But, if you leverage Collection methods you can get away with just running the 1 query at the beginning where you declared $challenges:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
You don't need to use ->load('user') because $challenge->entrants is already populated with entrants and the related users. so you can just leverage the Collection method ->sortByDesc() to sort the list in php.
also, You don't need to run ->all() because that would convert it into an array of models (you can keep it as a collection of models and still foreach it).

Laravel changing sorting of collection when sending mail (between constructor and build function call)

this is laravel 5.3
when I preview the email using this:
$wantsheet_products = WantsheetProduct::orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)->get();
View::make('email.wantsheet.email_wantsheet_to_supplier', ['wantsheet_products' => $wantsheet_products]);
the sorting is correct. that is, sorting is ['a','b','c'] the way i want it.
EDIT see note at the bottom
now when actually sending out the mails (i queue them), the sorting changed and is unsorted again, magic?! the change happens between the constructor and the build function
class WantsheetToSuppliersMail extends Mailable
{
public $wantsheet_products;
public $to_email;
/** #var WantsheetContact $wantsheetcontact*/
public $wantsheetcontact;
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($wantsheet_products)
{
//$wantsheet_products is a standard eloquent model collection, e.g. i get it like this: WantsheetProduct::orderByRaw(self::WANTSHEET_PRODUCT_ORDER_SQL)->get()
$this->wantsheet_products = $wantsheet_products; //is ['a','b','c']
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
// $this->wantsheet_products is ['b','a','c'];
$subject = 'abc';
return $this->from('me#myapp.com')->view('email.wantsheet.email_wantsheet_to_supplier', [])->subject($subject);
}
}
EDIT contd.
Now when i do
WantsheetProduct::orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)->get()->toArray();
it doesn't break the sorting any longer (so it works). But that is stupid, isn't it?
When your mail object is queued for delivery, it takes your Collection of Model instances, gets their ids, and stores the list of ids on the queued job. When the queued job is then processed, it takes those Model ids, and retrieves the data from the database.
The problem, however, is that the query being run to rebuild the collection doesn't care about the order of the ids. It just runs a whereIn() statement with the list of ids.
Everything worked when you converted your Collection toArray() because it also converted all your Models to arrays. So, it was no longer a Collection of Models, it was an array of arrays. There is no special serialization that takes place there, so the data went across exactly as you sent it.
The easiest way to get your order back is probably to override the restoreCollection method, so you can add in your order by clause to the restoration query. Add this method to your WantsheetToSuppliersMail class:
protected function restoreCollection($value)
{
if (! $value->class || count($value->id) === 0) {
return new EloquentCollection;
}
$model = new $value->class;
return $model->newQuery()->useWritePdo()
->whereIn($model->getKeyName(), $value->id)
->orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)
->get();
}
This is the same as the current function, just that your custom order by has been applied to the query.
it is a known bug of laravel 5.3
basically reretrieve the objects in the build function e.g.
public function build()
{
$this->wantsheet_products = WantsheetProduct::orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)->get();
$subject = 'abc';
return $this->from('me#myapp.com')->view('email.wantsheet.email_wantsheet_to_supplier', [])->subject($subject);
}

Laravel 4. Return database table as JSON

Im really new to Laravel. I have manage to set up a database via the migration functionality, and now i want to renturn a table from the database as json. What im working on is kind of a rest-api-thingy. Nothing too fancy.
In my router i have a route going to /api/cases wich inits the controller for the cases. From that controller i basically just want to return a table from my database as JSON.
Router:
Route::resource('/api/cases', 'CasesController');
Controller:
class CasesController extends \BaseController {
public function index()
{
//return db table as json here
}
}
Model:
class Case extends \Eloquent {
protected $fillable = [];
}
And my database looks like this:
I have only one table, named "cases". That one has attributes like "id", "name", "title".
How would i now return that rest-like as json?
You can simply call the toJSON() method:
Case::all()->toJson();
I assume you have your Case model tested and working properly. Once that's done, you can query for all the objects in this table, convert the result to an array, and encode it as JSON.
public function index()
{
return Response::json(Case::all()->toArray());
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
I often give talks about APIs and the dangers of trying to expose database schema directly. Unless you app is on an internal network, and only your app looks at this data, and your app will never going to change at all then interacting directly with the table is a very bad idea.
Here is my talk, which uses Laravel as an example a few times.

Zend Framework Model, Active Record pattern alternative

I'm writing some code that allows users to read reports on a site, using AJAX calls to dynamically load only what is requested, instead of the entire 15+MB report.
I'm writing a Model to access all the report data from the database, and I don't want to use the Active Record pattern. I'm following the idea of "A Model HAS a table, instead of IS-A table", since this model will be accessing 5 different tables, and there are some complex MySQL JOIN's between these tables.
What is a good design pattern to follow in Zend Framework for this, examples?
UPDATED on 2012-12-05 # 12:14PM EST
I'm currently working for a Market Research Report company. Without using actual function names, or revealing any meaningful details of the code, here are the basics:
readreportAction() does:
get the report meta data
get the report "table of contents"
readsectionAction() does:
get the report text, only a part of it
get the embedded tabular data
get the figures / images
get the footnotes
format the report text
reportpdfAction() does the exact same thing as readreportAction() and readsectionAction(), except all at one time. I'm trying to conceptualize a way to NOT copy + paste this code / programming logic. A data mapper seems to solve this.
I would recommend the Data Mapper pattern.
Everything you said makes sense and this pattern fits. Your model should not know or care how it is persisted. Instead the mapper does what it suggests - maps your model to your database. One of the things I like about this approach is it encourages people to think about the model in terms of an object, not a relational database table, as often happens with active record patterns and table row gateways.
Your object, unless very simple, typically will not reflect the structure of a database table. This lets you write good objects and then worry about the persistence aspects afterward. Sometimes more manual in that your mapper will need to deal with the complex joins, probably requiring writing some code or SQL, but the end result is it does just what you want and nothing more. No magic or conventions required if you don't want to leverage them.
I've always though these articles do a good job of explaining some of the design patterns that can be used well in ZF: http://survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors#zfbook.implementing.the.domain.model.entries.and.authors.exploring.the.entry.data.mapper
UPDATE:
Well you mapper might extend from an interface similar to this:
<?php
interface Mapper_Interface
{
/**
* Sets the name of the entity object used by the mapper.
*/
public function setObjectClass($class);
/**
* Sets the name of the list class used by the mapper.
*/
public function setObjectListClass($listClass);
/**
* Get the name of the object class used by the mapper.
*
*/
public function getObjectClass();
/**
* Get the name of the object list class used by the mapper.
*
* #return string
*/
public function getObjectListClass();
/**
* Fetch one row.
*
* #param array $where Criteria for the selection.
* #param array [$order = array()] Optionally the order of results
* #return Object_Abstract
* #throws Mapper_Exception
*/
public function fetchRow($where, $order = array());
/**
* Fetch all records. If there is no underlying change in the persisted data this should
* return a consistant result.
*
* #param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
* #param string|array $order OPTIONAL An SQL ORDER clause.
* #param int $count OPTIONAL An SQL LIMIT count.
* #param int $offset OPTIONAL An SQL LIMIT offset.
* #return Object_List_Abstract
* #throws Mapper_Exception
*/
public function fetchAll($where = null, $order = null, $count = null, $offset = null);
/**
* Deletes one or more object.
*
* #param array|string $where Criteria for row deletion.
* #return integer $affectedRows
* #throws Mapper_Exception
*/
public function delete($where);
/**
* Saves a record. Either updates or inserts, as required.
*
* #param $object Object_Abstract
* #return integer $lastInsertId
* #throws Mapper_Exception
*/
public function save($object);
}
And you would interact with the mapper like:
$fooObjectMapper = new Foo_Mapper;
$fooObjectList = $fooObjectMapper->fetchAll();
var_dump($fooObjectList->first());
or
$fooObjectMapper = new Foo_Mapper;
$fooObject = $fooObject->fetch(array('id = ?' => 1));
$fooObject->setActive(false);
$fooObjectMapper->save($fooObject);
I usually write a mapper abstract for any 'PDO' enabled databases. One of the attributes of that concrete mapper is then the Zend_Db_Adapter to issue commands against. Makes for a flexible solution, easy to use mock data sources in testing.
First it looks like you need to make a little bit more of a conceptual leap. With the data mapper pattern it helps to think in terms of objects instead of database tables. I found these two articles helpful when I needed to make the leap.
http://phpmaster.com/building-a-domain-model/
http://phpmaster.com/integrating-the-data-mappers/
That being said ZF 1 has some very useful tools for building a data mapper/domain model.
The convention in ZF 1 is for each table you are working with to be accessible through the Zend_Db_Table api. The simplest way I've found is to just use the DbTable resource for each table. You could also use the Zend_Db::factory or new Zend_Db_Table('tableName') or any other method that appeals to you.
This example is based on a mp3 song track.
//in effect this is the database adapter for database table 'track', This is $tableGateway used later.
<?php
class Application_Model_DbTable_Track extends Zend_Db_Table_Abstract
{
//name of database table, required to be set if name of class does not match name of table
protected $_name = 'track';
//optional, column name of primary key
protected $_primary = 'id';
}
there are several ways to attach a table to the Db adapter and the Zend_Db_Table api, I just find this method simple to implement and it makes setting up a mapper simple as well.
The mapper class is the bridge between the data source and your object (domain entity). The mapper interacts with the api for Zend_Db_Table in this example.
A really important point to understand: when using classes that extend Zend_Db_Table_Abstract you have all the basic functionality of the Zend_Db component at your disposal. (find(),fetchall(), fetchRow(), select() ...)
<?php
class Music_Model_Mapper_Track extends Model_Mapper_Abstract
{
//the mapper to access the songs artist object
protected $artistMapper;
//the mapper to access to songs album object
protected $albumMapper;
/**
* accepts instance of Zend_Db_Table_Abstract
*
* #param Zend_Db_Table_Abstract $tableGateway
*/
public function __construct(Zend_Db_Table_Abstract $tableGateway = null)
{
//at this point I tend to hardcode $tablegateway but I don't have to
$tableGateway = new Application_Model_DbTable_Track();
parent::__construct($tableGateway);
//parent sets the $tablegateway variable and provides an abstract requirement
//for createEntity(), which is the point of this class
}
/**
* Creates concrete object of Music_Model_Track
*
* #param object $row
* #return Music_Model_Track
*/
public function createEntity($row)
{
$data = array(
'id' => $row->id,
'filename' => $row->filename,
'format' => $row->format,
'genre' => $row->genre,
'hash' => $row->hash,
'path' => $row->path,
'playtime' => $row->playtime,
'title' => $row->title,
'track_number' => $row->track_number,
'album' => $row->album_id,//foriegn key
'artist' => $row->artist_id//foriegn key
);
//instantiate new entity object
return new Music_Model_Track($data);
}
/**
* findById() is proxy for find() method and returns
* an entity object.
*
* #param type $id
* #return object Model_Entity_Abstract
*/
public function findById($id)
{
//instantiate the Zend_Db_Select object
$select = $this->getGateway()->select();
$select->where('id = ?', $id);
//retrieve one database table row
$row = $this->getGateway()->fetchRow($select);
//create one entity object Music_Model_Track
$entity = $this->createEntity($row);
//return one entity object Music_Model_Track
return $entity;
}
//truncated
}
All that has gone before is for the express purpose of building the following object:
<?php
class Music_Model_Track extends Model_Entity_Abstract
{
/**
* $id, __set, __get and toArray() are implemented in the parent
*/
protected $album;
protected $artist;
protected $filename;
protected $format;
protected $genre;
protected $hash;
protected $path;
protected $playtime;
protected $title;
protected $track_number;
//artist and album mappers
protected $albumMapper = null;
protected $artistMapper = null;
//these are the important accessors/mutators because they convert a foreign key
//in the database table to an entity object.
public function getAlbum()
{
//if the album object is already set, use it.
if(!is_null($this->album) && $this->album instanceof Music_Model_Album) {
return $this->album;
} else {
//else we make a new album object
if(!$this->albumMapper) {
$this->albumMapper = new Music_Model_Mapper_Album();
}
//This is the album object we get from the id in our reference array.
return $this->albumMapper->findById($this->getReferenceId('album'));
}
}
//same as above only with the artist object.
public function getArtist()
{
if(!is_null($this->artist) && $this->artist instanceof Music_Model_Artist) {
return $this->artist;
} else {
if(!$this->artistMapper) {
$this->artistMapper = new Music_Model_Mapper_Artist();
}
return $this->artistMapper->findById($this->getReferenceId('artist'));
}
}
//the setters record the foriegn keys recorded in the table row to an array,
//this allows the album and artist objects to be loaded only when needed.
public function setAlbum($album)
{
$this->setReferenceId('album', $album);
return $this;
}
public function setArtist($artist)
{
$this->setReferenceId('artist', $artist);
return $this;
}
//standard setter and getters truncated...
}
so when using the track object you would get album or artist info like:
//this would be used in a controller most likely.
$mapper = new Music_Model_Mapper_Track();
$track = $mapper->findById('1');
//all of the information contained in the album or artist object is
//available to the track object.
//echo album title, year or artist. This album object also contains the artist object
//so the artist object would be available in two ways.
echo $track->album->title; //or
echo $track->artist->name;
echo $track->album->artist->name;
echo $track->getAlbum()->getArtist()->getName();
So what you really need to decide is how you want to structure your application. What I see as obvious may not be an option you wish to implement. A lot of the answers to your questions depend on exactly how these resources are to be used.
I hope this helps you at least a little bit.
You could consider using Doctrine 2. It's an ORM that does not use the ActiveRecord pattern.
In Doctrine, your models (entities) are all just normal PHP objects with zero knowledge of the database. You use mapping (xml, yaml or annotations) to tell Doctrine how they appear in the database, and the Entity Manager and repositories are used as a gateway for persisting entities or doing other database actions.

Creating a Pagination class in PHP

I want to build a paginator class, but i'm not sure what the best method is. I see many different codes on the internet that confuses me.
My data is stored in a MySQL database. Do i have to give a query to my pagniator class so that it can retrieve the data out of MySQL for me (and of course it will automatically do some pagination calculations after that)? Or do i get all the data from a model first, then supply the returned array from that model to the paginator class?
In either way i'd probably have to do another query to get the "total" amount of records and pass that result to the pagninator class as a separate param.
Once i know how to get started then i know how to pick up the rest. I'm just not sure how to pass in the data to the paginator class and what kind of data.
Any idea how to build a good paginator class?
A well designed OO Paginator class should be independent of your data and your database.
It should take parameters like count, page, and per_page, and should return objects that can be used for Dependency Injection into a Model for generating queries with the appropriate LIMIT, or into a PaginationHelper class for rendering the appropriate HTML.
Example of what might be a good Paginator interface (given 10 minutes of thought in this):
/**
* Your pagination master class
*/
interface iPaginator {
public function __construct($total_count, $count_per_page, $current_page=1);
public function getPaginationLimiter();
public function getPaginationHelper();
// These return iPaginatorPage objects
public function getCurrentPage();
public function getNextPage();
public function getPreviousPage();
public function getTotalCount();
public function getCountPerPage();
public function getPageCount(); // Calculated
}
/**
* Page representation
*/
interface iPaginatorPage {
public function __construct($page_number, $start, $end);
public function getNumber();
public function getStart();
public function getEnd();
public function getCount(); // Calculated
}
/**
* Helper for rendering the UI
*/
interface iPaginationHelper {
public function __construct(iPaginator $paginator);
public function render();
}
Example of how you could integrate into your model, by extending your base model, and then having your application models extend PaginatorModel instead of Model:
class PaginationModel extends Model {
public function query($sql, iPaginatorPage $page = null) {
if (!empty($page)) {
$start = $page->getStart();
$length = $page->getCount();
$sql .= " LIMIT ({$start}, {$length})";
return parent::query($sql);
}
}
}
Check the pager implementation from Pear. It's a very nice class, simple to use and with a lots of cool features.
create class, and have function like this:
function resultset($required_filters, $table, $pagenum, $records){
$getCount='SELECT count(*) from table';
$sql="SELECT ".$required fields." FROM ".$table." LIMIT ".(($pagenum-1) * $records).",".$records;
$sqlres=...
return array($getCount, $sqlres); }
call this function as list($rescount, $arrData)=resultset(all
parameters);
and for pagination use some for loop to display page numbers...for total $rescount pages,
using get you can show current pagenum highlighted

Categories