I have the database just like this
==== Group =====
id
name
==== Member ====
id
group_id
firstname
lastname
Now I can use Member member's table attributes in group controller just by using multimodel.
As I have done multimodel so I can easily make create update delete for both models from a single view page. Now my problem is when I am going to shoew both models in Group's admin view file I have to show both files in CGridView to show in a Grid. But my problem is in CGridView only first model can be seen.I want the second models to be shown on the CGridView. So how to do that?
I think you need to combine models using the Relational Active Record.
In the process of self learning I'm following, I hope I will shortly have an example to post here...
EDIT finally, I worked out an example. I've (maybe) found a bug in Yii, so what is an easy task, required more time than necessary...
I have two models, User e Persona, obtained from gii, previously unrelated. Then I add a Persona to User as optional attribute: in User.php
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'persona' => array(self::HAS_ONE,'Persona','id')
);
}
then the model for User automatically display selected fields from Persona, when bound to CGridView:
<hr><?php
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider' => new CActiveDataProvider('User'),
'columns' => array(
'username',
'password',
'email',
'persona.indirizzo'
),
));
?>
The bug I found (perhaps, need to investigate more): my Persona model has an attribute with an accented character in name, and if I use that instead I get an error: i.e. if
'columns' => array(
'username',
'password',
'email',
'persona.identità'
),
then the page can't be instanced:
The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.
/home/carlo/public/yii-1.1.8.r3324/framework/zii/widgets/grid/CGridView.php(332)
320 foreach($this->columns as $column)
321 $column->init();
322 }
323
324 /**
325 * Creates a {#link CDataColumn} based on a shortcut column specification string.
326 * #param string $text the column specification string
327 * #return CDataColumn the column instance
328 */
329 protected function createDataColumn($text)
330 {
331 if(!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/',$text,$matches))
332 throw new CException(Yii::t('zii','The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.'));
333 $column=new CDataColumn($this);
334 $column->name=$matches[1];
I think it's the regular expression that mismatch...
If you add getters for groupId and groupName to the Member model you can easily display the gridview for members and include their group data. It's not an extremely clean solution, but it's the easiest.
Define some function like 'getGroupNameById' in group model then call it as follows
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider, //for members model
'columns'=>array(
'id',
'firstName',
'lastName',
array(
'name'=>'groupName'
'value'=>'getGroupNameById($data->group_id)',
//$data is members row accessible in gridview
),
),
));
Check this post for more details CGridView-Render-Customized-Complex-DataColumns
Related
In Cake 2 you could save a model and specify which fields you wanted to restrict the save to.
Is there a built-in way to do this simply in Cake 3? For example, if the request data is being put straight into a new entity which is then saved, how can I tell the method to only save the fields I allow?
Simplified Example
// User makes a request; their POST data goes directly into the entity
$customer = $this->Customers->newEntity($this->request->data);
$this->Customers->save($customer);
The obvious danger here is that I can set any properties I like on that customer entity, via the request. In reality, I only want to allowing saving of a couple of specific fields.
That's what mass assignment protection is there for, in the form of the $_accessible entity property
class Customer extends Entity
{
// allow only `first_name` and `last_name` to be mass assigned
protected $_accessible = [
'first_name' => true,
'last_name' => true
];
}
and the fieldList and accessibleFields options for Table::newEntity/newEntities/patchEntity/patchEntities()
// allow only `first_name` and `last_name` to be mass assigned,
// ignoring the entity accessible defaults
$customer = $this->Customers->newEntity($this->request->data(), [
'fieldList' => [
'first_name',
'last_name'
]
]);
The accessibleFields option will change the accessibility of the specified fields only. Also it will actually modify the entity, ie unlike fieldList, which the marshaller will simply use as the whitelist instead of the entity defaults, accessibleFields will change the values of the entities $_accessible property!
See
Cookbook > Database Access & ORM > Entities > Mass Assignment
Cookbook > Database Access & ORM > Saving Data > Changing Accessible Fields
API > \Cake\ORM\Table::newEntity()
API > \Cake\ORM\Table::patchEntity()
There are two ways of protecting you against this problem. The first one is by setting the default columns that can be safely set from a request using the Mass Assignment feature in the entities.
The _accessible property allows you to provide a map of properties and whether or not they can be mass-assigned. The values true and false indicate whether a field can or cannot be mass-assigned:
http://book.cakephp.org/3.0/en/orm/entities.html#entities-mass-assignment
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected $_accessible = [
'title' => true,
'body' => true,
'*' => false,
];
}
The second way is by using the fieldList option when creating or merging data into an entity:
// Contains ['user_id' => 100, 'title' => 'Hacked!'];
$data = $this->request->data;
// Only allow title to be changed
$entity = $this->patchEntity($entity, $data, [
'fieldList' => ['title']
]);
$this->save($entity);
You can also control which properties can be assigned for associations:
// Only allow changing the title and tags
// and the tag name is the only column that can be set
$entity = $this->patchEntity($entity, $data, [
'fieldList' => ['title', 'tags'],
'associated' => ['Tags' => ['fieldList' => ['name']]]
]);
$this->save($entity);
Using this feature is handy when you have many different functions your users can access and you want to let your users edit different data based on their privileges.
The fieldList options is also accepted by the newEntity(), newEntities() and patchEntities() methods.
For more :http://book.cakephp.org/3.0/en/orm/saving-data.html
I'm pretty deep into a complex Symfony2 project, with many entities and join tables in forms etc. but I'm having a strange issue with the "multiple" attribute within the form builder.
Basically I have a form where a user can add an illness to the CRM, and each illness can be attached to a specific store (depending on the language used). There is a list of stores that can be chosen by the user within this form, and the values are stored in a join table. However I only want one store to be chosen (i.e. a select drop down) rather than a multiple select list but using the false value for the multiple attribute throws an error which I will outline later.
Firstly, here is the buildForm() code in my Type.php file:
$builder->add('name' , 'text');
$builder->add('description' , 'textarea');
$builder->add('store', 'entity',
array(
'class' => 'AppBundle:Store',
'empty_value' => 'Choose store',
'property' => 'name',
'multiple' => false,
));
$builder->add('save', 'submit', array(
'attr' => array(
'class' => 'btn btn-primary'
),
));
And the entry for the store field in my Entity:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Store", inversedBy="illness", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="store_id", referencedColumnName="id")
* })
*/
public $store;
However, if I used the false declaration for the multiple attribute in the form Type, when the form is submitted I receive the following error:
Warning: spl_object_hash() expects parameter 1 to be object, string given
because it looks like it's passing the text value of the select box, rather than the relevant Entity. When set as a multiple select box (i.e. set to true) then it works fine and persists as it should.
My controller code:
$addIllness = new Illness();
$form = $this->createForm(new IllnessType($em), $addIllness);
$form->handleRequest($request);
if ($form->isValid()) {
$em->persist($addIllness);
$em->flush();
return $this->redirect($this->generateUrl('app_illness_table'));
}
Having it as a multiple select box is not the end of the world, though I'd rather have it as a drop down select so the user cannot select more than one store - rather than me having to add an error message or note to tell them otherwise.
If anyone has an ideas as to why this may be happening, or has encountered it before please let me know, I would be very grateful!
Thank you
Michael
I'm pretty new to yii and I bumped into the following problem. I have 2 related tables, ClientTicket and Product with the following structure:
ClientTicket
id
ticket_name
client_id
product_id
Product
id
type
model
brand
The two tables are related through a foreign key which binds ClientTicket.product_id to Product.id.
The Problem
In the admin view of the ClientTicket I've managed to include two of the Product columns (brand, model) and have the search box display for each of them, but the filtering isn't working as expected. Ex: When I search in either of the two search boxes(brand, model), the other one populates automatically with the same value I typed (so no search results).
The ClientTicket model:
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'product' => array(self::BELONGS_TO, 'Product', 'product_id'),
........
);
}
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
...
$criteria->compare('product.model',$this->product_id, true);
$criteria->compare('product.brand',$this->product_id, true);
...
$criteria->with=array(..., 'product',);
$criteria->together= true;
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
'pagination' => array('pageSize' => 10),
));
}
The ClientTicket Admin view file:
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'client-ticket-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'ticket_number',
'ticket_date',
array('name'=>'agent_id',
'header'=> 'Agent',
'value'=> '$data->ticket_agent->name',
'filter'=>CHtml::listData(Agent::model()->findAll(), 'name', 'name'),
),
...
array('name'=>'product_id',
'header'=> 'Product',
'value'=> '$data->product->model',
),
array('name'=>'product_id',
'header'=> 'Brand',
'value'=>'$data->product->brand'
),
Your productand brand columns both have the same name but different values. The filter obtains the field name from name unless you explicitly state it i.e create your own active field. In addition you are using the same attribute product_id to search both fields in your search function.
How to filter using related models has been answered at Yii - how can I search by a column from foreign/related key on admin page?
For other users reference, here's how i got it to work.
Again, Thank you topher for the prompt response. :)
Inside ClientTicket model:
declared a new public variable named $brand
added 'brand' to 'safe' 'on'=>'search' array, inside of rules() function
added a new criteria inside the search() function like so:
$criteria->compare('product.brand',$this->brand, true);
In the ClientTicket admin view file:
modified the product brand column from this:
array('name'=>'product_id',
'header'=> 'Brand',
'value'=>'$data->product->brand'
),
to this:
array('name'=>'product.brand',
'header'=> 'Brand',
'filter'=>CHtml::activeTextField($model,'brand'),
),
I need to create CRUD screen using yii-framework. Simple CRUD screen using one table is working perfectly fine. I'm facing problem/issue while using dropdown (linking table).
I have installed giix extension which is suppose to create CRUD with dropdown if FK is specified but I dnt have MySql Engine InnoDB on my hosting provider, so I'm not able to use that extension. I need to do it manually.
I have two tables
main:-
id
store_id
prefix
store:-
id
name
Now store_id of main is FK to id of store table. And I want to create CRUD for main table.
So that Add Screen should show:-
Store Name:- Dropdown
prefix:- Textbox
View screen should use name column of store table instead of showing store_id
Thanks in anticipation.
Generate CRUD using Gii, then read my blog. http://jmmurphy.blogspot.com/2013/05/using-yii-model-relations.html
Basically you will have something like this for your store_id field after Gii Generation
<?php echo $form->labelEx($model,'store_id'); ?>
<?php echo $form->textField($model, 'store_id', array('size'=>60,'maxlength'=>255));?>
<?php echo $form->error($model,'store_id'); ?>
The textField line is replaced by:
<?php $list = CHtml::listData(Store::model()->findAll(), 'id', 'name'); ?>
<?php echo $form->dropDownList($model, 'store_id', $list, array('empty'=>'(Select a Store)')); ?>
You also need to define relations in your Main model so that you can access related tables (even if your database does not support foreign keys) like this:
public function relations()
{
return array(
'store'=>array(self::BELONGS_TO, 'Store', 'store_id'),
);
}
And to complete this relation, you should also add the following relation to your Store model:
public function relations()
{
return array(
'main'=>array(self::HAS_MANY, 'Main', 'store_id'),
);
}
These relations define a One to Many relation between Store and Main where store is the parent, and Main is the Child. To make it a One to One relationship change HAS_MANY to HAS_ONE. The HAS_* goes in the parent model and points to the foreign key attribute in the child table. The BELONGS_TO goes in the child and tells the attribute in the child table that points to the primary key in the parent.
Now to see the store name in the view action, you need to change 'store_id' in your view.php to an array that looks like:
array(
'name' => 'store_id',
'value' => $model->store->name,
)
The admin view is slightly different. I am not sure exactly why, but to view the store name instead of the id in the admin view you will need to use an array that looks like:
array(
'name' => 'store_id',
'value' => '$data->store->name',
)
Note that Gii generates this so that $data is the model instead of $model, and also it does a funky double indirection thing so that you need to put the variable declaration in single quotes.
Thanks jmarkmurphy for your help. You helped me lot and I have marked your answer as correct only as you gave me guidance. Just posting exact code in detail.
I changed view.php with below code:-
<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'id',
'store.name',
'prefix',
),
));
Also changed admin.php with below code:-
<?php echo CHtml::encode($data->store->name); ?>
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'main-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'id',
'store.name',
'prefix',
array(
'class'=>'CButtonColumn',
),
),
)); ?>
Thanks once again jmarkmurphy. Thanks a lot . My application is now working and exactly the way I wanted.
I am currently trying to display/retrieve data from my database using Yii framework relations which were auto generated by Gii. (MANY_MANY)
User Model contains:
return array(
'memberOfTeams' => array(self::MANY_MANY, 'UsersTeam', '{{teamMembers}}(userId, teamId)'),
);
UserTeam Model contains:
return array(
'teamMembers' => array(self::MANY_MANY, 'User', '{{teamMembers}}(teamId, userId)'),
);
Currently I am working on the User view called profile.php . All I am trying to accomplish is to display the current user with all the teams assigned to him.
teamMembers contains teamId and userId.
How would I write this query?
I have this currently
<?php echo CHtml::dropDownList("teamName", 'id', Chtml::listData(UsersTeam::model()->with('teamMembers')->findAll(teamMembers.userId, array($model->id)), 'id', 'teamName'),array('empty'=>'Select Team')); ?>
I am able to get all the information if I use findAll(), but I want only teams that the user is assigned to.
A project of mine does something similar:
MANY_MANY relation between shop and card, this dataprovider is used to display a list of shops linked to a specific card:
$shopDataProvider=new CActiveDataProvider( 'Shop',
array(
'criteria'=>array(
'with'=>array('cardShop'),
'condition'=>'cardShop.card_id=:cardId',
'params'=>array(':cardId'=>$id),
'order'=>'t.id DESC',
'together'=>true,
),
)
);