Octobercms child relationship of relationships - php

Im working in a octobercms plugin and i have the following models in it:
Houses
Pays
Owners
In Houses model i have the following relationship:
public $hasManyThrough = [
'owner' => [
'Author\Plugin\Models\Owner',
'table' => 'author_plugin_houses_owners',
'key' => 'houses_id',
'otherKey' => 'owners_id'
]
];
this relationship has a intermediate table 'author_plugin_houses_owners':
+------------+-------------+--------+
| hoses_id | owners_id | active |
+------------+-------------+--------+
And in Pays model i have the following relationship:
public $belongsTo = [
'houses' => ['Author\Plugin\Models\Houses']
];
this model corresponds to a table like this:
+----+----------+---------+-------+------+
| id | hoses_id | amounth | payed | debt |
+----+----------+---------+-------+------+
in the table i have a column "houses_id", the question is, how i can access to "owner" relationship declared in "Houses" model since "Pays" model? i need access to it because i need printed "owner_name" on list view in the plugin backend.
owner table:
+----+------------+--------+
| id | owner_name | active |
+----+------------+--------+
thanks a lot!

I assume that Pay belongs to only one house.
public $belongsTo = [
'house' => ['Author\Plugin\Models\Houses']
// ^ you can use house instead houses for relation name
];
Now we can access owner with relation chain
$payModel->house->owner
Since you made relation hasMany one house can have multiple owners [ hasManyThrough ]
so this will return you a list of owners.
As in list view you need to use it, You can define partial type column
columns:
id:
label: id
type: number
searchable: true
sortable: true
.... other fields ...
owner_name:
label: Owner
type: partial
path: $/hardiksatasiya/demotest/models/sort/_ownername_column.htm
Make sure to replace the path with your own plugin author name and path.
Now inside partial, you can write your actual logic [ this must be in PHP not in twig ]
<?php
// this is hasmany relation so we expect multiple owners
$owners = $record->house->owner;
$ownerArr = [];
foreach($owners as $ownr) {
$ownerArr[] = $ownr->owner_name;
}
$output = implode($ownerArr, ', ');
?>
<?php echo $output ?>
If there is only one owner it will show Hardik one name only if there are multiple owners it will show Hardik, Noe comma separated values.
if any doubts please comment.

Related

Can we make a "Class Level Relationship" in Laravel, as opposed to an "Object Level Relationship"?

Consider the following scenario:
There are couple of entities in my Laravel application like the following:
Post
Page
Image
Video
All the above entities can have CustomFieldValues, which is another entity. The structure of the custom_field_values table is as follows:
ID
entity_id
custom_field_definition_id
value
[Timestamp fields]
All the CustomFieldValues belong to a single CustomFieldDefinition entity. Its table custom_field_definitions looks like following:
ID
parent_entity_name
definition_name
[Timestamp fields]
Following are some sample data from the custom_field_definitions table:
| ID | parent_entity_name | definition_name |
|----|--------------------|-------------------|
| 1 | Post | AuthorTwitterUrl |
| 2 | Page | SeoTitle |
| 3 | Image | OriginalSourceUrl |
| 4 | Video | MpaaRating |
As you can see, CustomFieldDefinitions are definitions of extra data, that we can store about each type of entity.
Following are some sampel data from the custom_field_values table:
| ID | entity_id | custom_field_definition_id | value |
|----|-----------|----------------------------|-----------------------------------|
| 1 | 1 | 1 | https://twitter.com/StackOverflow |
| 2 | 1 | 2 | My Page's SEO Title |
| 3 | 1 | 3 | http://example.com/image.jpg |
| 4 | 1 | 4 | G – General Audiences |
A little description about the data contained in the custom_field_values table:
CustomFieldValue:1: The value for CustomFieldDefinition:1 and its entity 1 (Post:1, in this case, because CustomFieldDefinition:1 is related to Post.) is "https://twitter.com/StackOverflow".
CustomFieldValue:2: The value for CustomFieldDefinition:2 and its entity 1 (Page:1, in this case, because CustomFieldDefinition:2 is related to Page.) is "My Page's SEO Title".
CustomFieldValue:3: The value for CustomFieldDefinition:3 and its entity 1 (Image:1, in this case, because CustomFieldDefinition:3 is related to Image.) is "http://example.com/image.jpg".
CustomFieldValue:4: The value for CustomFieldDefinition:4 and its entity 1 (Video:1, in this case, because CustomFieldDefinition:4 is related to Video.) is "G – General Audiences".
custom_field_values table's entity_id can refer to any entity class, therefore it is not a foreign key in the DB level. Only in combination with custom_field_definition_id we can find to which entity it actually refers to.
Now, all is well and good, until I need to add a relationship called customFieldDefinitions to any of the entities (Say Post.).
class Post extends Model {
public function customFieldDefinitions(){
$this -> hasMany ('CustomFieldDefinition');
}
}
The above does not work, because the datapoint that indicates the CustomFieldDefinition's relationship is not a foreign key field in the custom_field_definitions table, named post_id. We have to somehow build the relationship based on the fact that some records in the custom_field_definitions table has "Post" as the value of the field parent_entity_name.
CustomFieldDefinition::where('parent_entity_name', '=', 'Post');
The above snippet fetches the CustomFieldDefinitions that are related to the Post, however, it is not possible to do something like the following with the relationship:
class Post extends Model {
public function customFieldDefinitions(){
$this
-> hasMany ('CustomFieldDefinition')
-> where ('parent_entity_name', '=', 'Post')
;
}
}
The where constraint works. But Laravel also injects the ID of the current Post object into the set of constraints.
So, what I want to do is, not consider the current object's ID at all, and build a "Class Leavel Relationship", and not an "Object Level Relationship".
Is this possible under Laravel?
There might be a workaround but I'm not pretty sure about it.
What you could try doing is to define a mutated attribute and set it as the local key of the relationship:
class Post extends Model
{
public function getEntityNameAttribute()
{
return 'Post';
}
public function customFieldDefinitions()
{
return $this->hasMany(
'CustomFieldDefinition',
'parent_entity_name',
'entity_name'
);
}
}
You could also go further and define a trait which could be used by all your models which have customFieldDefinitions. It could look like:
trait HasCustomFieldDefinitionsTrait
{
public function getEntityNameAttribute()
{
return (new ReflectionClass($this))->getShortName();
}
public function customFieldDefinitions()
{
return $this->hasMany(
'CustomFieldDefinition',
'parent_entity_name',
'entity_name'
);
}
}
Then you can use it wherever needed:
class Post extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Video extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Page extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Image extends Model
{
use HasCustomFieldDefinitionsTrait;
}
Instead of hasMany(), you can create One To Many (Polymorphic) relationship between Post, Page, Image, Video and CustomFieldDefinition.
More about polymorphic relationships here.

How to assign relationship and fetch data using array field in laravel

There are two models: Restaurant and Category
Restaurant{
'_id' : 12345678
'name' : "xyz",
'abstract' : "awdwadawdawdawd",
'category': [1,2,3,4] //category ids
}
Category{
'_id' : 1,
'name' : 'PQR'
}
How can I assign relationship and fetch all the categories using that array field (category) of Restaurant in laravel 5.3 and Database is mongodb?
I am not quite sure about your DB structure, but I think it would be better to create a new pivot table to store the categories of each restaurant in a different row like this:
id | restaurant_id | category_id
1 | 23 | 3
2 | 23 | 5
3 | 25 | 4
As a convention the table should be named 'category_restaurant'
This would be a many to many relationship in laravel.
class Restaurant
{
public function categories()
{
return $this->belongsToMany(Category::class);
}
}
class Category
{
public function restaurants()
{
return $this->belongsToMany(Restaurant::class);
}
}
Then in your code you can get the ids of a restaurant in this way:
$restaurant = Restaurant::find(1);
$categories = $restaurant->categories;

CakePHP 3 - Saving data with associations where dependant on association

For simplicity sake, I have simplified my tables to get my core need across.
I have two tables: admins and users. The admin table simple holds certain information that is not entirely relevant to the question.
Admins:
+----+---------+
| id | user_id |
+----+---------+
| 1 | 20 |
+----+---------+
| 2 | 25 |
+----+---------+
| 3 | 27 |
+----+---------+
Users:
+----+--------+-----------+
| id | name | surname |
+----+--------+-----------+
| 20 | Name 1 | Surname 1 |
+----+--------+-----------+
| 25 | Name 2 | Surname 2 |
+----+--------+-----------+
| 27 | Name 3 | Surname 3 |
+----+--------+-----------+
The idea is that when a super admin saves a new user, the fill in a form, being a user registration for of sorts and upon saving it saves the user and creates the admin record with the newly created user id.
Obviously the admin record cannot be created without the user account existing as it requires the user id. Cake simple returns an error saying "user_id is required".
This is my form:
<?= $this->Form->create($user) ?>
<fieldset>
<legend><?= __('Add Admin') ?></legend>
<?php
echo $this->Form->input('user.name');
echo $this->Form->input('user.surname');
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
I have tried creating a user with an admin association and the other way around:
$admin = $this->Admins->newEntity();
if($this->request->is('post')) {
$this->Admins->patchEntity($admin, $this->request->data,[
'associated' => ['Users']
]);
$savedAdmin = $this->Admins->save($admin);
}
and
$user = $this->Admins->Users->newEntity();
if($this->request->is('post')) {
$this->Admins->Users->patchEntity($user, $this->request->data,[
'associated' => ['Admins']
]);
$savedUser = $this->Admins->Users->save($user);
}
I do not what to save separate entities because I know it is possible to saved associated data, I'm just not really how to saved the admin record as it relies on the user record.
It sounds like it might make more sense to have a boolean field in the user table that defines whether a user is an admin or not.
That being said, you may need to define the relationship in your models if it is not there already. Technically, based on your table names and and columns (user_id) Cake should bake this in for you, but I don't remember if it's conventional to have plural model names (Users, Admins), so this may not have happened.
User:
public $belongsTo = [
'Admins' => [
'classname' => 'Admins', //are your models really plural?
'foreignKey' => 'user_id',
];
Admin
public $hasOne= [
'Users' => [
'classname' => 'Users',
'foreignKey' => 'user_id',
];
Then, I think you'd need to specify some information about the admin, or you won't have any data to save the association. In the view:
echo $this->Form->input('admins.field1');
echo $this->Form->input('admins.field2');
echo $this->Form->input('admins.field3');
Note these fields can be hidden if you don't want anything inputted.
To save, make a new user from the request data (your form creates a user, but you could create an admin with the form, and then add inputs like user.name and user.surname). Controller:
$user = $this->Admins->Users->newEntity();
if($this->request->is('post')) {
$this->Admins->Users->patchEntity($user, $this->request->data, [
'associated' => ['Admins']
]);
$this->Admins->Users->save($user);
}
First, create your admin entity and then associate users (as admin requires the user id). Once you have done that create your new user entity and assign it to a variable.
The, manually assign the foreign key the newly created user, and Cake will figure out it needs to assign it the primary based on your model. Finally, save your admin, and it will automatically save your associated entity.
That's why it is important to define your belongs to and has one in the relevant models as mentioned in the previous answer.
if ($this->request->is('post')) {
$admin = $this->Admins->newEntity($this->request->data, [
'associated' => ['Users']
]);
$user = $this->Admins->Users->newEntity();
$admin->user_id = [$user];
$saved = $this->Admins->save($admin);
}

Laravel: use Eloquent belongsto , get data from both tables?

In Controller, I would like to pass the only one variable with specifies column from parent in it. Now,I'm using
View::make('store.product')->with('products', Product::find($id)
->join('design','product.design_id','=','design.id')
->join('user','design.user_id','=','user.id')
->select('user.name','design.title','product.price')
->get();
My question is
1.Is there a better way to do this by using Belongsto?
2.If can do, Is it work the same with Hasmany?
This is my table structure.
User
id | name
1 | 'John'
Design
id | user_id | title
1 | 1 | 'Chill'
2 | 1 | 'Mad'
Product
id | design_id | price
1 | 1 | 20
2 | 1 | 30
And Model be like this
Product belongsto Design ,
Design belongsto User
Add a method to your Users like so for your designs that the user has;
public function designs(){
$this->hasMany('Design');
}
For the designs model add the following methods;
public function user(){
$this->belongsTo('User');
}
public function products(){
$this->hasMany('Product');
}
For your products model
public function design(){
$this->belongsTo('Design');
}
These will set up the relationship allowing you to eager load the data on your models.
This can be done like so;
$variable = Product::with('designs')->find(1); //This will load the product with the related designs
If you want all the designs and the users that belong to the designs do the following;
$variable = Product::with('designs', 'design.user')->find(1); //This will load the designs that relate to the Product and include the user that that design belongs to on the Design model.
To access the properties use the following;
$variable->designs //This will return a collection of all the designs.
$variable->designs->first()->user //This will return the user model that the design belongs to.
An example of displaying the information;
#foreach ($variable->designs as $design)
{{ $design->user->username }}
#endforeach
Please note: i have not tested this code.

Getting names to show in dropdown list with cakephp

I want to show the names of all our project leaders in a dropdown list.
The project leaders are only some of the employees that work in the company.
Here are my tables:
project_leaders
,----,----------------,
| id | hr_employee_id |
|----|----------------|
| 1 | 18 |
'----'----------------'
projects
,----,------,-------------------,
| id | name | project_leader_id |
|----|------|-------------------|
| 1 | cake | 1 |
'----'------'-------------------'
hr_employees
,----,------,---------,
| id | name | surname |
|----|------|---------|
| 18 | joe | dirt |
'----'------'---------'
My ProjectsController looks like this:
public function add() {
if ($this->request->is('post')) {
$this->Project->create();
if ($this->Project->save($this->request->data)) {
$this->_sendProjectRequestEmail($this->Project->getLastInsertID() );
$this->Session->setFlash(__('The project has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The project could not be saved. Please, try again.'));
}
}
$this->set('projectLeaders',$this->Project->ProjectLeader->find('list');
}
This only returns the id of the Project Leaders, not the name and surname. So instead of Joe Dirt, it returns 1.
I've tried doing $this->Project->ProjectLeader->HrEmployee->find('list') but that lists all the employees.
I've also tried specifying the fields, but it returns a unknown field error.
What am I doing wrong?
$this->set('projectLeaders',$this->Project->ProjectLeader->find('list'));
This will just list the records from the project_leaders table and most likely, as the table doesn't itself contain a name/title field (which cake would pick up automatically as the displayField) be like so:
array(
1 => 1
)
Use an appropriate find
To get a meaningful list of project leaders, you need to ensure you get a join between project_leaders and hr_employees one way to do that is using the containable behavior and simply specifying which fields to use in the list:
$projectLeaders = $this->Project->ProjectLeader->find('list', array(
'contain' => array(
'HrEmployee'
),
'fields' => array(
'ProjectLeader.id',
'HrEmployee.name',
)
));
$this->set(compact('projectLeaders'));
Is your db structure appropriate for your needs?
Having a table equivalent to saying "this user is admin" may not be the best idea - it would be easier to avoid data problems, and give you simpler queries, if the table project_leaders was (only) a boolean field on your hr_employees table and project_leader_id pointed at the hr_employee table, not some other data-abstraction.
Of course I don't know your whole schema, there very well may be a good reason for having a seperate project_leaders table.
Alternatively - denormalize
If you add a name field to project_leaders - you don't need a join to know the name of a project leader or any funkiness:
alter table project_leaders add name varchar(255) after id;
update project_leaders set name = (select name from hr_employees where hr_employees.id = hr_employee_id);
In this way you can know who is the relevant project lead with one query/join instead of needing to do two joins to get an employee's name.
You forgot to define the displayField:
The default case is
public $displayField = 'name';
And only for an existing "name"/"title" field in this table cake will automatically know what your drop-downs should get as label text.
To use the fields of other tables, make sure you pass fields in your find() options:
'fields' => array('Project.id', 'Project.name');
etc. Cake will then know: the first one is the key, and the second one the display value.
If you need combined/custom fields/texts in your dropdowns use virtualFields
public $virtualFields = array(
'full_name' => 'CONCAT(...)'
);
and set the displayField to this full_name virtual field then or use fields as explained above.
You could add a virtual field to your ProjectLeader Model:
public $virtualFields = array (
'name' => 'SELECT name FROM hr_employees AS Employee WHERE Employee.id = ProjectLeader.employee_id'
);
Also, add this virtual field as your displayField:
public $displayField = 'name';

Categories