I'm trying to figure out how to find a group of objects based on a layered relationship. I have 3 entities like so:
Referral --> manyToOne --> Patient --> manyToOne --> Payor
How do I find all referrals a given payor?
I'm using symfony3 with mysql and doctrine. My entities:
class Referral
{
// usual stuff
/**
* #ORM\ManyToOne(targetEntity="Patient")
*/
private $patient;
}
class Patient
{
// usual stuff
/**
* #ORM\ManyToOne(targetEntity="Payor")
*/
private $payor;
}
class Payor
{
// usual stuff
}
Obviously I could make the relationships birectional, for example so I could do something like this in my controller:
$patients = $payor->getPatients();
foreach ($patients as $patient) {
$referrals = $patient->getReferrals();
}
And then collect these into an appropriate array, but this seems messy and I'd rather do it all in a single database query in my repository. Can that be done?
you can find all referrals for a given payor using a query.
in ReferralRepository
public function findReferralsByPayor(Payor $payor)
{
$qb = $this->createQueryBuilder('r');
$qb
->join('BUNDLENAME:Patient', 'p', 'WITH', 'p.id = r.patient')
->where('p.payor = :payor')->setParameter('payor', $payor)
;
return $qb->getQuery()->getResult();
}
Related
I Have three tables
#1 Table timeline which is my reference table with an Auto incremented ID which is stored in column id
#2 timeline_videos
#3 timeline_else
What happens is on post if a video is uploaded with the post
it will go into Table #2 ,anything else goes into Table #3.
#2-3 have the Auto Increment Id from the Table timeline stored in a column pid
On query of The Timeline I need to join both tables data using id=pid
so I can use the rest of the Relational Data with the post.
I have done a bit of research and can't seem to find a method for doing so.
So far the code I have
Controller
$groupposts = timeline::where([
['owner','=',$owner],['id','<',$lastid],
])
->join('timeline_videos','timeline.id','=','timeline_videos.pid')
//->join('timeline_else','timeline.id','=','timeline_else.pid')
->orderBy('id','desc')
->limit(5)
->get();
This works with no errors with the second Join commented out but I need to also grab the timeline_else data .
Update --
I have now decided to use Eloquent Relationships to join the tables,
my question now is what type of relationship do I have between the
tables?
I realize it basically needs to be able to switch between two tables to
grab data based on the fact that timeline_videos and timeline_else will not be "JOIN" but separated by type .
The tables need to Join with table #1 timeline based on a column I now have named type for clarifying where to look and matching/joining using the id = pid
You can use relationships.
it sounds like timelines has many videos and has many video failures
https://laravel.com/docs/5.5/eloquent-relationships#one-to-many
you would have a model for each table and set up the relationships
timelines model:
public function videos()
{
return $this-> hasMany('App\Videos');
}
public function videoFailures()
{
return $this-> hasMany('App\videoFailures');
}
videos model:
public function timeline()
{
return $this->belongsTo('App\Timelines');
}
videos failures model:
public function timeline()
{
return $this->belongsTo('App\Timelines');
}
You can then go:
$timeLine = Timmeline::find($id);
to find videos of the time lines you would do:
$videos = $timeLine->videos();
to find else:
$videoElse = $timeLine-> videoFailures();
By using some of what Parker Dell supplied and a bit more trial and error
My Models Looks like
timeline
class timeline extends Model
{
protected $table ='timeline';
public $timestamps = false;
public function videos()
{
return $this->hasMany('App\timeline_videos','pid','id');
}
public function else()
{
return $this->hasMany('App\timeline_ect','pid','id');
}
}
timeline_ect.php ,I changed the name of the table
class timeline_ect extends Model
{
protected $table='timeline_ect';
public $timestamps = false;
public function timeline()
{
return $this->belongsTo('App\Models\timeline','pid','id');
}
}
timeline_videos
class timeline_videos extends Model
{
protected $table='timeline_videos';
public $timestamps = false;
public function timeline()
{
return $this->belongsTo('App\timeline','id','pid');
}
}
Then Lastly my Controller
$timeline = timeline::with('videos','else')
->orderBy('id','desc')
->limit(5)
->get();
So far no Problem query is correct.
I've created a concept of a Person model having many e-mails. I also have a Company model that can share the same e-mails with People (Person model).
In short:
People OneToMany PeopleEmail ManyToOne Email (classic manytomany unidirectional with a join table)
people { id, firstname, lastname, created }
people_emails { person_id, email_id, main, created }
emails { id, email, created }
as well as:
companies { id, name, employee_count, created }
companies_emails { company_id, email_id, main, created }
emails { id, email, created }
The problem is, that I'd like to store a boolean value called "main" in the join table as follows:
person_id | email_id | main | created
8 | 5 | true | 2014-10-21 16:54:21
...so that I can do this:
Mark Wilson (Person) has 5 emails.
3 of them are his company e-mails (shared): contact#company.com, office#company.com, it#company.com
2 of them are his own.
1 he answers only in his leisure time
1 is his MAIN email: i.e. m.wilson#company.com
Instead of fetching all those 5 emails, I'd like to easily get the primary email just as if it was a regular Person model column:
firstname: Mark (string)
lastname: Wilson (string)
emails: (array)
primary_email: (email object)
I cannot store the "main" property anywhere else, as I want to point, that the relation between Mark and his Email is "main", not the email itself.
Now, I do have this value stored, but the problem is, how to make an entity property like:
class Person {
(...)
/**
* #ORM\ManyToMany(targetEntity="Email")
* #ORM\JoinTable(name="people_emails",
* joinColumns={#ORM\JoinColumn(name="person_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="email_id", referencedColumnName="id", unique=true)}
* )
*/
private $primary_email;
/**
* #ORM\ManyToMany(targetEntity="Email")
* #ORM\JoinTable(name="people_emails",
* joinColumns={#ORM\JoinColumn(name="person_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="email_id", referencedColumnName="id", unique=true)} // A condition?
* )
*/
private $emails;
public function getPrimaryEmail() {
return $this->primary_email; // Single row limit?
}
public function getEmails() {
return $this->emails;
}
(...)
}
The thing is, I would really love to have it as an entity property, just for use in any case possible without the need to write custom repostory functions for the whole Person model.
Or maybe that is a completely wrong way. I'd like to use that property in Twig like:
{{person.primary_email.email}}
To sum up:
I'd like to store a ManyToMany single row relationship depending on joinTable column.
How to do that?
There are many ways to do this and there are also many critical things to say about your design choices. Either way here is an example of one way you could achieve this using two join tables (you need two if you want to use foreign keys).
<?php
interface AddressLink
{
public function getEmail();
public function isMain();
}
trait EmailAddressOwner
{
protected abstract function getAddressLinks();
public function getPrimaryEmailAddress()
{
return $this->getAddressLinks()->filter(function (AddressLink $addressLink) {
return $addressLink->isMain();
})->first();
}
public function getEmailAddresses()
{
return $this->getAddressLinks()->map(function (AddressLink $addressLink) {
return $addressLink->getEmail();
});
}
}
class PersonEmailAddress implements AddressLink
{
private $person; // ManyToOne
private $email; // ManyToOne
private $main; // bool
}
class CompanyEmailAddress implements AddressLink
{
private $company; // ManyToOne
private $email; // ManyToOne
private $main; // bool
}
class Email
{
private $id;
private $address;
}
class Person
{
/**
* #ORM\OneToMany(targetEntity="PersonEmailAddress")
*/
private $emailAddressLinks;
use EmailAddressOwner;
public function getAddressLinks()
{
return $this->emailAddressLinks;
}
}
class Company
{
/**
* #ORM\OneToMany(targetEntity="CompanyEmailAddress")
*/
private $emailAddressLinks;
use EmailAddressOwner;
public function getAddressLinks()
{
return $this->emailAddressLinks;
}
}
Another way would be to include one ManyToMany relation to your Email entity and one ManyToOne relation for the primary e-mail address.
To answer you question in the comments if in twig you do
{{ person.primaryEmail.email }}
It wil actually call the getPrimaryEmail() method on your Person object. Which you can implement like I've outlined above. That way you don't need to have this extra property.
I have the following tables:
content - id (PK), title, ... other fields ...
content_category - content_id (FK to content), category_id (FK to content)
Where a piece of content has_many categories, and a category is also a piece of content.
In content I have the following code:
public function getCategories()
{
return $this
->hasMany(Category::className(), ['id' => 'category_id'])
->viaTable('content_category', ['content_id' => 'id']);
}
public function getCategoriesCsv(){
...
}
For my grid view in the backend, I'd like to display a comma separated list of categories for each piece of content.
I'm aware that I could select this information separately, but I would like to do it as part of the find query and using the existing relation if possible.
Using defined relation (more simple, less efficient).
This approach typical way and it works with related Category models. Therefore it requires a lot of memory.
class Content extends \yii\db\ActiveRecord
{
/**
* Returns comma separated list of category titles using specified separator.
*
* #param string $separator
*
* #return string
*/
public function getCategoriesCsv($separator = ', ')
{
$titles = \yii\helpers\ArrayHelper::getColumn($this->categories, 'title');
return implode($separator, $titles);
}
// ...
}
Should be used with eager loading:
Content::find()
->with('categories')
->all();
Using subquery (more efficient, less convenient)
This approach uses subqueries and don't use relations and related models. Therefore this way more fast and keeps a lot of memory.
class Content extends \yii\db\ActiveRecord
{
const ATTR_CATEGORIES_CSV = 'categoriesCsv';
/**
* #var string Comma separated list of category titles.
*/
public $categoriesCsv;
/**
* Returns DB expression for retrieving related category titles.
*
* #return \yii\db\Expression
*/
public function prepareRelatedCategoriesExpression()
{
// Build subquery that selects all category records related with current content row.
$queryRelatedCategories = Category::find()
->leftJoin('{{%content_category}}', '{{%content_category}}.[[category_id]] = {{%category}}.[[id]]')
->andWhere(new \yii\db\Expression('{{%content_category}}.[[content_id]] = {{%content}}.[[id]]'));
// Prepare subquery for retrieving only comma-separated titles
$queryRelatedCategories
->select(new \yii\db\Expression('GROUP_CONCAT( {{%category}}.[[title]] )'));
// Prepare expression with scalar value from subquery
$sqlRelatedCategories = $queryRelatedCategories->createCommand()->getRawSql();
return new \yii\db\Expression('(' . $sqlRelatedCategories . ')');
}
// ...
}
When alias of additional column equals to some model property, it will be populated by all() method:
$contentModels = Content::find()
->andSelect([
'*',
Content::ATTR_CATEGORIES_CSV => Content::prepareRelatedCategoriesExpression(),
])
->all();
foreach ($contentModels as $contentModel) {
$contentModel->id;
$contentModel->categoriesCsv; // it will be also populated by ->all() method.
// ...
}
ps: I not tested this code, probably should be fixed query for retrieving categories.
Moreover, in this example it written using base simple syntax, but it may be optimized to more cute state using various helpers, junction models etc.
WITH CATEGORIES LOADED
Originally I implemented it as:
public function getCategoriesCsv(){
$categoryTitles = [];
foreach ($this->categories as $category){
$categoryTitles[] = $category->title;
}
return implode(', ', $categoryTitles);
}
Thanks to #IStranger, I neatened this to:
public function getCategoriesCsv()
{
$titles = ArrayHelper::getColumn($this->categories, 'title');
return implode(', ', $titles);
}
WITHOUT CATEGORIES LOADED
I have now managed to avoid loading all the category models by adding a separate CategoryCsv ActiveRecord:
class CategoryCsv extends ActiveRecord
{
public static function tableName(){
return '{{%content_category}}';
}
public function attributes(){
return ['content_id', 'value'];
}
public static function find(){
return parent::find()
->select([
'content_id',
'GROUP_CONCAT(
categoryCsv.title
ORDER BY categoryCsv.title
SEPARATOR ", "
) value'
])
->innerJoin('content categoryCsv','category_id = categoryCsv.id')
->groupBy('content_id');
}
}
Then in the Content ActiveRecord:
public function getCategoriesCsv(){
return $this->hasOne(CategoryCsv::className(), ['content_id' => 'id']);
}
Thus I can access the value like so:
$contents = Content::find()->with('categoryCsv')->all();
foreach($contents as $content){
echo $content->categoryCsv->value;
}
I am dealing with the following situation: I have two models, an Employee with id and name fields and a Telephone with id, employee_id and flag fields. There is also an one-to-many relationship between these two models, that is an employee may have many telephones and a telephone may belong to a single employee.
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
}
The Employee model references a table employees that exists in database schema named mydb1, while the Telephone model is related to a telephones table that exists in a different database schema named mydb2.
What I want is to fetch only the employees with at least one telephone of a specific flag eager loaded, using Eloquent and (if possible) not the query builder
What I tried so far without success is:
1) use the whereHas method in the Controller
$employees = Employee::whereHas('telephones', function ($query) {
$query->where('flag', 1); //Fetch only the employees with telephones of flag=1
})->with([
'telephones' => function ($query) { //Eager load only the telephones of flag=1
$query->where('flag', 1);
}
])->get();
What I try to do here is first to retrieve only the employees that have telephones with flag=1 and second to eager load only these telephones, but I get the following query exception because of the different db connections used:
Base table or view not found: Table mydb1.telephones doesn't exist (this is true, telephones exists in mydb2)
2) Eager load with constrains in the Controller
$employees = Employee::with([
'telephones' => function ($query) {
$query->where('flag', 1);
},
])->get();
This method eager loads the telephones with flag=1, but it returns all the employee instances, which is not what I really want. I would like to have a collection of only the employee models that have telephones with flag = 1, excluding the models with telephones = []
Taking into account this post, this post and #Giedrius Kiršys answer below, I finally came up with a solution that fits my needs, using the following steps:
create a method that returns a Relation object in the Model
eager load this new relationship in the Controller
filtered out the telephones of flag != 1 using a query scope in the Model
In Employee model
/**
* This is the new relationship
*
*/
public function flaggedTelephones()
{
return $this->telephones()
->where('flag', 1); //this will return a relation object
}
/**
* This is the query scope that filters the flagged telephones
*
* This is the raw query performed:
* select * from mydb1.employees where exists (
* select * from mydb2.telephones
* where telephones.employee_id = employee.id
* and flag = 1);
*
*/
public function scopeHasFlaggedTelephones($query, $id)
{
return $query->whereExists(function ($query) use ($id) {
$query->select(DB::raw('*'))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
In the Controller
Now I may use this elegant syntax a’la Eloquent
$employees = Employee::with('flaggedTelephones')->hasFlaggedTelephones()->get();
which reads like "Fetch all the employees with flagged telephones eager loaded, and then take only the employees that have at least one flagged telephone"
EDIT:
After dealing with the Laravel framework for a while (current version used 5.2.39), I figured, that in fact, whereHas() clauses do work in case of the relationship model exists in a different database using the from() method, as it is depicted below:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2.telephones')->where('flag', 1);
})->get();
#Rob Contreras credits for stating the use of the from() method, however it looks like the method requires to take both the database and the table as an argument.
Not sure if this will work but you can use the from method to specify your database connection within the closure:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2')->where('flag', 1);
})->get();
Hope this helps
Dirty solution:
Use whereExists and scope for better readability.
In Your Employee model put:
public function scopeFlags($query, $flag)
{
$query->whereExists(function ($q) use ($flag) {
$q->select(\DB::raw(1))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
Then modify your query like so:
$employees = Employee::flags(1)->get();
I have an app that handles user info, and one of the pieces of data we collect is what school(s) they're attending. We have a User object, School object, and a UserSchool object.
This is the user_schools table:
user_id (int),school_id (int)
With the following records for instance:
100, 20
200, 500
200, 10
300, 10
I'm trying to get all schools for the current user (say user 200). This is my UserSchool object:
class UserSchool extends Model
{
var $table = 'user_schools';
function user() {
return $this->belongsTo('User');
}
function school() {
return$this->belongsTo('School');
}
}
In User I have the following relations defined:
public function schools()
{
return $this->hasManyThrough('School', 'UserSchool');
}
public function userSchools()
{
return $this->hasMany('UserSchool');
}
When I var_dump($user->schools) I get no results, even though I know it should be returning multiple Schools. What am I doing wrong? I'm sure this must have been asked before but I don't know what the actual term for this intermediate type of relationship is (bridge table? pivot table?).
Edit: Either way, most of the examples I've looked at haven't worked. I've also tried:
public function schools()
{
return $this->hasManyThrough('School', 'UserSchool', 'school_id');
}
In fact you don't need to have UserSchool object here for this relationship
For User model you can use create the following relationship:
public function schools()
{
return $this->belongsToMany(School::class, 'user_schools');
}
And now you can get schools of user using something like this:
$user = User::find(1);
foreach ($user->schools as $school)
{
echo $school->name;
}
This is standard many to many relationship