CakePHP multi-model view - cakephp

I am creating a website in CakePHP and I am kind of new on it. I couldn't find good resources on this matter, so there you go:
I have a three table structure for registering users: Users, Addresses and Contacts. I have to build a view with info of all three tables like:
Full Name: [ ] (from Users)
Shipping Address: [ ] (from Address)
Mobile Phone: [ ] (from Contact)
e-Mail Address: [ ] (from Contact)
What is the best way to deal with this situation. Specially for saving. Creating a new Model to represent this, that will have a save() method itself (Maybe a sql view in the database) Create a Controller to deal with this View that binds or unbinds info
I wonder still how I will handle both contacts as they will be 2 different INSERT's
Any hint or resources I can dig of I will be glad.

If your using the latest 1.2 code, check out Model::saveAll in the api
eg. Your view might look something like this:
echo $form->create('User', array('action' => 'add');
echo $form->input('User.name');
echo $form->input('Address.line_1');
echo $form->input('Contact.tel');
echo $form->end('Save');
Then in your Users controller add method you'd have something like:
...
if($this->User->saveAll($this->data)) {
$this->Session->setFlash('Save Successful');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('Please review the form for errors');
}
...
In your User model you will need something like:
var $hasOne = array('Address', 'Contact');
Hope that helps!
http://api.cakephp.org/class_model.html#49f295217028004b5a723caf086a86b1

CakePHP allows you to easily maintains link between your models using relationship, see http://book.cakephp.org/view/78/Associations-Linking-Models-Together for the complete detail. Then retreiving the right User, you'll also get "for free", its address and contact informations.

3 models : User, Address, Contact
User hasOne Address, Contact
Address belongsTo User
Contact belongsTo User
in your model you define this like this :
class User extends AppModel {
var $name = 'User';
var $hasOne = array('Address','Contact');
..
To make this view, you need user_id field ind addresses, and contacts tables
To use this in a view, you simply call a find on the User model with a recursive of one (and btw, the users controller only uses User model).
$this->User->recursive = 1;
$this->set('user', $this->User->find('first', array('conditions'=>array('id'=>666)));
This will result in this array for your view :
array(
'Use' => array(
'id' => 666,
'name' => 'Alexander'
),
'Address' => array(
'id' => 123,
'zip' => 555
),
'Contact' => array(
'id' => 432,
'phone' => '555-1515'
));

Related

How to add link to show action of the relation entity on relation field in SonataAdminBundle

Im making admin panel in SonataAdminBundle.
In User show action i have field companies which return array of companies assigned to the user.
It is a OneToMany relation. UserCompany has user_id and company_id.
I want to create link on each returned company name, which points to it's entity show action.
This is code from configureShowFields() function in UserAdmin class:
->with('Assigned organizers',['class' => 'col-md-6'])
->add('companies', null, [
'label' => 'Organizers',
])
->end()
I managed to create a link on a string field pointing to show action of an entity, but the id property is taken from the current entity view:
->with('Address', ['class' => 'col-md-6'])
->add('userProfile.locality', 'url', [
'route' => [
'name' => 'admin_app_employee_show',
'identifier_parameter_name' => 'id'
],
'label' => 'Localiy',
])
What's more Sonata Admin create links on related fields, when the relation is direct,
for example:
Company has many Employee. Then in Company show action on employees field
I see array with links already heading to edit action of Employee entty.
Maybe there is a possibility to override template for this field, but it seems unclear for me,
as the documentation lacks of more advanced examples.
This is how I tried to test overriding the template of a field:
->add('userProfile.street', null, array(
'label' => 'Street',
'template' => 'custom-field.html.twig',
))
Location of the template: App/templates/Admin/
Any help appreciated
SonataAdmin automatically creates links to related entities once it has all of them configured and added to services.
Then you can just change the route action of the link on the relation field as following:
->with('Assigned events', ['class' => 'col-md-6'])
->add('events', null, [
'route' => [
'name' => 'show'
],
'label' => 'Events',
])
->end()
You can also change the type of relation field eg.'many_to_one' instead of null which might help in some cases.

CakePHP 3.x - Save many-to-many data

I am attempting to patchEntity with a join table, but I am unable to get it to save the associated records, and I think I have the backend code correct, but I am unsure of what the frontend code should look like...
Here is the scenario, I have three tables
Ledgers Table
id int
title string
Tribes Table
id int
name string
LedgersTribes Table
id int
ledger_id int
tribe_id int
Here is my backend code
public function ledgerSave($id = null)
{
$this->loadModel('Ledgers');
$ledger = $this->Ledgers->get($id, [
'contain' => ['LedgersTribes']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$ledger = $this->Ledgers->patchEntity($ledger, $this->request->getData(), [
'associated' => ['LedgersTribes']
]);
if ($this->Ledgers->save($ledger)) {
$this->Flash->success(__('The ledger has been saved.'));
return $this->redirect($this->referer());
}
$this->Flash->error(__('The ledger could not be saved. Please, try again.'));
}
$this->set(compact('ledger'));
}
Here is the relevant frontend code
<?= $this->Form->control('tribe_id[]', ['type' => 'checkbox', 'label' => false, 'class' => 'minimal', 'value' => $tribe->id]) ?>
My question is, what should the field name be for the tribe_id, the idea is, i have a list of checkboxes and the user checks off a couple of boxes and then those tribe_id's get inserted into the LedgersTribes table with the ledger_id
Any ideas on how I can do this?
EDIT: Here is a screenshot of the form
I have reviewed the following links, and none of them answer my question...
CakePHP 3: Save Associated Model Fail
Save associated in cakephp 3 not working
How to save associated joinData in cakephp 3.x
CakePHP 3 cannot save associated model
Cakephp 3 - Save associated belongsToMany (joinTable)
This should do:
echo $this->Form->control('tribes._ids[]', [
'type' => 'checkbox',
'label' => false,
'class' => 'minimal',
'value' => $tribe->id]
]);
This is described here: https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-inputs-for-associated-data
I think you have to get the table (ex: TableRegistry::getTableLocator()->get('')) where you want to save the data. Then create entity from that and save the data, hopefully it will work.

CakePHP 2 separate login tables

I have a Cake website and it needs to have two separate logins, each one will have their own login form and see different pages, it would be nice to have two different tables because there are no similarities between the two types of people.
Each login form will only be used by certain people and they will never login to the other form, and vice versa.
Also, the two login tables have a relationship between them, which requires 2 tables?
Is this possible?
First, add a couple of empty custom authenticate objects. We'll reuse the same logic that FormAuthenticate uses (that is, uses POST data to check the database for a user), but simply change the model within the object settings (later).
app/Controller/Component/Auth/ModelOneAuthenticate.php
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class ModelOneAuthenticate extends FormAuthenticate {
}
app/Controller/Component/Auth/ModelTwoAuthenticate.php
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class ModelTwoAuthenticate extends FormAuthenticate {
}
Then tell your app to use these objects to authenticate, and tell it what model to use. You can also customize the fields here. In your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array(
'ModelOne' => array(
'userModel' => 'ModelOne',
'fields' => array(
'username' => 'my_custom_username_field',
'password' => 'some_password_field'
)
),
'ModelTwo' => array(
'userModel' => 'ModelTwo'
)
)
)
);
The first authentication object would check the model_ones table for a username in my_custom_username_field and password in some_password_field, while the second one would check model_twos using the standard username and password fields.
The simplest way to do this is to just set a different session key for each login type:
if ($loginTypeOne) {
$this->Auth->authenticate = array(
'Form'=> array(
'userModel'=> 'TypeOne',
)
);
AuthComponent::$sessionKey = 'Auth.TypeOne';
} else {
$this->Auth->authenticate = array(
'Form'=> array(
'userModel'=> 'TypeTwo',
)
);
AuthComponent::$sessionKey = 'Auth.TypeTwo';
}
When they have to login there is a similarity: Both will require it to enter credentials, usually an username/email and password. So a users table and a foo_profiles table and a bar_profiles table depending on the user type should work also.
If you really want to go with two total different tables and the MVC stack for them, then simply use two different controllers FooUsers and BarUsers and inside of each create a customized login method.
I have done this previously by writing custom Authentication components that extend from BaseAuthenticate. As long as they implement the authenticate() method then you'll be able to do whatever you want to each of the different types of user.
In your AppController you need to register the different components by doing something like
public $components = array(
"Session",
"Auth" => array(
'authenticate' => array("UserType1", "UserType2"),
)
);
Check out the cookbook for the rest of it.
You can have a look on this.
Define Model for both login member and then define table which you want to use for the user.
set variable in model.
class SearchedCategory extends AppModel {
var $name = 'SearchedCategory';
Var useTable = 'give your table name here.';
var $primaryKey = 'id';
}

CakePHP - How to retrieve deeply associated data?

I have the following models:
User:
hasOne Profile, Page
hasMany Comment
Comment:
belongsTo User, Page, Profile
Page:
belongsTo User
hasMany Comment
Profile:
belongsTo User
When I retrieve a page, I want to get the associated Comments, and for each comment I want the Profile.
My comments table has fields page_id and user_id.
My profile table has user_id.
So, I assume I need to do something like
Comment belongsTo
'Profile' => array(
'conditions' => array('Profile.user_id' => 'Comment.user_id')
)
but that's not working - it returns a blank profile record.
I am using CakePHP 2.0.
Use CakePHP's Containable behavior [link]. Basically, this allows you to pick and choose which related models you want to include in your find.
You can also specify which field(s) you want from each model, specify conditions on each...etc.
It should look something like this:
//PageModel
public $actsAs = array('Containable');
public function getPage($id=null) {
$this->recursive = -1; //often set to -1 in the AppModel to make -1 the default
$this->find('all', array(
'conditions' => array(
'Page.id' => $id
),
'contain' => array(
'Comment' => array(
'User' => array(
'Profile',
),
),
)
));
}
If the relations are declared correctly, all the data you need can be retrieved by searching for the desired Page. But to limit the quantity of data returned, you will probably have to use te ContainableBehavior.
PageModel:
var $actsAs = array('Containable');
PagesController:
function view($id)
{
$this->Page->contain(array('Comment' => array('User', 'Profile')));
$page = $this->Page->find('first', array('conditions' => array('Page.id' => $id)));
}
EDIT
Sharon, according to your question it seems that Comments are linked to Users and to Profiles. But if Profile means a "user profile", the link between Comment and User is enough and the contain should then be:
$this->Page->contain(array('Comment' => array('User' => array('Profile'))));

CakePHP HABTM Form Submission

I have two tables, questions and tags, that have a HABTM relationship. When adding a question, I want to be able to specify a tag for the question (this would just be the first tag, with ability to add more tags later). The tags are pulled from their table. How can I configure my application so that when a question is added and a tag is specified, the join is reflected in the join table (questions_tags)?
Here is my question add action code :
function add() {
$tags = $this->Question->Tag->find('all');
$this->set('tags',$tags);
if (!empty($this->data)) {
$this->Question->create();
if ($this->Question->save($this->data)) {
$this->Session->setFlash(__('The question has been saved', true));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The question could not be saved. Please, try again.', true));
}
}
$users = $this->Question->User->find('list');
$tags = $this->Question->Tag->find('list');
$this->set(compact('users', 'tags'));
}
and here is my question add view code:
<?php
echo $this->Form->create('Question');
echo $this->Form->input('user_id',array('type' => 'hidden', 'value' => $this->Session->read('Auth.User.id')));
echo $this->Form->input('title');
echo $this->Form->input('details',array('type' => 'textarea'));
echo $this->Form->input('tag_id');
echo $this->Form->end(__('Submit', true));
?>
First make sure that your models are set up the right way. The fact that a user initially just adds one tag to your question does not change the fact that you should have a HABTM relation between the Question model and the Tag model (because you want the possibility add more tags later).
If your $this->data array is build according to the following schema:
$this->data = array(
'Question' => array(
'name' => 'Trick question'
),
'Tag' => array(
'Tag' => array(1,2,3)
)
);
Then a $this->Question->save() will save the Question data as well as the related Tag data (in this case Question 'Trick Question' with Tags with the id 1, 2 and 3).
Maybe take one step back and bake your Models, Views and Controllers for these two models (again) and see what Cake makes out of it. If I'm correct you'll just need a $this->Form->input('Tag') somewhere in your form (and if that not fills in the right data automatically you want to fill the options parameter with the result of $this->Question->Tag->find('list')).
if you have a single tag for a question, it's not HABTM. it has to be one to one or one to many relation.
in your questions model you can define property belongsTo:
class Question extends AppModel {
var $name = 'Question';
var $belongsTo = array(
'Tag' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id'
)
);
}
Something like this.
here is a link describing how to set HABTM
HABTM

Resources