Zend Framework 2 Db Validator Zend\Validate\Db\NoRecordExists - I just can't make things work for me - database

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 */
}

Related

How do I set up an adapter in zend framework 2?

this is the content of the autoload/global.php file :
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=web_builder;host=localhost',
'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 the content of the autoload/local.php file:
return array(
'db' => array(
'username' => 'DB_User_Name',
'password' => 'DB_Password',
,
); )
this is part of the content of module/Module.php :
namespace Application;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
use Zend\Db\Adapter\Adapter;
.........
public function getServiceConfig() {
return array(
'factories' => array(
'Application\Controller\UserController' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new Model\StickyNotesTable($dbAdapter);
return $table;
},
),
);
}
Here I really don't understand what this function do, I just copy pasted from an example. If you could explain me what does getServiceConfig function do, I will really appreaciate it.
Finally the controller content:
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Db\Adapter\Adapter;
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Sql\Select;
class UserController extends AbstractActionController{
public function __construct(Adapter $adapter) {
$this->adapter = $adapter;
}
public function loginAction(){
// here i just want to a simple select and yes I know queries will be executed
//in Model, but I want to here a simple query.
//For example in Codeigniter I can't do in model, controller, or view as well.
return new ViewModel();
}
The result of all this code is obviously an error:
Catchable fatal error: Argument 1 passed to Application\Controller\UserController::__construct() must be an instance of Zend\Db\Adapter\Adapter, none given, called in C:\xampp\htdocs\zf2\vendor\zendframework\zendframework\library\Zend\ServiceManager\AbstractPluginManager.php on line 170 and defined in C:\xampp\htdocs\zf2\module\Application\src\Application\Controller\UserController.php on line 21
Can someone post an answer to make this database or query stuff work ? thx

CakePHP hasOne-belongsTo relationship, how to look up and insert foreign key from user input?

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...

Drupal 7 Rules custom action assign return data to a replacement pattern

How can I create a custom Rule-Action which will successfully save a value as a replacement pattern for use in the other actions?
I got some very good help here on retrieving Product-Display information from a Product-Order.
As I said, the linked answer helped a great deal but the returned path data for the Product-Display comes back in the http://www.mysite/node/77 format. However, I really just need the numeric value only so I can load the node by performing a Fetch entity by id action supplying the numeric value and publishing the Product-Display node etc.
So, I implemented a custom action which will take the Product-Display URL(node/77) and return 77.
I copied the Fetch entity by id code and modified it so my returned numeric value can be saved and used in other Actions. The code is below:
function my_custom_action_info(){
$actions['publish_product_display_node'] = array(
'label' => t('Fetch product-display id'),
'parameter' => array(
'type' => array(
'type' => 'uri',
'label' => t('My Action'),
'options list' => 'rules_entity_action_type_options2',
'description' => t('Specifies the product-display url.'),
),
),
'provides' => array(
'entity_fetched' => array('type' => 'integer', 'label' => t('Fetched entity')),
),
'group' => t('Entities'),
'access callback' => 'rules_entity_action_access',
);
return $actions;
}
function publish_product_display_node($path = null){
$parts = explode('node/', $path);
return $parts[1];
}
function rules_entity_action_type_options2($element, $name = NULL) {
// We allow calling this function with just the element name too. That way
// we ease manual re-use.
$name = is_object($element) ? $element->getElementName() : $element;
return ($name == 'entity_create') ? rules_entity_type_options2('create') : rules_entity_type_options2();
}
function rules_entity_type_options2($key = NULL) {
$info = entity_get_info();
$types = array();
foreach ($info as $type => $entity_info) {
if (empty($entity_info['configuration']) && empty($entity_info['exportable'])) {
if (!isset($key) || entity_type_supports($type, $key)) {
$types[$type] = $entity_info['label'];
}
}
}
return $types;
}
function rules_action_entity_createfetch_access2(RulesAbstractPlugin $element) {
$op = $element->getElementName() == 'entity_create' ? 'create' : 'view';
return entity_access($op, $element->settings['type']);
}
As I said I copied the modified code so I don't claim to thoroughly understand all the functions aside from publish_product_display_node.
My code modifications work as far as setting the Product-Display URL token as the argument and also setting an entity variable label(Display NID) and value(display_nid).
The problem is when I check display_nid in newly created actions, the value is empty.
I need help figuring out the how to successfully save my entity value so I can use it in following Actions.
in the function publish_product_display_node, can you verify that you don't need to be returning $parts[0], instead of $[parts[1]?
It's just that Drupal paths are frequently in the form 'node/7' or 'taxonomy/term/6', and if you explode with 'node/' as the separator, you'd only have a single value which would start at index 0 for nodes...
So, just wondering if that would solve your issue...

Match data entered with data in the database CAKEPHP

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;
}
}

CakePHP: Where To Place This Function

I have 3 models: Student, Course and StudentCourse. Course 'hasAndBelongsToMany' Student, Student 'hasMany' Course, and StudentCourse 'belongsTo' Student and Course. Before a student can signup for a course, I need to check a few things (ie: is the course full, has that student taken that course in the past, etc). I can handle the logic inside of the function, but which model should I place that function under? And, how should it be called? One way I thought of was:
// Student Model
public function canSignupForCourse($studentId, $courseId) {
// is the course full?
// have they signed up before, etc
// return either true or false
}
// Could it then be called anywhere as:
if($this->Student->canSignupForCourse($studentId, $courseId)) {
// etc
}
Or, is there a better/easier way to do it (and, do I need to send both the studentid and courseid each time)?
I think the best thing to do is to try to implement these restrictions as validation rule in the model.
According to your description, applying a student for a course is done by creating a new StudentCourse, so that's where you should try to fit the validation rules, for example:
// StudentCourse.php
$validate = array(
'course_id' => array(
'rule' => array('maxStudents', 30),
'required' => true,
'on' => 'create'
)
)
function maxStudents($check, $max) {
$count = $this->find('count', array(
'conditions' => array('course_id' => $check['course_id']),
'contain' => false
));
return $count < $max;
}
I'd first check out the example in the manual here: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany-through-the-join-model
This should convince you that you should probably make Student 'hasAndBelongsToMany' course as well (since Course has student but student doesnt belongto course in your model relationships)
You can then define that relationship model as something like CourseMembership (as in the example link above)
I would then put canSignupForCourse function in that model. However I'd probably split this function up into a few separate ones, like courseNotFull and courseNotTakenBefore
I would then put these functions into the model's validate object like so:
public $validate = array(
'course_id' => array(
'courseNotFull' => array(
'rule' => array('courseNotFull'),
'message' => "Course is full",
),
'courseNotTakenBefore' => array(
'rule' => array('courseNotTakenBefore'),
'message' => "Student has taken course before",
)
)
);
And define the model functions like this:
function courseNotFull() {
$this->Course->id = $this->data[$this->alias]['course_id'];
$course = $this->Course->read();
return $course['Course']['isFull'];
}
function courseTakenBefore() {
$this->Student->id = $this->data[$this->alias]['student_id'];
$this->Course->id = $this->data[$this->alias]['course_id'];
$course = $this->Student->Course->findById($this->Course->id);
return $course;
}
Now whenever you try to save or validate() CourseMembership, the validate will return a descriptive error message if it is unsuccessful.

Resources