Doctrine getting relational entites by status flag - php

I have 3 tables users, groups, group_users (holds relation between group and user)
The table of group_users includes the following columns
-- (is_active 0: not active, 1: active)
| id | user_id | group_id | is_active |
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 0 |
In entity of Group I have a relational column like the following that gets group users using the relation in group_users table
/*
* #ORM\Entity()
* #ORM\Table(name="groups")
*/
class Group{
...
/**
* #var Collection|GroupUser[]
* #ORM\OneToMany(targetEntity="App\Group\Domain\Entity\GroupUser")
*/
private $group_users;
In entity of Group I want to get group users but only ones that are active. In normal condition the relation above gets all related entities.
If I need to give an example, according to above records in group_users table, when I call variable like $this->group_users I want to see that only first record is listed
| id | user_id | group_id | is_active |
| 1 | 1 | 1 | 1 |
not this
| id | user_id | group_id | is_active |
| 2 | 1 | 2 | 0 |
Do you have any idea what's the best way to handle this problem, thanks.
Note: I don't want to physically delete any record.

You can define a new getter in Group entity which will filter out the related group users as per your criteria
use Doctrine\Common\Collections\Criteria;
class Group{
//...
protected getActiveGroupUsers() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->eq("is_active", 1));
return $this->group_users->matching($criteria);
}
}
And when you query a group you can get the related active group users as
$group = $em->getRepository("...\Group")->find($id);
$group->getActiveGroupUsers();
How filter data inside entity object in Symfony 2 and Doctrine

Related

laravel recursion display referred users by level/depth

So I'm working with affiliate and doing fine with registration and saving who referred to a user,
now I'm struggling in showing those users with referrals by Level.
Level is not save in the database, I'm thinking of it as incrementing in the logic area?
users table structure
id | name | sponsor_id |
1 | John | NULL |
2 | Jane | 1 |
3 | Jess | 1 |
4 | Joe | 2 |
so the output I want should be like this:
I am John, level 1 are who's sponsor_id is mine, and level 2 are who's sponsor_id is id's of whom I invited.
ID | Name | Level |
2 | Jane | 1 |
3 | Jess | 1 |
4 | Joe | 2 |
where Jane & Jess's sponsor_id is mine, and Joe's sponsor_id is Jess
User has sponsor
public function sponsor()
{
return $this->belongsTo('App\User', 'sponsor_id');
}
User has referrals
public function referrals()
{
return $this->hasMany('App\User', 'sponsor_id');
}
If MySQL 8 (very advised with hierarchical data like yours), you can do this with a recursive CTE :
WITH RECURSIVE CTE_users_levels(id, name, level) AS
(
SELECT id, name, 0 as level
FROM users
WHERE sponsor_id IS NULL
UNION ALL
SELECT u.id, u.name, cte.level+1
FROM CTE_users_levels cte
JOIN users u ON cte.id=u.sponsor_id
)
-- To output all users' levels:
SELECT * FROM CTE_users_levels ORDER BY level;
Now you have virtual table containing the level column for all your users and you can query it
| id | name | level |
| --- | ---- | ----- |
| 1 | John | 0 |
| 2 | Jane | 1 |
| 3 | Jess | 1 |
| 4 | Joe | 2 |
DBFiddle
GOing further...
Make a view out of your CTE
CREATE VIEW VIEW_User_Levels AS
(
WITH RECURSIVE CTE_users_levels(id, name, level) AS
(
SELECT id, name, 0 as level
FROM users
WHERE sponsor_id IS NULL
UNION ALL
SELECT u.id, u.name, cte.level+1
FROM CTE_users_levels cte
JOIN users u ON cte.id=u.sponsor_id
)
SELECT * FROM CTE_users_levels ORDER BY level
)
Now it's easy to get all your users levels (no need to have the CTE statement) :
SELECT * FROM VIEW_User_Levels
Then, if you have a lot of users, it's a bit ovekilling to recompute the whole tree all the time (which the VIEW does)
Then you can fix the level of your users in a new column.
ALTER TABLE users ADD level INT;
And populate that column for all users with the help of your view:
UPDATE users u, VIEW_User_Levels v
SET u.level=v.level
WHERE u.id=v.id;
Which gives
SELECT * FROM users
id name sponsor_id level
1 John null 0
2 Jane 1 1
3 Jess 1 1
4 Joe 2 2
DBFiddle
If you have a lot of users, and your aim is to query the level column a lot, INDEX IT.
Note that you can also update the level value of only one user (for instance if its sponsor_id changes, or if the user is new)
UPDATE users u, VIEW_User_Levels v
SET u.level=v.level
WHERE u.id=v.id AND u.name='Jane';

laravel 5 - Eloquent Relationships (permissions table)

I think am struggling to get the relationships correctly in the scenario I have.
I have three tables
Table: users
| id | username |
----------------------
| 1 | pluto |
|------|-------------|
Table: permissions
| id | user_id | level_id | app_id |
--------------------------------------------------
| 1 | 1 | 2 | 9 |
|------|-------------|--------------|------------|
Table: levels
| id | level |
----------------------
| 1 | admin |
|------|-------------|
| 2 | manager |
|------|-------------|
| 3 | editor |
|------|-------------|
The result I am looking to get is
manager //through User:: model or Auth::
I would like to get a value from levels table in level column either through User model. This is the last version on what I have in my classes...
class User extends Authenticatable
{
public function permissions()
{
return $this->hasOne('App\Permissions');
}
}
class Permissions extends Model
{
public function user()
{
return $this->hasMany('App\User');
}
public function levels()
{
return $this->hasMany('App\Levels');
}
}
class Levels extends Model
{
public function permissions()
{
return $this->belongsTo('Modules\Ecosystem\Entities\permissions');
}
}
Using this in the controller I am able to retrieve values from permissions table. However I am unable to get values from levels table...
$user = User::with('permissions')->find(Auth::user()->id);
However I am unable to get values from levels table when I try this...
$user = User::with('permissions.levels')->find(Auth::user()->id);
That produces an error:
Column not found: 1054 Unknown column 'levels.permissions_id' in 'where clause' (SQL: select * from `levels` where `levels`.`permissions_id` in (1))
I understand that I am not understanding exactly how relationships would work in this instance but I don't want to just guess a solution. I'd like to understand it.
The thing is ,, Levels table serves only as a list of permission levels (roles). I recognize that I can define permission levels in some other way but for the moment this is how everything is set up.
If you are making a usual user permission system, where 'roles' is replaced by 'levels', then you need to reorganized your tables and relationships.
Table: users
| id | username | level_id |
---------------------------------------
| 1 | pluto | 2 |
|------|-------------|----------------|
Table: levels
| id | level |
----------------------
| 1 | admin |
|------|-------------|
| 2 | manager |
|------|-------------|
| 3 | editor |
|------|-------------|
Table: permissions
| id | level_id | app_id |
-----------------------------------
| 1 | 2 | 9 |
|------|-------------|------------|
So now User
hasOne('App\Levels', 'level_id');
Levels
hasMany('Modules\Ecosystem\Entities\permissions', 'level_id');
Permissions
belongsTo('App\Levels', 'level_id');
That is probably what you were trying to do, and it will works.
However, if many roles may have similar permissions, e.g: admin, manager & editor can all have access to a 'Page' or 'Content' or whatever that they all have access to, then you will need a 4th table to have many-to-many relationship between permissions and levels.
Table: permissions
| id | app_id |
---------------------
| 1 | 9 |
|------|------------|
Table: levels_permissions
| level_id | permission_id |
-----------------------------------
| 2 | 1 |
|-------------|-------------------|
With this, in Levels
belongsToMany('Modules\Ecosystem\Entities\permissions', 'levels_permissions', 'level_id', 'permission_id');
Inverse the relation in Permissions
belongsToMany('App\Levels', 'levels_permissions', 'permission_id', 'level_id');
In both approaches, you can now do
$user = User::with('levels.permissions')->find(Auth::user()->id);

How can I make a relation between tables in Laravel?

I have three tables like these:
// users
+----+-----------+
| id | user_name |
+----+-----------+
| 1 | Jack |
| 2 | Peter |
| 3 | John |
+----+-----------+
// skills
+----+------------+
| id | skill_name |
+----+------------+
| 1 | PHP |
| 2 | HTML |
| 3 | Laravel |
| 4 | MySQL |
| 5 | jQuery |
+----+------------+
// skill_user
+----------+---------+
| skill_id | user_id |
+----------+---------+
| 2 | 1 |
| 5 | 1 |
| 1 | 2 |
| 2 | 2 |
| 4 | 2 |
+----------+---------+
Now I want to get all skills of one specific user. I can do that by using a join clause in a raw sql query:
SELECT s.skill_name
FROM skills s
JOIN skill_user su ON s.id = su.skill_id
WHERE su.user_id = :user_id
/* output (assuming :user_id is 1)
+------------+
| HTML |
| jQuery |
+------------+
Ok All fine. Now I want to know, how can I do that by adding a method in the model of Laravel framework? All I'm trying to do is using belongsTo(), hasOne(), hasMany() etc ..
To do in Eloquent way, you need to add the relationship. In your case, it is many-to-many relationship so in User.php, add this function to declare the relationship:
public function skills() {
$this->belongsToMany(\App\Skill::class, 'skill_user', 'user_id', 'skill_id');
}
Then,
Access it like this to eager load it:
$users = \App\User::with('skills')->get();
OR
Say you have a single user,
$user = User::where('id', 1)->first(); // Get the user with id = 1
and to get the skill for that user:
$user->skills;
OR
If you don't want to do using Eloquent way, you can use Query Builder. Note that, using this method, you basically join it manually so you don;t need to add any method to the Model:
\DB::table('users')
->join('skill_user', 'skill_user.user_id','=', 'users.id')
->join('skills', 'skill_user.skill_id', '=', 'skills.id')
->select('*')
->where('users.id', 1)
->get();
You have to use belongsToMany relationships. Assuming you will create SkillUser model for skill_user table. Add the following code to User Model
public function skills() {
$this->belongsToMany('App\SkillUser', 'skill_user', 'user_id', 'skill_id');
}
Now you can access it by
$users->skills();
I assume you are talking about Many to Many relationship between Users and skills, then the example given in Laravel many-to-many speaks about it.

How to filter OneToMany relationship' entity based on specific field when querying main entity

So I have this basic blog that contains articles (AppBundle\Entity\Courrier) and comments (AppBundle\Entity\Reaction). These two entities are bound together through a OneToMany relationship. Here are their definitions:
Courrier.php
<?php
namespace AppBundle\Entity;
class Courrier
{
// ...
/**
*
* #ORM\OneToMany(targetEntity="Reaction", mappedBy="courrier")
*/
private $reactions;
Reaction.php
<?php
namespace AppBundle\Entity;
class Reaction
{
const STATUS_ACCEPTED = 0;
const STATUS_PENDING = 1;
const STATUS_MODERATED = 2;
const STATUS_TRASHED = 3;
// ...
/**
* Thread of this comment
*
* #var Courrier
* #ORM\ManyToOne(targetEntity="Courrier", inversedBy="reactions")
* #ORM\JoinColumn(name="courrier_id", referencedColumnName="id")
*/
private $courrier;
with
/**
* #var integer
*
* #ORM\Column(name="status", type="integer")
*/
private $status;
My problem is that when I'm querying AppBundle:Courrier repository, I want to filter AppBundle:Reaction based on $status. That means doing something like :
$courrier = $doctrine->getRepository('AppBundle:Courrier')->findOneWithReactionsFiltered($slugCourrier, Reaction::STATUS_ACCEPTED)
So I built this very repository method and here it goes:
public function findOneWithReactionsFiltered($slug, $status)
{
return $this->createQueryBuilder('c')
->leftJoin('c.reactions', 'r')
->where('c.slug = :slug')
->andWhere('r.status = :status')
->setParameters([
'slug' => $slug,
'status' => $status,
])
->getQuery()
->getOneOrNullResult()
;
}
But the return value of this method (that is $courrier) is filled with Courrier entity with its Reactions regardless of the $status.
I also dumped the SQL query :
SELECT c0_.id AS id0, c0_.name AS name1, c0_.slug AS slug2, c0_.envoi AS envoi3, c0_.intro AS intro4, c0_.courrier AS courrier5, c0_.reponse AS reponse6, c0_.published AS published7, c0_.like_count AS like_count8, c0_.recu AS recu9, c0_.image_id AS image_id10, c0_.categorie_id AS categorie_id11 FROM courrier c0_ LEFT JOIN reaction r1_ ON c0_.id = r1_.courrier_id WHERE c0_.slug = 'yaourt-cerise' AND r1_.status = 0;
Finally, here's a small dump of the table:
mysql> select r.id, r.status, c.slug from reaction as r left join courrier as c on c.id = r.courrier_id order by c.slug asc;
+-------+--------+------------------------------------+
| id | status | slug |
+-------+--------+------------------------------------+
| 15533 | 1 | yaourt-cerise |
| 15534 | 1 | yaourt-cerise |
| 15535 | 1 | yaourt-cerise |
| 15536 | 1 | yaourt-cerise |
| 15537 | 1 | yaourt-cerise |
| 15538 | 1 | yaourt-cerise |
| 15539 | 1 | yaourt-cerise |
| 15540 | 1 | yaourt-cerise |
| 15541 | 1 | yaourt-cerise |
| 15526 | 0 | yaourt-cerise |
| 15542 | 1 | yaourt-cerise |
| 15527 | 1 | yaourt-cerise |
| 15543 | 1 | yaourt-cerise |
| 15528 | 1 | yaourt-cerise |
| 15544 | 1 | yaourt-cerise |
| 15529 | 1 | yaourt-cerise |
| 15545 | 1 | yaourt-cerise |
| 15530 | 1 | yaourt-cerise |
| 15546 | 1 | yaourt-cerise |
| 15531 | 1 | yaourt-cerise |
+-------+--------+------------------------------------+
Can someone help?
It's the intended behavior. By querying the Courrier repository you'll get all entities which have at least one Reaction with the status you provide. Reactions are not immediately joined, but the Doctrine proxy you get from the repository fetches all of them as soon as you access its $reaction ArrayCollection.
You can get what you want in two ways:
Use the Reaction repository and execute the query there, maybe joining the Courrier so you don't have to query again for it;
Create a method in Courrier entity to filter collection results based on a criteria. It could be something like that:
public function getReactionsWithStatus($status)
{
return $this->reactions->matching(
Criteria::create()->where(Criteria::expr()->eq('status', $status));
)->toArray();
}
NOTE: below is the outdated reply since the question evolved
When replacing parameters, you should pass them without the colons. Change
->setParameter(':courrier', $value)
to
->setParameter('courrier', $value)
and you should be good to go.
Do not forget to do the same thing for reaction.
You need to select both entities to hydrate then into the collection on query.
E.g. you need to add another select for the r alias:
->addSelect('r')
Then your collection will be correctly loaded and filtered from your query.
For more info see my answer below:
Filtering of associated entity collection in Symfony / Doctrine

Using nested queries and many to many relationships in Doctrine's QueryBuilder

So I'm having a bit of trouble thinking of how to approach this using a query builder. Currently, I have three objects that are the following:
HelpRequest
id
...
status
Filter
id
name
statuses -> ManyToMany(targetEntity="Status")
Status
id
name
A filter can have multiple statuses so there is a table that is keeping track what statuses are part of a specific filter.
Sample Data
help_requests
---
| id | content | status |
| 1 | hello | 3 |
filters
---
| id | name |
| 1 | Active |
| 1 | Inactive |
statuses
---
| id | name |
| 1 | Open |
| 2 | Closed |
| 3 | Waiting User Response |
status_filter
---
| status_id | filter_id |
| 1 | 1 |
| 3 | 1 |
| 2 | 2 |
The status_filter table is automatically generated from a ManyToMany relationship in doctrine between a Status object and a Filter object.
Based on the given information, I've written this SQL query but now I'm having troubles writing this with QueryBuilder.
SELECT * FROM help_requests WHERE status IN (SELECT status_id FROM status_filter WHERE filter_id = 1)
If there's any more information I can give, let me know. I've read multiple questions on SO and have tried a number of things but I can't seem to get it right. I'm aware I could just hard coded that query but I'd like the experience using QueryBuilder
Thanks for the help!
Update
In the end, since I couldn't get it to work with QueryBuilder and I didn't want to create a new entity solely to map two other entities together, I decided to use createQuery() instead and this is what I came up with:
SELECT
hr
FROM
HelpRequest hr
WHERE
hr.status
IN (
SELECT
s.id
FROM
Filter f
JOIN
f.statuses s
WHERE
f.name = :name
)
Thank you everyone for the help.
Try this query, and put is in your HelpRequestsRepository class:
$subquery = $this->->select('st.status_id')
->from('/path/to/StatusFilter', 'st')
->where('st.filter_id = 1');
$query = $this->createQueryBuilder('hr')
->select('*')
->where('hr.status IN (' . $subquery->getDQL() . ')')
->getQuery();
Try this approach in the HelpRequestsRepository class:
$qb = $this->createQueryBuilder('hr');
$qb->select("hr");
$qb->join("::Status","s",Expr\Join::INNER_JOIN, "hr.status=s" );
$qb->join("::Filter","f",Expr\Join::INNER_JOIN, "s.filters=f" );
$qb->where("f.name = :name");
$qb->setParameter('name', $nameOfTheFilterToBeFound)
Hope this help

Categories