I am stuck at the loop function of cakephp.
The logic is I need to compare the data entered by users with the data already in a table. I have two tables, one is Bookings and one is Inventories_Bookings. Below is my coding but it doesnot work. any help! Thanks
public function add2() {
if ($this->request->is('post')) {
foreach ($invbook as $invenbook)
{
if ($this->request->data['Booking']['bookings_location'] == $invenbook['InventoriesBooking']['test'])
{
$this->Session->setFlash(__('The booking cannot be created'));
$this->redirect(array('action' => 'add2'));
debug($this->request->data['Booking']['bookings_location'] == $invenbook['InventoriesBooking']['test']);
}
}
$this->Booking->create();
$invbook = $this->Booking->InventoriesBooking->find('list',array('fields' => array('InventoriesBooking.id', 'InventoriesBooking.test')));
$this->set(compact('invbook'));
}
}
I would use a custom validation function for this.
You are able to create your own functions in the model, and from here you can access the database to do the lookup. If it matches you can return true.
You can read about custom validation methods in the book.
There is an example of a rule like this using the db in the book.
Quoted for great justice.
class User extends AppModel {
public $validate = array(
'promotion_code' => array(
'rule' => array('limitDuplicates', 25),
'message' => 'This code has been used too many times.'
)
);
public function limitDuplicates($check, $limit) {
// $check will have value: array('promotion_code' => 'some-value')
// $limit will have value: 25
$existing_promo_count = $this->find('count', array(
'conditions' => $check,
'recursive' => -1
));
return $existing_promo_count < $limit;
}
}
Related
Well,
I created a Model with the following restriction
public $validate = array(
'player_id' => array(
'rule' => array(
'checkUnique',
array(
'player_id',
'game_id'
),
true
),
'required' => true,
'allowEmpty' => false,
'on' => 'create',
'message' => 'Same player_id y game_id'
)
);
So each time I try to create a game record in the table it is created only if it is not created yet.
So I created an action in one controller that get recent games of one player and use saveAll to save into the database.
If the database is empty there is no a single problem, of course. But if I receive some games and some of them are already being inserted previously saveAll fails because SOME of the games are already into the database.
public function getRecentGames($server = null, $player = null){
$this->autoRender = false;
if( !empty($server) && !empty($player) ){
$r = $this->_getRecentGames($server, $player, $gamesData);
if ($r['code'] == 200) {
if ($this->Game->saveAll($gamesData, array('deep' => true))) {
pr($gamesData);
prd('Saved');
} else {
pr($this->Game->invalidFields());
prd('Not saved');
}
} else {
}
}
return print_r($gamesData, true);
}
Basically saveAll(..) calls internally validateMany(..) which returns false because not every entry is valid and saveAll does not try to save. This is the normal behavior of CakePHP and the way developers want it to work.
So, what should I do?
Check each game and try to save it?
foreach ($games as $game) {
$this->Model->saveAssociated(..);
}
Modify the behavior of saveAll(..) in order to save the valid games and not the invalid ones. (Do you think this should be the default behavior of CakePHP?)
Other solutions I didn't think(?). Please show me then
Thank you
Well this is the best approach I could think of:
$validations = $this->Game->validateMany( $gamesData, array('deep' => true, 'atomic' => false) );
for ($i=count($gamesData)-1; $i>=0; $i--) {
if (!$validations[$i]) {
unset($gamesData[$i]);
}
}
if (!empty($gamesData)) {
$result = $this->Game->saveAll($gamesData, array('deep' => true, 'validate' => false));
}
I have two tables:
Property (..., postcode_id, ...)
Postcode (id, postcode, latitude, longitude)
The relationship:
class Property extends AppModel {
public $belongsTo = array(
'Postcode' => array(
'className' => 'Postcode'
)
);
class Postcode extends AppModel {
public $hasMany = array(
'Property' => array(
'className' => 'Property'
)
);
What I'd like to happen:
Enter a postcode in the Property add form.
Check entered postcode is in the Postcode table.
If not, so some logic (possibly add it, just return validation error for now).
If so, then record the Postcode.id in Properties.postcode_id (the foreign key).
I can't figure out the best way to do this with cake. A custom Property validation function that does the check and adds it to data to be added? Or in beforeValidation? Or does Cake deal with this?
Thanks!
EDIT
Based on the answer, I think this is the best solution...
I think this is the better way to do it, as it validates too. Thanks Anubhav for the putting me on the right track!
As per Anubhav's answer, in the Postcode model:
public function checkPostcode($postcode = null){
$record = $this->find('first', array(
'conditions'=>array('postcode' => $postcode),
'fields' => array('id')
));
if (!empty($record)){
return $record['Postcode']['id'];
} else {
return false;
// Logic for finding and adding new postcode
}
}
But and in the Property model:
public $validate = array(
'postcode' => array(
'exists' => array(
'rule' => 'postcodeExists',
'message' => 'Postcode does not exist'
)
)
);
public function postcodeExists($check) {
$id = $this->Postcode->checkPostcode($check);
if ($id) {
$this->data['Property']['postcode_id'] = $id;
return true;
}
return false;
}
public function beforeSave($options = array()) {
unset($this->data['Property']['postcode']);
}
By modifying the values to save here, the controller is kept skinny, and, rather than making sure postcode is real then finding the id, both are done at once, so only one query is needed.
Follow the steps:
Step 1: Create one action in Postcode.php
function checkPostcode($postcodeValue = null){
$returnData = $this->find('first',
array('conditions'=>array('postcode'=>$postcodeValue),
'fields'=>array('id'))
);
if(!empty($returnData)){
return $returnData['Postcode']['id'];
}else{
$saveData['Postcode']['postcode'] = $postcodeValue;
$this->save($saveData);
return $this->getLastInsertID();
}
}
The above function will check postcode from postcode model and insert postcode if not exist and returns the id of postcode table for corresponding row.
Step 2: Call this function controller action as
function someFuntion(){
$postcode_id = $this->Postcode->checkPostcode($userInputPostcodeHere);
// Write code to save property here
}
Hope this will give you some idea about the logic...
I have been searching all over, found a few "solutions" to this, some even made me re-write most of my InputFilter and add a lot of stuff to my Module.php and/or module.config.php... With no luck whatsoever... Just couldn't make it work for me, still got all sort of errors.
I decided to undo everything and start from scratch (the way my code initially looked, before getting to validate form entries against the db) and ask my question here.
I am doing a registration process.
Of course, I need to validate the email address against existing records in my users table (no 2 identical email addresses should be allowed).
Sure, in my database I have that column set to only accept unique values... but I also have to validate it and give the user the appropriate message on form submit, before I actually do anything with the database.
How do I use Db\NoRecordExists (or any other Db validator for that matter)?
What should I further write in my code (add/edit)?
I've pasted all my code below.
The form element I need to add the Db\NoRecordExists validator is 'user_identifier'.
This is my /config/autoload/global.php :
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=my_database_name;host=localhost',
'username' => 'my_user',
'password' => 'my_password',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
This is my Registration form (/module/User/src/User/Form/RegisterForm.php) :
namespace User\Form;
use Zend\Form\Form;
class RegisterForm extends Form {
public function __construct() {
parent::__construct('register');
$this->setHydrator(new \Zend\Stdlib\Hydrator\Reflection());
$this->setObject(new \User\Entity\Users());
$this->setAttributes(array(
// not important for my issue
));
$this->setInputFilter(new \User\Form\RegisterFilter);
// User Identifier
$identifier = new \Zend\Form\Element\Email();
$identifier->setName('user_identifier');
$identifier->setAttributes(array(
'id' => 'user-email',
'placeholder' => 'Email',
'class' => 'form-control'
));
$identifier->setLabel('Your email:');
/*
* Many other fields were here, but to make the sample code
* shorter here, I've only left one of the fields I need to
* validate against the database
*/
$this->add($identifier); // User's email - used for login
// Submit
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Register',
'class' => 'btn btn-primary',
),
));
}
}
And here is my RegisterFilter (/module/User/src/User/Form/RegisterFilter.php) :
namespace User\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Zend\Db\Adapter\Adapter;
class RegisterFilter extends InputFilter {
public function __construct() {
// User Identifier (Email)
$this->add(array(
'name' => 'user_identifier',
'required' => true,
'filters' => array(
array('name' => 'StringTrim'),
),
'validators' => array(
/*
* Some validators here (NotEmpty, EmailAddress)
*/
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => 'users',
'field' => 'user_identifier',
/*
* 'adapter' => had many examples for what to put here, like:
* \Zend\Db\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter()
* and for that I also had to put:
'Zend\Db\Adapter\Adapter' => function ($sm) {
$adapterFactory = new Zend\Db\Adapter\AdapterServiceFactory();
$adapter = $adapterFactory->createService($sm);
\Zend\Db\TableGateway\Feature\GlobalAdapterFeature::setStaticAdapter($adapter);
return $adapter;
}
* in my /config/autoload/global.php (and can't remember anything else) BUT, while
* following the example to the letter, I still got errors (like "no static adapter blah-blah - can't remember) and didn't work
* and so on... followed quite a few different examples/methods, rewrote/added many lines in my code
* (in the Model and/or Controller and/or Module.php) but still couldn't make things work for me.
*/
),
),
),
));
/*
* Filters and validators for the rest of the form elements here
* Removed them so I would keep the code focused on my question
*/
}
}
Here's my User module's Module.php (/module/User/Module.php) :
namespace User;
use User\Entity\Users;
use User\Entity\UsersTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
class Module {
public function getAutoloaderConfig() {
// ...
}
public function getConfig() {
// ...
}
public function getViewHelperConfig() {
// ...
}
public function getServiceConfig() {
return array(
'factories' => array(
'User\Entity\UsersTable' => function($sm) {
$tableGateway = $sm->get('UsersTableGateway');
$table = new UsersTable($tableGateway);
return $table;
},
'UsersTableGateway' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Users());
return new TableGateway('users', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
}
Here is my model (/module/User/src/User/Entity/Users.php):
namespace User\Entity;
class Users {
public $user_id;
public $other_id;
public $user_identifier;
public $user_credential;
public $user_type;
public $user_alias;
public $active;
public $enabled;
public $token;
public $created;
public function exchangeArray($data) {
$this->user_id = (isset($data['user_id'])) ? $data['user_id'] : null;
$this->other_id = (isset($data['other_id'])) ? $data['other_id'] : 0;
$this->user_identifier = (isset($data['user_identifier'])) ? $data['user_identifier'] : 'what?';
$this->user_credential = (isset($data['user_credential'])) ? md5($data['user_credential']) : 'not-possible';
$this->user_type = (isset($data['user_type'])) ? $data['user_type'] : 'client';
$this->user_alias = (isset($data['user_alias'])) ? $data['user_alias'] : 'Anonymus';
$this->active = (isset($data['active'])) ? $data['active'] : 0;
$this->enabled = (isset($data['enabled'])) ? $data['enabled'] : 1;
$this->token = (isset($data['token'])) ? $data['token'] : 'no-token';
$this->created = (isset($data['created'])) ? $data['created'] : date('Y-m-d h:m:s', time());
}
public function getArrayCopy() {
return get_object_vars($this);
}
}
and the TableGateway (/module/User/src/User/Entity/UsersTable.php) :
namespace User\Entity;
use Zend\Db\TableGateway\TableGateway;
class UsersTable {
protected $tableGateway;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
/*
* fetchAll(), getUserById(), deleteUser() etc.
* Different methods here...
*/
public function saveUser(Users $user) {
$data = array(
'user_id' => $user->user_id,
'other_id' => $user->other_id,
'user_identifier' => $user->user_identifier,
'user_credential' => $user->user_credential,
'user_type' => $user->user_type,
'user_alias' => $user->user_alias,
'active' => $user->active,
'enabled' => $user->enabled,
'token' => $user->token,
'created' => $user->created
);
$user_id = (int)$user->user_id;
if ($user_id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getUser($user_id)) {
$this->tableGateway->update($data, array('user_id' => $user_id));
} else {
throw new \Exception("User with id {$user_id} does not exist");
}
}
}
}
And last, but not least, here's my controller (/module/User/src/User/Controller/IndexController.php) :
namespace User\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use User\Entity\Users;
class IndexController extends AbstractActionController {
public function registerAction() {
$registerForm = new \User\Form\RegisterForm;
if ($this->getRequest()->isPost()) {
// Form processing
$formData = $this->getRequest()->getPost();
$registerForm->setData($formData);
if ($registerForm->isValid()) {
// Insert into DB here
$user = new Users();
$user->exchangeArray($formData->user);
$this->getUsersTable()->saveUser($user);
}
return new ViewModel(array(
'form' => $registerForm,
));
} else {
return new ViewModel(array(
'form' => $registerForm,
));
}
}
/*
* Other methods go here
* login, logout, editAccount, emailConfirmation
* etc.
*/
public function getUsersTable() {
if (!$this->usersTable) {
$sm = $this->getServiceLocator();
$this->usersTable = $sm->get('User\Entity\UsersTable');
}
return $this->usersTable;
}
}
First of all I feel the need to shout here:
If in my /config/autoload/global.php I have set up my database connection, why on earth should I ever, in any other place in the application, speciffy anything (else) regarding the database, except maybe just the table I want to use?
Why do I have to mess around with setters and getters and factories and service manager and so on at the module level (sometimes even in the controller) and fatten the code so much? And as far as I can see from all sorts of examples (from which I got to write the code in getServiceConfig() in my Module.php), I need to do that for every entity. wtf? This really sucks! Big time!
Then, if I work with #n tables in a controller, I have to have #n functions like "public function getUsersTable() {}"? And #n factories like 'User\Entity\UsersTable' in my getServiceConfig() in Module.php? That's crap! Is this the best way to go? Or am I just unlucky and keep finding the worst examples possible while learning zf2? Why should there be a getTableNameTable() function in my controller? Isn't the Model that should worry about what table I'm talking about? Since every model is designed for one specific table? Like in zf1, where I would just have "protected $_name = 'users';" in my model and that was all I needed.
Why aren't those connection settings "magically" (simply) available ANYWHERE in my application, like in zf1? Why else am I putting that in the config for? I really don't understand why do I need all that in the getServiceConfig() in my Module.php and how could I avoid that?
Things seamed more compact in zf1. In zf2, most of the time I have no ideea whatsoever of what I'm doing, I copy snipets and prey on form submit or F5 that they work right out of the box and I get no errors. I should also probably mention that I don't have a strong understanding of OOP, I'm just a newb trying to learn zf2 (after using zf1 for 2 projects that had a lot to do with database, content administration, ajax, work with facebook api, google maps). With zf1, even if I bearly had an ideea about OOP, I still could do whatever I needed to do. In zf2, it seams there are 1000 ways right out of the box to do each one thing. Almost every time I'm looking for a solution to some problem I ran into, I find many examples... but most of the time, no example has the base code similar to mine to build upon, so I have to rewrite a lot (because I can't adapt, I'm a newb and if I adapt what I find, I immediately get errors, which I sometimes get even if I rewrite accordingly to the exampled I find).
So, what I am asking is:
In my code, pasted above, what should I add/modiffy in order to make that validation against the database? Because, right now, I get "No database adapter present" <em>sure I do</em>
This probably goes beyond the initial scope of this post, BUT how can I avoid so much code and configuration spreading all over the place? I don't want to have to speciffy anything regarding the database connection all over my application, it really should be done in one place and all other modules and entities and controllers should know all there is to know about the database (without telling them where to look in every controller/model, they should simply know that from the config), anything else they would need to know at Model(Entity)/Controller level should just be the table I want to work with, I shouldn't need to repeat myself and keep saying all over the place that "this is my adapter, there - get it from >here<" - that's crap) stop messing with getters and setters and factories and service manager and "make this from here available to that over there" etc. I shouldn't speciffy an adapter in my InputFilters or anywhere except the global/local.php. All that I should tell the validator in the input filter is the table and column to validate against, isn't this more natural?
I did this by passing the adapter to my interface:
Controller code:
if ($request->isPost()) {
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
$admins = new Admins($dbAdapter);
.
.
.
}
My interface code:
class Admins implements InputFilterAwareInterface
{
public $id;
public $first_name;
public $last_name;
/* etc */
private $gatewayAdapter;
public function __construct($dbAdapter = null) {
$this->gatewayAdapter = $dbAdapter;
}
/* etc*/
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
/* etc */
$inputFilter->add($factory->createInput(array(
'name' => 'email',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array('name' => 'NotEmpty',),
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => 'admins',
'field' => 'email',
'adapter' => $this->gatewayAdapter
),
),
),
)));
/* etc */
}
/* etc */
}
I'm trying to build on the example code at http://book.cakephp.org/2.0/en/models/saving-your-data.html in the section that starts off with "Saving Related Model Data (hasOne, hasMany, belongsTo)". However, I'm getting the following error message when I call unset:
Indirect modification of overloaded property AppModel::$MealContent has no effect [APP\Controller\MealsController.php, line 15]
Attempt to modify property of non-object [APP\Controller\MealsController.php, line 15]
Naturally, the data isn't saved either.
As you can see, my application doesn't have Companies or Accounts. Instead, I have Meals and MealContents but the relationships seem to have been set up the same. Obviously, there's a problem somewhere though so here's some code.
Meals.php:
class Meals extends Entry {
public $hasMany = 'MealContents';
public $validate = array(
'Timestamp' => array(
'validDate' => array(
'rule' => array('garbageDateChecker'),
'required' => true,
'message' => 'The date could not be understood.'),
'noTimeTravel' => array(
'rule' => array('noTimeTravel'),
'required' => true,
'message' => 'You cannot enter times in the future.')
)
);
}
MealContents.php:
class MealContents extends Entry {
public $belongsTo = 'Meals';
public $validate = array(
'Meals_ID' => array(
'notEmpty' => array(
'rule' => 'notEmpty',
'required' => true,
'message' => 'This field cannot be blank')
),
'Item_ID' => array(
'notEmpty' => array(
'rule' => 'notEmpty',
'required' => true,
'message' => 'This field cannot be blank')
)
);
}
And finally, the controller's index() function:
public function index() {
$this->set('title_for_layout', "Food Log");
if ($this->request->is('post')) {
$this->Meal->create();
unset($this->Meal->MealContent->validate['Meals_ID']);
if ($this->Meal->saveAssociated($this->request->data)) {
$this->Session->setFlash('The meal was logged.');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash("Couldn't save the meal.");
}
}
}
Entry.php
abstract class Entry extends AppModel {
public function garbageDateChecker($dateToCheck) {
date_default_timezone_set("America/Tijuana");
$result = TRUE;
try {
new DateTime($dateToCheck['Timestamp']);
} catch (Exception $e) {
$result = FALSE;
}
return $result;
}
public function noTimeTravel($dateToCheck) {
date_default_timezone_set("America/Tijuana");
$result = TRUE;
$objectifiedDateToCheck = new DateTime($dateToCheck['Timestamp']);
$currentTime = new DateTime("now");
if ($objectifiedDateToCheck > $currentTime) {
$result = FALSE;
}
return $result;
}
}
I'm pretty sure the save() failure isn't due to the validation because even when I comment out the validation rule and the unset() line, the data isn't saved.
It would be easy to blame it on bad data or a screwed up view. However, the data looks OK:
$this->request->data
--MealContents
---[0]
-----[Food_Item_ID] = "0"
--Meals
---[Comments] = ""
---[Timestamp]
-----[day] = "12"
-----[hour] = ...
What have I missed when I read the CakePHP book?
In all of your model class declarations, you should be extending the AppModel class not "Entry". Also, you need to change your model names to singular nouns. Meal instead of Meals.
class Meal extends AppModel {
//your model's code
}
class MealContent extends AppModel {
//your model's code
}
In your controller, if you would like to skip validation on the saveAssociated call, you can pass an options array, with element "validate" set to False as the second parameter. You should never use unset on your model like that as it will affect the rest of your app.
$this->Meal->saveAssociated($this->request->data, array("validate" => false));
I can't figure out why this code isn't working. The beforeSave is not being called. It's supposed to fail the save and put some lines in the debug log, but it actually does save OK and no lines are written in the debug log.
<?php
class Link extends AppModel {
var $name = "Link";
var $belongsTo = array('Category' => array('className' => 'Category', 'foreignKey' => 'category_id'));
public function beforeSave(){
if ($this->data[$this->alias]['id'] == null) {
$this->log("new record", 'debug');
$link = $this->find('first',array('conditions' => array('Link.status = 1 AND Link.category_id = '.$this->data[$this->alias]['category_id']), 'order' => array('Link.order DESC') ));
if (is_null($link)) {
$this->data[$this->alias]['order'] = 1;
}else{
$this->data[$this->alias]['order'] = $link['Link']['order'] + 1;
}
}
else {
$this->log("old record", 'debug');
}
return false;
}
}
?>
I am launching a save in the controller like this:
public function add($category_id = null)
{
if ($category_id == null) {
$this->Session->setFlash(__('Category id cant be null'),'default', array('class' => 'error-message'));
$this->redirect(array('action' => 'index', 'controller' => 'categories'));
}
else
{
if($this->request->is('post'))
{
$this->Link->create();
$this->Link->set('category_id' => $category_id));
if($this->Link->save($this->request->data))
{
$this->Session->setFlash(__('The link has been saved'),'default', array('class' => 'success'));
$this->redirect(array('action' => 'index/'.$category_id));
}
else
$this->Session->setFlash(__('The link could not be saved. Please, try again.'),'default', array('class' => 'error-message'));
}
$this->set('category_id',$category_id);
}
}
Another question in StackOverflow points out that the beforeSave method needs to be declared in the Model. I've also done the same thing with another model.
Here's some general advice and some comments on your code for an answer:
1) If a model callback or any model method isn't working, make sure the correct model is being used and not the default model (AppModel). Check filename, class name, extension (in your case), and location.
2) You're using conditions array incorrectly (in this case).
array('conditions' => array('Link.status = 1 AND Link.category_id = '.$this->data[$this->alias]['category_id'])
You should really be doing:
array('conditions' => array('Link.status' => 1, 'Link.category_id' => $this->data[$this->alias]['category_id'])
3) Your redirect is being used wrong
$this->redirect(array('action' => 'index/'.$category_id));
Should be:
$this->redirect(array('action' => 'index', $category_id));
As written, your save() will always fail with this beforeSave(). beforeSave() must return true in order for the save function to succeed.
In fact, yours appears to always return false, guaranteeing a failed save.
From the Cake manual:
Be sure that beforeSave() returns true, or your save is going to fail.