I'm updating cakephp2 to cakephp3.
For model side, I can not get paginate parameters using func_get_arg() function in Model.
How can I get it?
I want to keep using func_get_arg() because other Model classes have also same logic.
class TopController extends AppController {
public function showTop() {
$query = array(
'limit' => 5,
'extra' => array(
'data' => 'test',
),
);
$topRawTable = TableRegistry::get('TopRawSql');
$pages = $this->Paginator->paginate($topRawTable->paginate($query)); //Error occur
}
}
class TopRawSqlTable extends Table {
public $useTable = false;
function paginate()
{
$extra = func_get_arg(6); // null
$limit = func_get_arg(3); // null
$page = func_get_arg(4); // null
// Execute custom query
}
}
I knew that CakePHP2 supports override paginate() and paginateCount() functions. But CakePHP3 DOESN'T support override paginate() and paginateCount(). That's why I couldn't get parameters by func_get_arg().
So, I switched to use CakePHP2 paginator feature.
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 have added translate behaviour to a model, the model comes here
App::uses('AppModel', 'Model');
class Category extends AppModel
{
public $hasMany = "Product";
public $validate = array(
'name' => array(
'rule' => 'notEmpty'
)
);
public $actsAs = array(
'Translate' => array(
'name','folder','show'
)
);
public $name = "Category";
public $translateModel = 'KeyTranslate';
}
And heres the controller for updating the model
public function admin_edit_translate($id,$locale)
{
$this->Category->locale = $locale;
$category = $this->Category->findById($id);
if ($this->request->is('post') || $this->request->is('put')) {
$this->Category->id = $id;
if ($this->Category->save($this->request->data)) {
$this->Session->setFlash('Category translate has been updated');
//$this->redirect(array('action' => 'edit',$id));
} else {
$this->Session->setFlash('Unable to update category');
}
}
if (!$this->request->data) {
$this->request->data = $category;
}
}
My Problem is that i have a name field in the categories database and when i update or create a new translation it gets updated with the translated value. How do i avoid that
You must use Model::locale value to set code language for save in database
This happens because the TranslateBehavior uses callbacks like beforeSave and afterSave to save translated content, so it needs to let the model's save operation continue and thus will contain the last translated content.
You could get around this by tricking the TranslateBehavior into thinking the model is saving something by calling the beforeSave and afterSave like this:
$Model = $this->Category;
$Model->create($this->request->data);
$Model->locale = $locale;
$beforeSave = $Model->Behaviors->Translate->beforeSave($Model, array(
array(
'callbacks' => true
)
));
if($beforeSave) {
$Model->id = $id;
$Model->Behaviors->Translate->afterSave($Model, true);
}
This way the translation will be saved and the main table will be left untouched. Might not be the best way to save translations though. Why do you need to leave the main table untouched?
Callback Behavior::beforeSave is before Model::beforeSave...
but, the simplest way to modify data in Model::beforeSave before Behavior::beforeSave before realy saving is:
$this->Behaviors->Behavior_Name->runtime[Model_Name]['beforeSave'][Field_Name] = '...';
Containable is not behaving as I expect in CakePHP 2.1.2 in the following code:
class ReportCard extends AppModel {
....
// debug shows expected results
public function test1(){
$this->Behaviors->attach('Containable');
$params = array(
'conditions' => array('ReportCard.id' => 1),
'contain' => array(
'ReportCardGradingPeriodCollection' => array(
'GradingPeriodCollection')));
debug($this->find('first', $params));
$this->Behaviors->detach('Containable');
}
// Unexpected: debug shows same array as test1
public function test2(){
$this->Behaviors->attach('Containable');
$params = array(
'conditions' => array('ReportCard.id' => 1),
'contain' => array(
'ReportCardGradingPeriodCollection' => array(
'GradingPeriodCollection' => array(
'GradingPeriodCollectionDetail' => array(
'GradingPeriod')))));
debug($this->find('first', $params));
$this->Behaviors->detach('Containable');
}
}
I get unexpected results when calling the functions from the controller. test1() shows the expected array. test2() shows the same array from test1. If I run test2() then test1() I get expected results (large array, then small array).
class ReportCardsController extends AppController {
....
public function test(){
$this->ReportCard->test1();
$this->ReportCard->test2();
}
}
I have tried using actAs in the model instead of dynamically loading the behavior in each function, but that did not help.
I apologize if I'm missing something simple. Thanks in advance!
Quoting the manual:
Containable must to be attached to all models used in containment,
From your example code it's not clear whether you are doing that.
And here's the remaining half of that note:
you may consider attaching it to your AppModel.
Since I'm in need of my navigation buttons I've placed the following code inside my AppController since i need it on every page.
// app/Controller/AppController.php
class AppController extends Controller {
public $uses = array('Categorie');
public function beforeFilter()
{
$parents = $this->Categorie->find('all', array('conditions' => array('cat_parent' => 0)));
$childs = $this->Categorie->find('threaded', array('conditions' => array('cat_parent' => $parents['Categorie']['cat_id'])));
echo '<pre>'.var_dump($parents).'</pre>';
}
}
I don't think this is the good way too accomplish what I want.. how can i do a while inside a while with the CakePHP Framework.
Kind Regards,
Jordy
You don't need a while loop in this case, you could just find the categories where cat_parent is not zero:
$childs = $this->Categorie->find('threaded', array(
'conditions' => 'cat_parent != 0'
));