I'm doing a join between two tables using the doctrine that comes bundled in the current symfony release. This is my controller code:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\GearDBBundle\Entity\TbGear;
use Acme\GearDBBundle\Entity\TbDships;
class DefaultController extends Controller
{
public function indexAction()
{
$repository = $this->getDoctrine()
->getRepository('AcmeGearDBBundle:TbGear');
$query = $repository->createQueryBuilder('p')
->select('p', 'q')
->innerJoin('p.fkShip', 'q', 'WITH', 'p.fkShip = q.id')
->getQuery();
$result = $query->getResult();
foreach ( $result as $p ) {
$gear[] = array('shortname' => $p->getGearShortName(), 'name' => $p->getGearName(), 'shipname' => $p->getShipName /* Does not work, since the getter is in a different entity */);
}
return $this->render('AcmeGearDBBundle::index.html.twig', array('gear' => $gear));
}
}
The query generated by this is correct and delivers the expected fields if I execute it in phpmyadmin.
SELECT t0_.GEAR_NAME AS GEAR_NAME0, t0_.GEAR_SHORT_NAME AS GEAR_SHORT_NAME1, t0_.STATUS AS STATUS2, t0_.ID AS ID3, t1_.SHIP_NAME AS SHIP_NAME4, t1_.CONTACT_NAME AS CONTACT_NAME5, t1_.CONTACT_EMAIL AS CONTACT_EMAIL6, t1_.ID AS ID7, t0_.FK_SHIP_ID AS FK_SHIP_ID8, t0_.FK_TYPE AS FK_TYPE9
FROM tb_gear t0_
INNER JOIN tb_dships t1_ ON t0_.FK_SHIP_ID = t1_.ID
AND (t0_.FK_SHIP_ID = t1_.ID)
However, I have no clue how do access those fields in the returned result set. The way I expected it to work ( by accessing the getter of the joined table entity ) does not work. The error message reads: FatalErrorException: Error: Call to undefined method Acme\GearDBBundle\Entity\TbGear::getShipName() in /var/www/symfony/src/Acme/GearDBBundle/Controller/DefaultController.php line 24
which makes sense since the TbGear entity doesn't have a getter method called getShipName() , since that's a method from the joined entity. But how do I access those values? This probably is a stupid question, but I just can't figure it out. Any help is appreciated.
$p->getFkShip()->getShipName() maybe?
This should work since it will retrieve only TbGear that satisfies you relationship. So you could be able to access to all FkShip (I suppose that is a many-to-one relation) that should be only one, and then .... you got it!
EDIT
Of course I suppose that you have correctly designed your class so that you have a getter from TbGear to access the relation with FkShip
Can you add that custom getter: getShipName()?
public function getShipName(){
if ( $this->ship != null ){
return $this->ship->getName();
}
return null; // or an empty string
}
Related
There is some basic understanding/theory here that I am missing.I don't understand the difference between these function calls:
$distributors = $store->distributors();
$distributors = $store->distributors;
$distributors = $store->distributors()->get();
$distributors = $store->distributors->get();
What I am trying to accomplis here is to get a list of the distributors for a store (a many to many relationship), and they get each distributors list of beers into one giant list.
foreach ($distributors as $distributor)
{
$available_beers = array_merge($distributor->beers(), $available_beers);
}
I don't know if that is the best way to do this and I can't get it to work. Similar to the first list of methods, I don't know if I need ->$beers or ->$beers()
Update
Thanks to everyone who answered! This will be a good reference for me going forward. My biggest lesson was the difference between getting a collection back, vs getting the query builder/relationship object back. For future reference to those who find this question, here is what I set up in my controller:
$store = $this->store->find($id)->first();
$distributors = $store->distributors;
$beers = [];
foreach ($distributors as $distributor){
$beers = array_merge($distributor->beers->lists('name', 'id'), $beers);
}
Short answer
$model->relation() returns the relationship object
$model->relation returns the result of the relationship
Long answer
$model->relation() can be explained pretty simple. You're calling the actual function you defined your relation with. Yours for distributor probably looks somewhat like this:
public function distributors(){
return $this->hasMany('Distributor');
}
So when calling $store->distributors() you just get the return value of $this->hasMany('Distributor') which is an instance of Illuminate\Database\Eloquent\Relations\HasMany
When do you use it?
You usually would call the relationship function if you want to further specify the query before you run it. For example add a where statement:
$distributors = $store->distributors()->where('priority', '>', 4)->get();
Of course you can also just do this: $store->distributors()->get() but that has the same result as $store->distributors.
Which brings me to the explanation of the dynamic relationship property.
Laravel does some things under the hood to allow you to directly access the results of a relationship as property. Like: $model->relation.
Here's what happens in Illuminate\Database\Eloquent\Model
1) The properties don't actually exist. So if you access $store->distributors the call will be proxied to __get()
2) This method then calls getAttribute with the property name getAttribute('distributors')
public function __get($key)
{
return $this->getAttribute($key);
}
3) In getAttribute it checks if the relationship is already loaded (exists in relations). If not and if a relationship method exists it will load the relation (getRelationshipFromMethod)
public function getAttribute($key)
{
// code omitted for brevity
if (array_key_exists($key, $this->relations))
{
return $this->relations[$key];
}
$camelKey = camel_case($key);
if (method_exists($this, $camelKey))
{
return $this->getRelationshipFromMethod($key, $camelKey);
}
}
4) In the end Laravel calls getResults() on the relation which then results in a get() on the query builder instance. (And that gives the same result as $model->relation()->get().
The direct answer to your question:
$store->distributors() will return the actual relationship object (\Illuminate\Database\Eloquent\Relations\BelongsToMany).
$store->distributors will be a collection containing the results of the relationship query (\Illuminate\Database\Eloquent\Collection).
$store->distributors()->get() will be a collection containing the results of the relationship query (\Illuminate\Database\Eloquent\Collection).
$store->distributors->get() should return an error since you're calling get() on a Collection object and the first parameter is not optional. If not an error, it should at least return null.
More information:
Given the following model:
class Store extends Eloquent {
public function distributors() {
return $this->belongsToMany('Distributor');
}
}
Calling the relationship method ($store->distributors()) will return to you the relationship (\Illuminate\Database\Eloquent\Relations\BelongsToMany) object. This is basically a query object which you can continue to modify, but you still need to call some type of method to get the results (e.g. get(), first(), etc).
However, accessing the relationship attribute ($store->distributors) will return to you a collection (\Illuminate\Database\Eloquent\Collection) object containing the results from executing the relationship query.
By default, the relationship attribute is created and assigned a value the first time it is accessed (known as "lazy loading"). So, the first time you access $store->distributors, behind the scenes it is executing the relationship query, storing the results in the $store->distributors attribute, and then returning those results. However, it only does this once. The next time you access $store->distributors, the attribute already contains the data, so that is what you are accessing.
To illustrate this:
// the following two statements will run the query twice
$r1 = $store->distributors()->get();
$r2 = $store->distributors()->get();
// the following two statements will run the query once.
// the first statement runs the query, populates $store->distributors, and assigns the variable
// the second statement just accesses the data now stored in $store->distributors
$r3 = $store->distributors;
$r4 = $store->distributors;
// at the end, $r1 == $r2 == $r3 == $r4
Relationships can also be "eager" loaded, using the with() method on the query. This is done to alleviate all of the extra queries that may be needed for lazy loading (known as the n+1 problem). You can read more about that here.
When you work with relationships with Eloquent the property is a collection (Illuminate\Database\Eloquent\Collection) of your relation white the method is a start of a new query.
Say your model looks like this:
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
If you try to access $user->roles, Eloquent will run the query and fetch all roles related to that user thanks to magic methods and returns an instance of Illuminate\Database\Eloquent\Collection. That class has a method called get, that's why $user->roles->get() works for you.
If you try to access the method, $user->roles(), you will instead get a query builder object so you can fine tune your query.
$user->roles()->whereIn('role_id', [1, 3, 4])->get();
That would only return roles where role_id is 1, 3 or 4.
So, the property returns a complete query and it results (Illuminate\Database\Eloquent\Collection) while the method lets you customize your query.
$distributors = $store->distributors();
Result of a method (function)
$distributors = $store->distributors;
Value of property (variable)
$distributors = $store->distributors()->get();
Take the first one, where it's the result of a method, if the method returns an object, this is a method in that object that was returned.
$distributors = $store->distributors->get();
If the property is an object, then it's calling a method in that property that's an object.
Re ->$beers vs ->$beers() that's a dynamic name of a property/method depending on what you're for. Just make a really rough guess at what you're doing, in your class you're going to have
$this->beers = array('bud','miller','sam');
and in your code using the $store object, you're actually going to go something like
$drink_type = 'beers';
$drink_list = $store->$drink_type;
And that will return $this->beers from $store, the same as writing $store->beers;
Imagine that the store class looks like this:
<?php
class Store {
public $distributors;
function __construct($distributors = array()) {
$this->distributors = $distributors;
}
public function distributors() {
return $this->distributors;
}
}
So the difference is:
$store = new Store(array('some guy', 'some other guy'));
$guys = $store->distributors; # accesing the $distributors property
$more = $store->distributors(); # calling the distributors() method.
The main difference is:
$distributors = $store->distributors() return instance of the relationship object like Illuminate\Database\Eloquent\Relations\BelongsToMany. You can use other conditions such as where after call this.
$store->distributors return instance of the collection Illuminate/Database/Eloquent/Collection. Laravel call the magic method __get under the hood. It will return a result of query relationship.
Maybe this will be usefull.
Access to method:
$object->method();
Access to property:
$object->property;
I'd like to map an entity of a form by myself. The thing is I'm working with 36 000 cities in a database and Doctrine doesn't return any result when I'm performing a request using findBy. But I set this up by writting my owns methods.
The problem is in a form I need to ask for a city through an entity field (because there are a lot of data, I'm using select2 with remote's data). So far, no problem but when I'm submiting the form, Symfony can't bind the city's id to a database entry because of the none result of the classic method of Doctrine.
So, my question is : How can I tell Symfony to use my repository's method instead of the Doctrine's one to bind my data?
Thank you very much ! And have a good day ;)
The method findBy() request an array parameter sometimes people miss that:
$result = $this->getDoctrine()->getManager()
->getRepository("AcmeDemoBundle")->findBy(array(
"city" => $city)
);
If you want to use repository you just need to map it to your class:
/**
* #ORM\Entity(repositoryClass="Acme/DemoBundle/Repository/CountryRepository")
*/
class Country
{ ... }
Then in
class CountryRepository extends EntityRepository
{
public function getMySpecificCity($city)
{
$qb = $this->createQueryBuilder('c');
$cities = $qb->select(*)
->where("c.city =:city ")->setParameter('city', $city)
->getQuery()
->getResult();
return $cities;
}
...
}
So you can use it as follow:
$result = $this->getDoctrine()->getManager()
->getRepository("AcmeDemoBundle")->getMySpecificCity($city);
I have a PHP ActiveRecord model in which I have a function that requires the number of rows a query will return. I obtain the number of rows using the built in static::count($conditions) function. This works well and good but the issue arises when I include a GROUP BY statement. When I include this the count returns 1. I examined the resulting SQL and it was similar to
SELECT COUNT(*)
FROM TABLE
/* JOINS */
/* WHERE CONDITIONS */
GROUP BY `field`
When I ran the query manually I get
1
1
1
.
.
.
1
(1,000 times since there are 1,000 rows in the DB)
When I remove the GROUP BY statement, I get the value 1,000 like I should.
Obviously this occurs since COUNT is an aggregate function and it doesn't play well with group by. So with that being said, how can I return the correct number of rows using activerecord with a group by?
I had the same problem. I followed the example set by #jvenema in this question, wherein one defines a BaseModel class to override default ActiveRecord\Model behavior. Your models will then extend the BaseModel class.
class BaseModel extends ActiveRecord\Model
{
public static function count(/* ... */)
{
$args = func_get_args();
$options = static::extract_and_validate_options($args);
// Call the original function if $options['group'] is undefined
if ( !array_key_exists('group', $options) )
return call_user_func_array( 'parent::count', func_get_args() );
// This might fail if the table has a `counts` column
$options['select'] = 'COUNT(*) as counts';
if (!empty($args) && !is_null($args[0]) && !empty($args[0]))
{
if (is_hash($args[0]))
$options['conditions'] = $args[0];
else
$options['conditions'] = call_user_func_array('static::pk_conditions',$args);
}
$table = static::table();
$sql = $table->options_to_sql($options);
$values = $sql->get_where_values();
// Again, this might fail if there is a table named `tmp`
$wrapper = "SELECT COUNT(counts) FROM ({$sql->to_s()}) as tmp";
// Casting to (int) is optional; remove if it causes problems
return (int) static::connection()->query_and_fetch_one($wrapper,$values);
}
}
This function will fire only if $options['group'] is set. Additionally, note that this executes a COUNT() of rows created by GROUP BY rather than a SUM(). This is meant to account for cases when $has_many and $options['joins'] are in play, so as to prevent double-counting when INNER JOIN returns multiple results for an association.
Whenever I add additional logic to Eloquent models, I end up having to make it a static method (i.e. less than ideal) in order to call it from the model's facade. I've tried searching a lot on how to do this the proper way and pretty much all results talk about creating methods that return portions of a Query Builder interface. I'm trying to figure out how to add methods that can return anything and be called using the model's facade.
For example, lets say I have a model called Car and want to get them all:
$cars = Car::all();
Great, except for now, let's say I want to sort the result into a multidimensional array by make so my result may look like this:
$cars = array(
'Ford' => array(
'F-150' => '...',
'Escape' => '...',
),
'Honda' => array(
'Accord' => '...',
'Civic' => '...',
),
);
Taking that theoretical example, I am tempted to create a method that can be called like:
$cars = Car::getAllSortedByMake();
For a moment, lets forget the terrible method name and the fact that it is tightly coupled to the data structure. If I make a method like this in the model:
public function getAllSortedByMake()
{
// Process and return resulting array
return array('...');
}
And finally call it in my controller, I will get this Exception thrown:
Non-static method Car::getAllSortedByMake() should not be called statically, assuming $this from incompatible context
TL;DR: How can I add custom functionality that makes sense to be in the model without making it a static method and call it using the model's facade?
Edit:
This is a theoretical example. Perhaps a rephrase of the question would make more sense. Why are certain non-static methods such as all() or which() available on the facade of an Eloquent model, but not additional methods added into the model? This means that the __call magic method is being used, but how can I make it recognize my own functions in the model?
Probably a better example over the "sorting" is if I needed to run an calculation or algorithm on a piece of data:
$validSPG = Chemical::isValidSpecificGravity(-1.43);
To me, it makes sense for something like that to be in the model as it is domain specific.
My question is at more of a fundamental level such as why is all()
accessible via the facade?
If you look at the Laravel Core - all() is actually a static function
public static function all($columns = array('*'))
You have two options:
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
or
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Both will allow you to do
Car::getAllSortedByMake();
Actually you can extend Eloquent Builder and put custom methods there.
Steps to extend builder :
1.Create custom builder
<?php
namespace App;
class CustomBuilder extends \Illuminate\Database\Eloquent\Builder
{
public function test()
{
$this->where(['id' => 1]);
return $this;
}
}
2.Add this method to your base model :
public function newEloquentBuilder($query)
{
return new CustomBuilder($query);
}
3.Run query with methods inside your custom builder :
User::where('first_name', 'like', 'a')
->test()
->get();
for above code generated mysql query will be :
select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null
PS:
First Laurence example is code more suitable for you repository not for model, but also you can't pipe more methods with this approach :
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
Second Laurence example is event worst.
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Many people suggest using scopes for extend laravel builder but that is actually bad solution because scopes are isolated by eloquent builder and you won't get the same query with same commands inside vs outside scope. I proposed PR for change whether scopes should be isolated but Taylor ignored me.
More explanation :
For example if you have scopes like this one :
public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and')
{
$builder->where($column, $operator, $value, $boolean);
}
and two eloquent queries :
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->where('first_name', 'like', 'b');
})->get();
vs
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->whereTest('first_name', 'like', 'b');
})->get();
Generated queries would be :
select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null
vs
select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null
on first sight queries look the same but there are not. For this simple query maybe it does not matter but for complicated queries it does, so please don't use scopes for extending builder :)
for better dynamic code, rather than using Model class name "Car",
just use "static" or "self"
public static function getAllSortedByMake()
{
//to return "Illuminate\Database\Query\Builder" class object you can add another where as you want
return static::where('...');
//or return already as collection object
return static::where('...')->get();
}
Laravel model custom methods -> best way is using traits
Step #1: Create a trait
Step #2: Add the trait to model
Step #3: Use the method
User::first()->confirmEmailNow()
app/Model/User.php
use App\Traits\EmailConfirmation;
class User extends Authenticatable
{
use EmailConfirmation;
//...
}
app/Traits/EmailConfirmation.php
<?php
namespace App\Traits;
trait EmailConfirmation
{
/**
* Set email_verified_at to now and save.
*
*/
public function confirmEmailNow()
{
$this->email_verified_at = now();
$this->save();
return $this;
}
}
I have a superclass LibraryCommon and 4 subtypes: Library, PurchasedLibrary, PurchasableLibrary and PersonalLibrary.
Library is a simple extension of LibraryCommon with no additional fields, PurchasedLibrary has a user entity. In one instance, I want all Libraries and all PurchasedLibraries that belong to a certain user.
So I created a method in the LibraryCommonRepository:
public function findLibraries($user)
{
return $this->createQueryBuilder('l')
->where('l INSTANCE OF Library')
->orWhere('l INSTANCE OF PurchasedLibrary AND l.user = :user')
->setParameter(':user', $user)
->getQuery()
->getResult()
;
}
However, this errors out as [Semantical Error] line 0, col 182 near 'user = :user': Error: Class LibraryCommon has no field or association named user.
Am I missing something, or do I really need to join two seperate queries to get the result I want?
Additionally, if I do not provide the user and do like this:
public function findLibraries()
{
return $this->createQueryBuilder('l')
->where('l INSTANCE OF Library OR l INSTANCE OF PurchasedLibrary')
->getQuery()
->getResult()
;
}
The generated query looks this:
SELECT
...
FROM
library l0_
WHERE
(
l0_.type IN ('library')
OR l0_.type IN ('purchased')
)
AND l0_.type IN (
'library', 'personal', 'purchased',
'purchasable'
)
Is there any way to make the query just do WHERE l0_.type IN ('library', 'purchased')
You might want to add the User mapping to your LibraryCommon, just the private attribute, default to null, and keep getter and setter in your PurchasedLibrary.