Symfony 4 Doctrine multiple table joins in one property - mysql

Very new to symfony and Doctrine. I have the following tables in my database.
mo_user
id | email | password
__________________________________
9144 | summer#h.com | !password!
mo_user_role
user_id| role_id
_________________
9144 | 5
mo_permission
id | namespace | name | description
______________________________________________
1 | admin | - | -
2 | users | - | -
3 | view_summary_report | - | -
4 | view_user_statement | - | -
mo_role_permission
role_id | permission_id
________________________
5 | 3
5 | 4
I am trying to return an array of the permissions of the current user in this case user with id = 9144 which should be array('view_summary_report','view_user_statement').
I have mapped all the tables to their corresponding entity classes. and in MoUser.php entity class which corresponds to mo_user table, I have a
permissions method which should return the array but my join from annotations is failing,
My getPermissions() method in MoUser.php
/**
* #var Collection|MoPermission[]
* #ORM\ManyToMany(targetEntity="App\Entity\MoPermission")
* #ORM\JoinTable(
* name="mo_user_role",
* joinColumns={#ORM\JoinColumn(name="user_id",referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id",referencedColumnName="id")}
* )
*/
private $permissions;
public function getPermissions()
{
$currentPermissions = array();
foreach ($this->permissions->toArray() as $index => $permission) {
$currentPermissions[] = $permission->getNamespace();
}
//Return default role if Roles are not assigned to this user.
if(count($currentPermissions)>0) {
return $currentPermissions;
} else {
return array('DEFAULT_PERMISSION');
}
}
So I figured out the raw sql to achieve what I wanted which is below, but I would like to know the Symfony/Doctrine annotated way of achieving the following raw SQL.
SELECT t0.id AS id_1, t0.namespace AS namespace_2, t0.name AS name_3, t0.description AS description_4
FROM mo_permission t0
LEFT JOIN mo_role_permission ON t0.id = mo_role_permission.permission_id
LEFT JOIN mo_user_role ON mo_role_permission.role_id = mo_user_role.role_id
WHERE mo_user_role.user_id = 9144;

I don't think there is a proper way to achieve what you're trying to do directly through property annotations with your current setup.
You could achieve what you want with one of these solution though :
One of mo_user_role and mo_role_permission is not needed, since none of them have additional field. You should just have a mo_user_permission table generated by a ManyToMany relationship between MoUser and MoPermission, which would grant you direct access to MoPermission from MoUser's getPermissions()
Another way would be to create a service which would have a GetPermissionsFromUser(MoUser $moUser) method (for example), calling the proper query from the entity's repository, which you would call when needed.
You could still achieve what you want in your getPermissions() method with your current setup, but you would have to loop through each relation's items to build your new result array manually.
e.g. for last point :
public function getPermissions() {
$permissions = [];
foreach($this->roles as $role) {
foreach($role->getPermissions() as $permission) {
permissions[] = $permission->getNamespace();
}
}
return $permissions;
}
This would assume you have a MoRole entity, which would make sense regarding your current setup, but you didn't mention it. Otherwise, same logic could still be applied though, it's just a naming matter.

I'm pretty sure that you could do that query using Doctrine (and a QueryBuilder) like...
use Doctrine\ORM\EntityRepository
class PermissionRepository extends EntityRepository
{
//....
/**
* #param UserInterface $user
* #return array|Permission[]
*/
public function getPermissionsForUser(UserInterface $user)
{
$queryBuilder = $this->createQueryBuilder('permission');
/**
* permissions will be in a multi-dimensional array
* with a single key per array of 'namespace'
*/
$permissions = $queryBuilder
->select('permission.namespace')
->join('permission.role', 'role')
->join('role.user', 'user')
->where($queryBuilder->expr()->eq('user', ':user'))
->setParameter('user', $user)
->getQuery()
->getArrayResult();
if (count($permissions) > 0) {
/**
* If there are any permissions found just return
* the 'namespace' property from each "sub-array"
*/
return array_column($permissions, 'namespace');
}
return ['DEFAULT_PERMISSION'];
}
//...
}
And then you would call it like..
$permissions = $repository->getPermissionsForUser($user);

Related

How to get sum of distinct ids/elements in a column Laravel? [duplicate]

How do I use join with Eloquent taking in consideration the following table structure:
I have a properies table
---------------------
ID | Name
---------------------
1 | Property Name
than I have rooms
----------------------
RoomID | Property
----------------------
A-212 | 1
----------------------
F-1231 | 1
here Property is the foreign key
than I want to get all Properties and count how many rooms do they have each
The query which retrives all looks like
class PropertiesRepository extends EloquentBaseRepository implements PropertiesInterface
{
use TaggableRepository;
/**
* Construct
* #param Properties $properties
*/
public function __construct( Properties $properties )
{
$this->model = $properties;
}
/**
* Get all properties joining rooms
* #return Properties
*/
public function getAll()
{
return $this->model->get();
}
}
How do I extend this query to get the desired result?
This is more of a MySQL join+group+select trick which includes following steps.
Join your relation table(use join if you want to exclude rows with RoomsCount=0, else use leftJoin)
Use groupBy by primaryKey to avoid duplicates of the join.
Select count of joined table
$this->model->leftJoin('Rooms', 'Properties.ID', '=', 'Rooms.Property')
->selectRaw('Properties.*, count(Rooms.RoomID) as RoomsCount')
->groupBy('Properties.ID')
->get();
Define the relationship on your Property model class:
<?php
namespace App\Models;
class Property extends Model {
public function rooms() {
return $this->hasMany(Room::class);
}
}
$properties = Property::withCount(['rooms'])->get();
This will add a rooms_count to the result.

Eloquent relation on data types (fields) table

Here my tables, User is the classic Auth table:
DataTypes
+----+--------------+
| id | field |
+----+--------------+
| 1 | address |
| 2 | mobile_phone |
| 3 | city |
+----+--------------+
UserData
+----+----------+--------------+---------+
| id | value | data_type_id | user_id |
+----+----------+--------------+---------+
| 1 | Milan | 3 | 1 |
| 2 | 99123233 | 2 | 1 |
+----+----------+--------------+---------+
My current crazy model:
class User extends Authenticatable {
public function field($field){
$field=DataType::where('field',$field)->first();
return $this->hasMany('App\UserData')->where('datatype_id',(isset($field->id) ? $field->id : 0));
}
}
Of course it works fine when I have to find values:
auth()->user()->field('mobile_phone')->get();
But how do I set or update a new mobile phone?
Another approach is link User and DataTypes as a many to many relationship and use a scope to handle it.
First of all, change UserData table to dataTypeUser, so you could use default eloquent relationships.
In any model put the belongsToMany relationship.
User Model
public function dataTypes()
{
return $this->belongsToMany('App\DataType')
->withPivot(['value']);
}
public function scopeField($query, $field)
{
return $query->whith(['dataTypes'=>function($q)use($field){
$q->where('data_types.field',$field);
}]);
}
DataType Model
public function users()
{
return $this->belongsToMany('App\User')
->withPivot(['value']);
}
To get some value you can use $field = $user->field('something')->first() to save new data could use $user->field('something')->updateExistingPivot($field->id,['value'=>$newValue]).
Anyway, if you don't have many data of same type attached to your user (more than one phone number for example), it could be a better approach to use one single table extending user migration, or at last an userData with columns for each datatype. In small applications you have no problems but as your application grows performance will be a problem with many tables and many relationships.
To avoid a long declaration, you could overwrite magic methods __get and __set. Both are declared in Illuminate\Database\Eloquent\Model so put in your User Model:
public function __get($key)
{
return $this->getAttribute($key) ?? $this->getAttributesFromPivot($key);
}
public function __set($key, $value)
{
if(in_array($key,array_keys($this->original)))
$this->setAttribute($key, $value);
else
$this->setAttributeFromPivot($key, $value);
}
public function setAttributeFromPivot($key, $value)
{
$this->dataTypes()->where('field',$key)->update(['value'=>$value]);
}
protected function getAttributesFromPivot($key)
{
return $this->dataTypes()
->where('field',$key)
// use this to get only one value direct
->first()->pivot->value ?? null;
// or use this to get all of them as array
// ->get()
// ->map(function($item){ return $item->pivot->value;}) ?? null;
}
In this approach, you could use $user->city to get field city or another one replacing ->city. Or you could use $user->address = 'foo'; to set a new value in pivot table. Note that it will update database directly.
Now, if you are not comfortable to overwrite those methods, you don't need to. Change the signatures of setAttributeFromPivot and getAttributeFromPivot to public function getField($key) and public function setField($key, $value). Now you can use them as common methods.

Laravel sql select users email who listed rooms where status = listed

I have these 2 tables:
**users**
id | name | email | ...
-----------
**rooms**
user_id | title | status | ...
---------------------------
How can i select all users's email with rooms where rooms status is Listed in Laravel 5 ?
What you have to do, you have to use "with" and "has"
$user = User::with(['room'])->whereHas('room,function($query){
$q->where('status',1);
})->get();
create model of User and Room & a relation in user name room.
May be the above one solve your problem
If you have already define a relation between App\User and App\Room Models like this.
class User {
/* other codes in your model goes here */
public function rooms(){
return $this->hasMany(App\Room::class);
}
}
class Room {
/* other codes in your model goes here */
public function user(){
return $this->belongsTo(App\User::class);
}
}
You can retrieve all users's email with rooms where rooms status is Listed like this
$users= \App\User::with(['rooms' => function($query){
$query->where('status', 'listed');
}])->pluck('email');

Eloquent - Strange behavior on save

I'm saving a record and then returning the values from database and Eloquent is not returning all of the values.
Example:
If I have a MySQL table called names like this:
| ID | Date | Name |
|----------------------------|-------------------------------------|--------------|
| AUTO INCREMENT PRIMARY KEY | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | VARCHAR(100) |
and I have an eloquent model called Names
and then I save and return like this:
function save($sName) {
$oName = new Names();
$oName->Name = $sName;
$oName->save();
$oReturn = new stdClass();
$oReturn->Name = $oName->Name;
$oReturn->Date = $oName->Date;
return $oReturn;
}
It returns:
echo json_encode(save("Test")); // {"Name": "Test", "Date": null}
Is this normal?
Yes that is normal. Eloquent doesn't save and then do another query to select all the data for the record it just inserted.
You can manually set field date, just add this code into your mode:
public static function boot()
{
static::creating(function ($model) {
$model->date = date('Y-m-d H:i:s');
});
}
You will avoid second DB request for selecting inserted item. Just be carefull with timezones when you're using date function.
Also you can use getAttributes() Eloquent method and get rid of stdClass:
function save($sName)
{
$oName = new Names();
$oName->Name = $sName;
$oName->save();
return $oName->getAttributes();
}

Yii Call beforeSave() in other model's to be Save

I have created two tables as, forum_post and gallery.
forum_post table:
id user_id ststus photo_id
1 1 hi...! NULL
2 1 hello! NULL
3 1 NULL 1
4 1 NULL 2
user_gallery table:
id user_id image video
1 1 1.jpg NULL
2 1 new.gif NULL
When, user upload the image file in the user_gallery table, i want to create one row in the forum_post table and store the gallery id into the forum_post-> image field. as well as the user id also stored in the forum_post table.
My model code in the ForumPost is:
public static function addForumImage($id, $user_id) {
$forumImage = ForumPost::model()->find('LOWER(photo_id) = ?', array( strtolower($image)));
if (!$forumImage) {
$forumImage = new ForumPost;
$forumImage->photo_id = $image;
$forumImage->save(false);
}
UserGallery beforeSave function is:
protected function beforeSave() {
if (parent::beforeSave()) {
ForumPost::addForumImage($this->id, $this->user_id);
// var_dump($forumPost->photo_id);
return true;
}
return false;
}
My table relationship is, user_gallery->image refers the forum_post->photo_id.
Now, the image is stored in the user_gallery folder and i dint get the id in the ForumPost model...
Please any one help me.. :(
try this
protected function beforeSave() {
if (parent::beforeSave()) {
ForumPost::addForumImage($this->id, $this->user_id, $this->forum_image);
// var_dump($forumPost->photo_id);
return true;
}
return false;
}
Model
public static function addForumImage($id, $user_id,$image) {
$forumImage = ForumPost::model()->find('photo_id = :image', array( ':image'=>strtolower($image)));
if (empty($forumImage)) {
$forumImage = new ForumPost;
$forumImage->user_id=$user_id;
$forumImage->content= NULL
$forumImage->photo_id = $image;
$forumImage->save(false);
}
}