I'm using CakePHP 2.6.7.
A user will be able to add to his profile multiple cars and multiple addresses. He will also be able to link multiple addresses to one car, and link one address to multiple cars.
I then have a User model with :
class User extends AppModel {
public $hasMany = array(
'Car',
'Address'
);
}
A Car model with :
class Car extends AppModel {
public $belongsTo = array(
'User'
);
public $hasAndBelongsToMany = array(
'Address' =>
array(
'unique' => 'keepExisting'
)
);
}
An an Address model :
class Address extends AppModel {
public $hasAndBelongsToMany = array(
'Car' =>
array(
'unique' => 'keepExisting',
),
);
public $belongsTo = array(
'User'
);
}
I have a form so that the user can edit his cars (only one for the moment) :
[...]
<legend>Mes voitures</legend>
<?php
for($nbrvoiture = 0; $nbrvoiture <= 0; $nbrvoiture++)
{
?><h3>Voiture <?php echo $nbrvoiture+1?></h3><?php
$myLabelOptions = array('text' => "Marque");
echo $this->Form->input('Car.'.$nbrvoiture.'.CarMake', array('label' => array_merge($mainLabelOptions, $myLabelOptions)));
$myLabelOptions = array('text' => "Modèle");
echo $this->Form->input('Car.'.$nbrvoiture.'.CarModel', array('label' => array_merge($mainLabelOptions, $myLabelOptions)));
$myLabelOptions = array('text' => "Plaque d'immatriculation");
echo $this->Form->input('Car.'.$nbrvoiture.'.NumberPlate', array('label' => array_merge($mainLabelOptions, $myLabelOptions)));
echo $this->Form->submit("Valider", array(
'class' => 'btn btn-default col-sm-offset-2'
));
}
The thing is that I can't save the data to my database.
Here is part of the User controller code :
function edit() {
// On récupère l'ID de l'utilisateur
$user_id = $this->Auth->user('id');
// Si l'utilisateur n'a pas d'ID => il n'est pas connecté => il ne peut pas éditer son profil
if(!$user_id){
$this->redirect('/');
die();
}
debug($this->User->find('all'));
$this->User->id = $user_id;
$passError = false;
if($this->request->is('put') || $this->request->is('post')){
$d = $this->request->data;
$d['User']['id'] = $user_id;
debug($d);
if($this->request['pass'][0]=='cars')
{
if($this->User->saveAssociated($d, array('deep' => true))){
$this->Session->setFlash(__("Les informations sur la voiture ont bien été modifiées"), 'alert', array (
'plugin' => 'BoostCake',
'class' => 'alert-success'
));
}else{
$this->Session->setFlash(__("Impossible de modifier ou d'ajouter les infos"), 'alert', array (
'plugin' => 'BoostCake',
'class' => 'alert-danger'
));
}
}
When I save the data, it shows the error.
In my database I have these 4 tables :
Users(id, username, mail, password, created, lastlogin, active, firstname, lastname, gender, birthdate, phonenumber)
Cars(id, CarMake, CarModel, NumberPlate, user_id)
Addresses(id, address, city, state, postcode, country, user_id)
Addresses_cars(address_id, car_id)
This is an example of what I can get from the form (the $d variable) :
array(
'Car' => array(
(int) 0 => array(
'CarMake' => 'Allard',
'CarModel' => '2005',
'NumberPlate' => '56QDS1'
)
),
'User' => array(
'id' => '1'
)
)
I don't understand why it doesn't work...
Can you help me please ?
Thank you ! :)
EDIT : I also tried with SaveAll but I can't get it to work.. what's wrong?
It's already been solved by OP, but for future CakePHPers, if you have a save() or saveAll() that isn't saving (checked by your if:else block), the most common problem is a validation error.
CakePHP 2.x Data Validation
CakePHP 3.x Data Validation
If that's not it, try looking at any behaviors you're using, and check for anywhere that should be returning true and isn't (like in it's `beforeSave() method).
Related
I'm trying to unit test a form in my Symfony application. A normal form will work fine, but I cannot test when I have a repeated form type.
The test:
class MoniteurCreationTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$formData = array(
'username' => 'user',
'plainPassword' => 'pass',
);
$type = new \AppBundle\Form\MoniteurCreationType();
$form = $this->factory->create($type);
$object = new Moniteur();
$object->setUsername($formData['username']);
$object->setPlainPassword($formData['password']);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($object, $form->getData());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
}
My Form Type:
class MoniteurCreationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', 'text', array('label' => 'Nom d\'utilisateur', 'attr' => array('placeholder' => 'Insérez ici votre nom d\'utilisateur')))
->add('plainPassword', 'repeated', array(
'type' => 'password',
'first_options' => array('label' => 'Mot de passe', 'attr' => array('placeholder' => 'Choisissez un mot de passe')),
'second_options' => array('label' => 'Répéter le mot de passe', 'attr' => array('placeholder' => 'Veuillez entrer encore une fois le mot de passe choisit')),
)
)
->add('envoyer', 'submit', array('attr' => array('class' => 'col-lg-12 col-xs-12 btn btn-primary submit')))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Moniteur'
));
}
public function getName()
{
return 'moniteurCreation';
}
}
The phpunit log:
There was 1 failure:
1) Tests\AppBundle\Form\MoniteurCreationTypeTest::testSubmitValidData
Failed asserting that two objects are equal.
--- Expected
+++ Actual
## ##
'plainPassword' => null
- 'password' => 'pass'
+ 'password' => null
'email' => null
'isActive' => null
'derniereConnexion' => null
'dateRecuperationMotDePasse' => null
)
C:\web\www\testDoctrine\src\AppBundle\Tests\Form\MoniteurCreationTypeTest.php:29
FAILURES!
Tests: 8, Assertions: 60, Failures: 1.
All the other formy types word, but here I become a null in my unit test from the form. Do you knows why?
Thanks
Best regards
There are a few things going on here.
Your $formData variable is using a key of password when the key should be plainPassword, which is your field name in MoniteurCreationType
This may not be the right place to test that values in your object are being set correctly - this seems like it would be better suited to a functional test. The form unit tests are merely to test the operational capabilities of the form, not setting values
The RepeatedType is made up of two other fields, normally named first and second. You can change those via the first_name and second_name options if you so chose.
If you really want to submit the proper data to your form, and then test that it has been set (if you dump($form->getData()); you'll see it), then you will need to create your form data as follows:
$formData = array(
'username' => 'user',
'plainPasword' => array('first' => 'pass', 'second' => 'pass),
);
I have searched many topic in here but I can't solve my problem. Please check this for me.
I made the register page and when I made for password field...
I had users_controller.php like:
class UsersController extends AppController
{
var $name = "Users";
var $helpers = array('Paginator','Html');
var $paginate = array();
//Doi tuong component de thuc thi thao tac login
public $components = array
(
'Auth' => array
(
'authorize' => 'controller',
'loginRedirect' => array
(
'admin' => FALSE,
'controller' => 'users',
'action' => 'dashboard'
),
'loginError' => 'Invalid account',
'authError' => 'You don\'t have permission'
),
'Session'
);
//Ham loc cac user truoc khi truy cap trang
public function beforeFilter()
{
parent::beforeFilter();
$this->Auth->allow('add');
$this->Auth->allow('viewuserall');
}
//Ham them moi user
public function add()
{
$this->layout = 'TDCake';
$this->User->set($this->data);
if($this->User->valid_user() == TRUE)
{
if(!empty($this->data))
{
$this->User->create();
if($this->User->save($this->data))
{
$this->Session->setFlash('User has been created!');
$this->redirect(array('action'=>'login'));
}
else
{
$this->Session->setFlash('Please correct the errors');
}
};
}
else
{
$this->Session->setFlash("Your data is NOT available");
}
}
//Ham login cho user
public function login()
{
$this->layout = 'TDCake';
if
(
!empty($this->data) &&
!empty($this->Auth->data['User']['username'])&&
!empty($this->Auth->data['User']['password'])
)
{
$user = $this->User->find
(
'first',array
(
'conditions'=>array
(
'User.email'=>$this->Auth->data['User']['username'],
'User.password'=>$this->Auth->data['User']['password']
),
'recursive' => -1
)
);
if(!empty($user) && $this->Auth->login($user))
{
if($this->Auth->autoRedirect)
{
$this->redirect($this->Auth->redirect());
}
}
else
{
$this->Session->setFlash
(
$this->Auth->loginError,
$this->Auth->flashElement,
array(),'auth'
);
}
}
}
//Ham logout cho user
public function logout()
{
$this->redirect($this->Auth->logout());
}
//Ham gi cha biet, de do tinh sau =))
public function dashboard()
{
$this->layout = 'TDCake';
}
//Ham view cac user khong dieu kien trong table users
function viewuserall()
{
$this->layout = 'TDCake';
$this->paginate=array
(
'limit' => 10,
'order' => array('id' => 'asc'),
);
$data = $this->paginate("User");
$this->set("data",$data);
}
}
User.php in Model is:
class User extends AppModel
{
var $name = "User";
var $validate = array();
function validate_passwords()
{
if($this->data[$this->alias]['pass'] == $this->data[$this->alias]['rpass'])
{
return $this->data[$this->alias]['pass'] = $this->data['User']['password'];
}
else return FALSE;
}
function valid_user()
{
$this->validate = array
(
//Kiem tra username truoc khi add
'username' => array
(
'rule01_notEmpty' => array
(
'rule' => 'notEmpty',
'message' => 'You must enter your Username !'
),
'rule02_max16' => array
(
'rule' => array('maxLength', 20),
'message' => 'Your Username must be less than 20 chars !'
),
'rule03_exists' => array
(
'rule' => 'isUnique',
'message' => 'Your Username have already existed !'
)
),
//Kiem tra email truoc khi add
'email' => array
(
'rule01_notEmpty' => array
(
'rule' => 'notEmpty',
'message' => 'You must enter your Email !'
),
'rule02_exists' => array
(
'rule' => 'isUnique',
'message' => 'Your Email have already existed !'
),
'rule03_emailtype' => array
(
'rule' => 'email',
'message' => 'You didn\'t type a email !'
)
),
//Kiem tra password truoc khi add
'pass' => array
(
'length' => array
(
'rule' => array('between', 6, 20),
'message' => 'Your password must be between 8 and 40 characters.',
),
),
'rpass' => array
(
'length' => array
(
'rule' => array('between', 6, 20),
'message' => 'Your password must be between 8 and 40 characters.',
),
'compare' => array
(
'rule' => 'validate_passwords',
'message' => 'The passwords you entered do not match.',
)
)
);//End this->validate=array
if($this->validates($this->validate==TRUE))
{
return TRUE;
}
else
{
return FALSE;
}
}//End function valid_user
}
add.ctp is
echo $this->Session->flash('auth');
echo $this->Form->create();
echo $this->Form->input('username', array('label' => ('Username')));
echo $this->Form->input('email', array('label' => ('Email')));
echo $this->Form->input('pass', array('label' => ('Password'),'type' => 'password', 'value' => ''));
echo $this->Form->input('rpass', array('label' => ('Repeat Password'), 'type' => 'password', 'value' => ''));
echo $this->Form->input('firstname', array('label' =>('Firstname')));
echo $this->Form->input('lastname', array('label' =>('Lastname')));
echo $this->Form->input('dob', array('label' =>('DOB'),'type' => 'date'));
echo $this->Form->end('Register');
Explanation:
So, in this case, I can validate 2 Password Fields (empty, not equal,...), but it can't insert to the database. That mean it INSERTED current data into the DB but password column in DB is EMPTY. In database, my password column name "password" also.
In another case, I change the name "pass" into "password" for the
echo $this->Form->input('pass', array(
Of course, I have changed any place related to...
and in that case, it can be inserted the password but can not validate anything.
I am too confused about this...I don't know what I am wrong is....can anybody help me.
I am not sure why you are doing an assignment in your validate function:
return $this->data[$this->alias]['pass'] = $this->data['User']['password'];
And even if you were doing an assignment, it should be:
return $this->data['User']['password'] = $this->data[$this->alias]['pass'];
Realize that the field "password" is getting the value from $this->data which has the information, not the other way around.
Also. It would be better (in terms of clarity), to break this code in two lines.
$this->data['User']['password'] = $this->data[$this->alias]['pass'];
return $this->data['User']['password'];
You should name your field the exact name "password" if that's what it is called in the database AND if you are not explicitly assigning it.
Your add function is not doing the above, and further more, as a best practice, you should be hashing the password.
See the CakePHP book on tutorials and examples.
Take some time to go through it with all the snippets and recommendations. And don't forget the standards. :)
I have a site develop in cakephp 2.0.
I have a relation HABTM to the same model like this:
class Product extends AppModel {
public $name = 'Product';
public $useTable = 'products';
public $belongsTo = 'User';
public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Product' => array(
'className' => 'Product',
'joinTable' => 'ingredients_products',
'foreignKey' => 'product_id',
'associationForeignKey' => 'ingredient_id',
'unique' => false
)
);
}
I want to save a record into my view with a simple form like this:
echo $this->Form->create('IngredientProduct', array ('class' => 'form', 'type' => 'file'));
foreach ($product as $prod) {
echo '<div>'.$prod['ProductAlias']['alias'].'</div>';
echo $this->Form->input('IngredientProduct.product_id', array ('type'=>'text', 'value'=> $prod['ProductAlias']['id'], 'label'=> false, 'id' => 'id'));
}
$select = '<select name="data[IngredientProduct][ingredient_id]" id="[IngredientProductIngredientId">';
foreach ($other_product as $prod2) {
$select .= '<option value="'.$prod2['ProductAlias']['id'].'">'.$prod2['ProductAlias']['alias'].'</option>';
}
$select .= '</select><br>';
echo($select);
echo $this->Form->submit('Collega', array('id'=>'link_product'));
echo $this->Form->end();
Into my controller I save in this mode:
if ($this->Product->saveAll($this->request->data)){
$this->Session->write('flash_element','success');
$this->Session->setFlash ('Prodotto collegato con successo.');
//$this->redirect(array('action'=>'edit',$alias));
}
else{
$this->Session->write('flash_element','error');
$this->Session->setFlash('Errore di salvataggio activity');
}
When I'm going to see into the database I see that ingredient:id is setting well but product_id is 0.
I have debugged my request->data and this is the array:
array(
'IngredientProduct' => array(
'product_id' => '1',
'ingredient_id' => '2'
)
)
I have print the sql query created by cakephp:
INSERT INTO `db`.`ingredients_products` (`product_id`, `ingredient_id`, `modified`, `created`) VALUES ('', 2, '2012-10-09 23:19:22', '2012-10-09 23:19:22')
Why product_id is null instead of 1?
Can someone help me?
Thanks
I think this line is wrong:
$this->Product->saveAll($this->request->data);
Try:
$this->IngredientProduct->saveAll($this->request->data);
as your form seems to ask data for a relationship, not a new product.
Ok this is kind of hard to explain but I'll try my best.
I have 3 tables
companies products product_availabilities
--------- -------- ----------------------
id id id
name name company_id
product_id
buys (tinyint)
sells (tinyint)
And their models
class Company extends AppModel
{
public $name = 'Company';
public $hasMany = array(
'ProductAvailability'
);
class Product extends AppModel
{
public $name = 'Product';
public $hasMany = array(
'ProductAvailability'
);
class ProductAvailability extends AppModel
{
public $name = 'ProductAvailability';
public $belongsTo = array(
'Company',
'Product'
);
}
What I want to do is when I create a company, I want to be able to select products that the company buys or sells. I've seen an example of a hasMany through relationship in the book (http://book.cakephp.org/1.3/view/1650/hasMany-through-The-Join-Model) but they are creating the form from the "join table" controller. Is it possible to bind the productAvailability model to my company model to be able to select the products while creating the company?
Edit : Here is how I've done it. I know it is not optimal as there is a lot of looping involved but it works.
Company controller :
$products = $this->Company->ProductAvailability->Product->find('list', array('fields' => array('Product.id', 'Product.label')));
$this->set('products', $products);
if($this->request->is('post')){
if($this->Company->save($this->request->data)){
foreach($products as $product)
{
$tmpArray = array(
'company_id' => $this->Company->id,
'product_id' => $product['Product']['id']
);
foreach($this->request->data('BuyProducts.product_id') as $buyProduct)
{
if($buyProduct == $product['Product']['id'])
$tmpArray['buys'] = 1;
}
foreach($this->request->data('SellProducts.product_id') as $sellProduct)
{
if($sellProduct == $product['Product']['id'])
$tmpArray['sells'] = 1;
}
if(count($tmpArray) > 2)
{
$this->Company->ProductAvailability->create();
$this->Company->ProductAvailability->set($tmpArray);
$this->Company->ProductAvailability->save();
}
}
$this->Session->setFlash('Yay', 'success');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('Nay', 'error');
}
}
Company add form :
<?php echo $this->Form->create('Company'); ?>
<?php echo $this->Form->input('name', array( 'div' => 'full-form')); ?>
<?php echo $this->Form->input('BuyProducts.product_id', array('multiple' => 'checkbox', 'options' => $products, 'div' => 'full-form', 'label' => false)); ?>
<?php echo $this->Form->input('SellProducts.product_id', array('multiple' => 'checkbox', 'options' => $products, 'div' => 'full-form', 'label' => false)); ?>
<?php echo $this->Form->end(array('label' => __('Save'), 'div' => 'center', 'class' => 'bouton-vert')); ?>
You have two options. Either let cakePHP do some magic with the hasAndBelongsToMany relationship or doing it manually which is necessary if you add attributes to the join table
1. CakePHP HABTM
Using the capabilities of CakePHP and making a straight forward solution I would make these changes:
Model
If one company has more than one product, and the products belong to many companies. It is a hasAndBelongsToMany relationship between Company<->Product
// company.php
...
var $hasAndBelongsToMany = array(
'Product' => array(
'className' => 'Product',
'joinTable' => 'companies_products',
'foreignKey' => 'company_id',
'associationForeignKey' => 'product_id',
'unique' => true,
)
);
...
// similar in product.php
Add the required table 'companies_products' in the database.
Controller
Then in the add function from the Company Controller there should be something like:
$products = $this->Company->Product->find('list');
$this->set(compact('products'));
View
Finally insert the products in the add.ctp, the select should allow multiple selections and let cakePHP do some magic, like this:
echo $this->Form->input('products', array(
'label' => 'Products to buy (Ctr+multiple choice)'
'type' => 'select',
'multiple' => true,
));
2. Manually
When the HABTM becomes more 'exotic' and includes some attributes like in your case 'buy' or 'sell' you need to do the manual way. This is in the Product Controller setting manually the fields before inserting them in the database. Something like:
foreach($availableProducts as $availableProduct ){
$this->Product->ProductAvailabilities->create();
$this->Product->ProductAvailabilities->set( array(
'company_id' => $this->Product->id,
'product_id' => $availableProduct['Product']['id'],
'buys' => $availableProduct['Product']['buy'],
'sells' => $availableProduct['Product']['sell']
// or however you send it to the controller
));
$this->Product->ProductAvailabilities->save();
}
Let's hope this helps you ...
you are planning a habtm-relationship (m:n) with the possibility to have extra fields in the join table. Even though this can be done with regular habtm I prefer the way you choose and implement the m:n as 1:n:1, which is simply the same and gives you more options when saving your data.
Here is a similar question and answer
As for your question: You can collect the the data from your products table like this:
$this->Company->ProductAvailability->Product->find('all', $params);
Also you might want to have a look at the containable-behaviour which is very useful for this use case:
$params['conditions'] = array(
'Company.id' => $id
);
$params['contain'] => array(
'ProductAvailability' => array(
'conditions' =>array(
'buys' => 1
),
'Product' => array(
'order' => array(
'name' => 'ASC'
)
)
)
);
$this->Company->find('all', $params);
I have my validation rules in model, and everything is fine. It validates like I want to, but in the Edit actions, although it not validates, don't show me de red error marks under textbox.
Any tip?
Thanks.
The Model Code(Model name is Safpercent):
var $validate = array(
'sequencia' => array(
'must_be_numeric' => array(
'rule' => 'Numeric',
'message' => 'Number Field: insert only numbers.'
)
),
);
View Text Box:
echo $form->input('Safpercent.sequence', array('id' => 'sequence', 'options' => $criteria, 'label' => false, 'div' => false, 'style' => 'width: 300px'));
Controller Code:
function edit($id = null) {
$criteria = $this->Safpercent->Safrequirement->find('list', array('fields' => array('Safrequirement.sequencia', 'Safrequirement.descricao'), 'conditions' => array('Safrequirement.tipo' => 'ILC')));
$this->set('criteria', $criteria);
if (!$id && empty($this->data)) {
$this->Session->setFlash(RecordNotValid, 'flash_failure');
$this->redirect(array('controller' => 'safpercents', 'action'=>'index'));
}
if (!empty($this->data)) {
$sequencia = $this->data['Safpercent']['sequencia'];
if($this->data['Safpercent']['tipo'] == ''){$tipo = 'ILC';}else{$tipo = $this->data['Safpercent']['tipo'];}
$encontro = $this->Safpercent->Safrequirement->find('all', array('conditions' => array('sequencia' => $sequencia, 'tipo' => $tipo)));
if($encontro <> array()){
if ($this->Safpercent->save($this->data)) {
$this->Session->setFlash(RecordSaved, 'flash_success');
$this->redirect(array('controller' => 'safpercents', 'action'=>'index'));
}else{
$this->Session->setFlash(RecordNotSaved, 'flash_failure');
}
}else{
$this->Session->setFlash('A Sequência que tentou Inserir não existe. Verifique a tabela de novo, por favor.');
}
}
if (empty($this->data)) {
$this->data = $this->Safpercent->read(null, $id);
$this->set('id', $id);
}
$this->set('cod_percent',$this->Safpercent->read(null, $id));
}
(Portuguese Variables and Text in some cases)
Try
debug($this->Safpercent->validationErrors)
and see if it shows any errors.
I see now. In your controller, you are using the sequencia field that you are validating, but not doing any validation at this stage. It passes a non-number to the find query, which then returns an error or something, and the save never gets called?
Before you do this:
$sequencia = $this->data['Safpercent']['sequencia'];
You should check that the data validates, by calling this:
$this->ModelName->set($this->data);
if ($this->ModelName->validates()) {
... //do your business here
So basically, change:
if (!empty($this->data)) {
to:
$this->Safpercent->set($this->data);
if (!empty($this->data) && $this->Safpercent->validates()) {