Yii2 Relational databases confusion - php

So i am working with Yii2 and i'm trying to get what i think is relation databases in order. What im trying to do is have multiple tables return one set of data with the Yii2 rest api.
Ive been trying to find exactly what i need to do but im a bit confused. I believe i have to setup foreign keys and link them somehow but that's where i get lost. Also i think once the database portion is setup, does the gii tool pick up on the 'relationships' when i build a new model? I think ill have to manually build the new model/controller instead of using gii, that sends all templates to the 'frontend' directory and not my versioned 'module/v1' directory
----EDITED for changes----
table 'state':
- country_id is the index
+--------------------------------------+---------------------------------+
| Column | Internal Relations | Foreign Key constraint (INNODB) |
+--------------------------------------+---------------------------------+
| country_id | yii2.country.country_id | yii2.ountry.country_id |
+------------------------------------------------------------------------+
+-------------+------------------+----------------------+
| id (int, AI)| country_id (int) | state_name (varchar) |
+-------------+------------------+----------------------+
| 0 | 10 | Texas |
| 1 | 10 | New York |
| 2 | 20 | Glasgow |
+-------------+------------------+----------------------+
table 'country':
- country_id is the Primary Key
- column to display in relation-view is country_name
+------------------+--------------------------+
| country_id (int) | country_name (varchar) |
+------------------+--------------------------+
| 10 | United States of America |
| 20 | Germany |
+------------------+--------------------------+
So my goal is to have my api GET request of: localhost/yii2/api/web/vi/states/
And get a response of:
{
"success": true,
"data": [
{
"id": 1,
"country_id": 10,
"state_name": "Texas"
},
{
"id": 2,
"country_id": 10,
"state_name": "New York"
},
{
"id": 3,
"country_id": 20,
"state_name": "Glasgow"
}
]
}
}
model (State.php):
<?php
namespace api\modules\v1\models;
use Yii;
/**
* This is the model class for table "state".
*
* #property integer $country_id
* #property string $state_name
*
* #property Country $country
*/
class State extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'state';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['country_id', 'state_name'], 'required'],
[['country_id'], 'integer'],
[['state_name'], 'string', 'max' => 55]
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'country_id' => 'Country ID',
'state_name' => 'State Name',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getCountry()
{
return $this->hasOne(Country::className(), ['country_id' => 'country_id']);
}
}
controller (StateController.php):
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
/**
* Country Controller API
*
*/
class StateController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\State';
}

Setup the foreign keys in the database and use gii to generate the models and it will link them automatically according to your relation and then you can use it like this ...
$states= State::find()->with('countries')->all();
this code will get you the states with their countries

I believe i have to setup foreign keys and link them somehow but
that's where i get lost. Also i think once the database portion is
setup, does the gii tool pick up on the 'relationships' when i build a
new model?
Yes
I think ill have to manually build the new model/controller instead of
using gii, that sends all templates to the 'frontend' directory and
not my versioned 'module/v1' directory
You can make Gii send the files wherever you want, you just have to put the namespace in the proper place.

Related

Laravel global access with polymorphic relations

In my application I have two columns employable_id && employable_type in almost every table, which are used for storing the information about the user who has created the record.
Something like this:
subscribers: products:
------------------------------------ ------------------------------------
id | employable_id | employable_type id | employable_id | employable_type
------------------------------------ ------------------------------------
1 | 1 | App\Company 1 | 1 | App\Company
2 | 1 | App\Company 2 | 1 | App\Employee
3 | 1 | App\Employee 3 | 3 | App\Employee
4 | 1 | App\Employee 4 | 8 | App\Employee and more...
Subscriber.php
class Subscriber extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
}
I have created an accessor to combine polymorphic relationship's columns, which is working fine.
class Subscriber extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
/**
* Accessor
*/
public function getAddedByAttribute()
{
return $this->employable->name . ' [ ' . $this->employable->designation . ' ]';
}
}
class Product extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
/**
* Accessor
*/
public function getAddedByAttribute()
{
return $this->employable->name . ' [ ' . $this->employable->designation . ' ]';
}
}
I am trying to make the getAddedByAttribute method global instead of adding in every model class.
How can I do that..?
you can make a trait and use it in every model need this function

Laravel/Eloquent ORM - retrieve only referenced records

I have a many-to-many relationship which I resolved with an intersection table.
So table A and B are connected through AxB.
A and AxB are 1-n and B and AxB are 1-n.
The actual names of the tables:
table A = extensiontables_registry
table B = ad_groups
table AxB = extensiontables_registryxad_groups
You can see the logical datamodel here:
https://imgur.com/MNpC3XV
Ive put the part we are talking about right now into a red frame.
Now, I have the following line of code in my backend-API:
$permittedTables = extensiontables_registry::findMany($ids)->pluck('extensiontable_name')->toArray();
To keep things short, the $ids contains all the ids from "ad_groups". These I've gotten from a fetch which works as intended.
The $ids contains these values/ids according to my logs:
[1,2,3,4,5,6,7,8,9,10,12]
Now, the extensiontables_registryxad_groups currently looks like this:
select * from extensiontables_registryxad_groups;
+-----------------------------+-------------+------------+------------+
| extensiontables_registry_id | ad_group_id | created_at | updated_at |
+-----------------------------+-------------+------------+------------+
| 1 | 8 | NULL | NULL |
| 2 | 8 | NULL | NULL |
+-----------------------------+-------------+------------+------------+
2 rows in set (0.000 sec)
And the extensiontables_registry looks like this:
+----+-----------------------+------------+------------+
| id | extensiontable_name | created_at | updated_at |
+----+-----------------------+------------+------------+
| 1 | extensiontable_itc | NULL | NULL |
| 2 | extensiontable_sysops | NULL | NULL |
| 3 | test | NULL | NULL |
+----+-----------------------+------------+------------+
And now the problem is that my codesnippet from above:
$permittedTables = extensiontables_registry::findMany($ids)->pluck('extensiontable_name')->toArray();
returns me this result:
array (
0 => 'extensiontable_itc',
1 => 'extensiontable_sysops',
2 => 'test',
)
So the codesnippet does NOT do what I want it to do. It should only fetch me the names of those extensiontables which have IDs which exist on the very same record(s) in extensiontables_registryxad_groups with IDs from my inputarray above. So The result I currently would expect would be this:
array (
0 => 'extensiontable_itc',
1 => 'extensiontable_sysops'
)
I am pretty new to laravel and eloquent, so I dont really know what I did wrong in my codesnippet. I also have no idea what I can do to get this working as intended ^^
For the sake of completeness, I'll show you my eloquent models/classes for this arrangement of tables, just in case you might need it:
AD_Group.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ad_group extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name'
];
/**
* Hides pivot from return queries.
*
* #var array
*/
protected $hidden = [
'pivot'
];
/**
* Many-To-Many relationship with User-Model.
*/
public function Ad_users()
{
return $this->belongsToMany('App\Ad_user', 'Ad_usersxad_groups', 'Ad_group_id', 'Ad_user_id');
}
public function extensiontables()
{
return $this->belongsToMany('App\extensiontables_registry', 'extensiontables_registryxad_groups');
}
}
extensiontables_registry.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Extensiontables_Registry extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'Extensiontables_Registry';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'extensiontable_name'
];
/**
* Many-To-Many relationship with User-Model.
*/
public function Ad_groups()
{
return $this->belongsToMany('App\Ad_group', 'extensiontables_registryxad_groups');
}
}
I would be really thankful if you could help me or at least give me a hint what to look out for in order to find information on leveraging the methods of laravel/eloquent accordingly ^^ If you need any more info, ask right away :)
I think you got things a little mixed up.
what this query does:
$permittedTables = extensiontables_registry::findMany($ids)->pluck('extensiontable_name')->toArray();
is access the extensiontables_registry table and get records with the same array of $ids you sent.
But it will NOT take in consideration the relationship with extensiontables_registryxad_groups table because you didn't explicitly specify it in the query.
What you should add is has('relationship name') which get the records of this model that only has a corresponding Foreign key in relationship name' in your case Ad_groups
So in this case the query would look like this:
$permittedTables = extensiontables_registry::has('Ad_groups')->pluck('extensiontable_name')->toArray();
i think you can get there using join:
$permittedTables = extensiontables_registry::whereIn('id', $ids)
->join('extensiontables_registryxad_groups','extensiontables_registryxad_groups'.'extensiontables_registry_id','extensiontables_registry.id')
->select('extensiontables_registry.extensiontable_name')->get()->all();
please note you can avoid repeated results using 'distinct';

Illegal offset type error on fetching a relations to self class in laravel

My User model is like this :
class User extends Authenticatable
{
protected $primaryKey = 'user_id';
protected $fillable = [
'username',
'password',
'name',
'family',
'supervisor'
];
public function children()
{
return $this->hasMany(\App\User::class, 'supervisor', 'user_id')->with('children');
}
}
As you can see there a supervisor column that specify parent of a user.
Now to fetch all children of user models that have supervisor= null, I wrote this :
return User::with('children')->whereNull('supervisor')->get();
but it return this error always :
PHP Warning: Illegal offset type in D:\wamp\www\zarsam-app\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Relations\HasOneOrMany.php on line 168
Table users have these data :
+---------+-------------+---------+--------------+------------+
| user_id | username | name | family | supervisor |
+---------+-------------+---------+--------------+------------+
| 1 | 09139616246 | ahmad | badpey | null |
| 7 | alinasiri | ali | nasiri arani | 1 |
| 8 | zahedi | mostafa | zahedi | 1 |
| 9 | hsan | hasan | ghanati | 8 |
+---------+-------------+---------+--------------+------------+
Update :
I found that problem is that I have a accessor same name supervisor attribute like this :
public function getSupervisorAttribute($value)
{
return is_null($value) ? null : User::select('user_id', 'name', 'family')->find($value);
}
I added that because I want to return supervisor user as an object.
But now in this case, what do I do ?
Try with this setup:
public function children()
{
return $this->hasMany(\App\User::class, 'supervisor', 'user_id');
}
And fetching them like
return User::with('children.children')->whereNull('supervisor')->get();
If this doesn't resolve your issue, then your relationships are not well defined, then I suggest reading about foreignand local keys inside your children.children model
NOTE:
HasOneOrMany.php
...
return $results->mapToDictionary(function ($result) use ($foreign) { //line in question
...
And when I found that method, this is what it said:
/**
* Run a dictionary map over the items.
*
* The callback should return an associative array with a single key/value pair.
*
* #param callable $callback
* #return static
*/
public function mapToDictionary(callable $callback)
I want to point out The callback should return an associative array with a single key/value pair. Which means you cannot do it recursively, at least I believe you can't.

Laravel: Get users based on Eloquent relation

I have a User model which has an attribute type among other attributes. Type is used to identify parents and children.
Parent and children (students) have many-to-many relationship.
Also students belong to one or many groups (model Group).
User model
/**
* Filter the scope of users to student type.
*
* #param $query
*/
public function scopeStudent($query){
$query->where('type', '=', 'std');
}
/**
* Filter the scope of users to parent type.
*
* #param $query
*/
public function scopeParent($query){
$query->where('type', '=', 'prt');
}
/**
* List of groups the user is associated with.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function groups(){
return $this->belongsToMany('\App\Group', 'group_user_assoc')
->withTimestamps();
}
/**
* List of students associated with the user(parent).
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function children(){
return $this->belongsToMany('\App\User', 'student_parent_assoc', 'parent_id', 'student_id')
->withPivot('relation');
}
/**
* List of parents associated with the user(student).
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function parents(){
return $this->BelongsToMany('\App\User', 'student_parent_assoc', 'student_id', 'parent_id')
->withPivot('relation');
}
The aforementioned relations are working correctly.
Below are my association tables.
student_parent_assoc
----------------------
+------------+------------------+------+-----+
| Field | Type | Null | Key |
+------------+------------------+------+-----+
| student_id | int(10) unsigned | NO | PRI |
| parent_id | int(10) unsigned | NO | PRI |
| relation | varchar(25) | YES | |
+------------+------------------+------+-----+
group_user_assoc
----------------------
+------------+------------------+------+-----+
| Field | Type | Null | Key |
+------------+------------------+------+-----+
| group_id | int(10) unsigned | NO | MUL |
| user_id | int(10) unsigned | NO | MUL |
| created_at | timestamp | NO | |
| updated_at | timestamp | NO | |
+------------+------------------+------+-----+
I need to find students who do not belong to any group along with their parents. I have managed to find students like so
$students = User::student()
->whereDoesntHave('groups')
->get();
Question:
Now I want to find parents of these students. But I am not able to build an eloquent query for the same. How do I do it?
Note: I could get collection of students from above query and run a foreach loop to get their parents like so
$parents = new Collection;
foreach($students as $student) {
foreach($student->parents as $parent) {
$parents->push($parent);
}
}
$parents = $parents->unique();
But I need a Builder object and not a Collection as I am using Datatables server side processing with Yajra/datatables.
for loading parents relation you hae to use eager loading.
2 methods are with($relation) and load($relation). Difference is just you get parents already with result objects or load them later.
So in your example to get parents you can use with('parents') or if you want to modify resulted set:
User::student()
->with(['parents' => function ($parentsQueryBuilder) {
$parentsQueryBuilder->where('condition', 1)->whereIn('condition2', [1,2,3]);
}])
->whereDoesntHave('groups')
->get();
Then you will get your parents in a relationship aswell but performance will be high cause you will spend only one query to load parents to your objects. Then you can pluck if needed them in one collection like this:
$students->pluck('parents')->flatten()->unique();
Or example 2 - if you just need all parents related to selected students - almost the same what eager loading does:
$studentIds = $students->modeKeys();
User::parent()->whereHas('children', function ($query) use($studentIds) {
$query->whereIn('id', $studentIds);
})->get();
UPDATED
For getting builder of parents try this:
/** BelongsToMany <- relation query builder */
$relation = with(new User)->query()->getRelation('parents');
$relation->addEagerConstraints($students->all());
This will create for you new instance of BelongsToMany relation and attach Constraints of whereIn($studentIds) to it. Then hitting ->get() on it you have to receive related $parents
Well, I managed to solve it like so
$students = User::student()
->with('parents')
->whereDoesntHave('groups')
->has('parents') //to get only those students having parents
->get();
$parent_ids = array();
// get ids of all parents
foreach ($students as $student) {
foreach ($student->parents as $parent) {
$parent_ids[] = $parent->user_id;
}
}
// build the query
$users = User::parent()
->with('children')
->whereIn('user_id', $parent_ids);
I would still like it if someone could suggest a better and simple approach.

A little complex query using eloquent

I am trying to learn laravel and currently using eloquent to interact with the database. I am stuck on how I could use eloquent to get a kind of a join in eloquent.
I have a many to many relation between two tables :users and projects , I use sharedProject table to be the intermediate table .
The tables are as such
Users:
| iduser | name | password |
----------------------------------------
| 1 | somename | hashPassword |
| 2 | somename2 | hashPassword |
| 3 | somename3 | hashPassword |
| 4 | somename4 | hashPassword |
----------------------------------------
Projects:
| pid | projectname
-------------------
| 1 | somename
| 2 | somename
SharedProjects:
| pid | iduser | sharedProjectid |
----------------------------------
| 1 | 1 | 1 |
| 1 | 2 | 2 |
Now I want to get all the users who are not sharing a given project, for example in the above case for project with id 1 , I should get user 3 and user 4.
Here are my relationships in User model
/**
* User can have many projects
*
* #var array
*/
public function projects(){
return $this->hasMany('App\Project','pid','iduser'); // hasmany(model,foreignkey,localkey)
}
/**
* The user can have many shared projects
*/
public function sharedProjects()
{
return $this->belongsToMany('App\SharedProject', 'sharedProjects', 'iduser', 'pid');// belongsToMany('intermediate tablename','id1','id2')
}
and in the Project model:
/**
* The project can be shared by many users
*/
public function sharedProjects()
{
return $this->belongsToMany('App\SharedProject', 'sharedProjects', 'pid', 'iduser');// belongsToMany('intermediate tablename','id1','id2')
}
/**
* a project belongs to a single user
*
* #var array
*/
public function user()
{
return $this->belongsTo('App\User');
}
I would prefer a eloquent way to do this , but would also except it, if can't be done in eloquent and I have to see a alternate approach I would appreciate a plain mysql query as well.
Thanks
Once you define your Eloquent models and your many-to-many relationships, you can use them to get the data you're looking for.
Assuming a User model that has a projects relationship defined, you can use the whereDoesntHave() method to query for a list of users that are not related to a specific project.
$projectId = 1;
$users = User::whereDoesntHave('projects', function ($q) use ($projectId) {
return $q->where('projects.id', $id);
})->get();
You can read about defining many-to-many relationships in Eloquent here.
You can read about querying relationship existence here.
As you may notice, not all methods are documented (like whereDoesntHave()), so you may have to go source code diving. You can dig into the Eloquent codebase here.
I resort to use plain mysql queries, this seems to work for me:
$nonSharedUsers = DB::select( DB::raw("SELECT iduser FROM users WHERE NOT EXISTS (SELECT * FROM sharedProjects WHERE sharedProjects.iduser= users.iduser and sharedProjects.pid=:projectId)"), array(
'projectId' => $pid,
));

Categories