Add Order by option in SonataAdminBundle's Form (in Symfony 4) - sonata-admin

I have this code in src/Admin/CambiosseiAdmin.php:
$formMapper
.......
->add('peticionario', ModelType::class, array(
'class' => Bomberos::class,
'query' => $this->modelManager->createQuery('App\Entity\Bomberos')
))
and in src/Entity/Bomberos.php I have:
public function __toString()
{
return (string)$this->getNombre();
}
but the out nombre's list order is by id and I want change the order by nombre (name). Where can I write the 'order by' option to work it?

In your class src/Admin/CambiosseiAdmin.php use the following line:
protected $datagridValues = ['_sort_order' => 'ASC', '_sort_by' => 'nombre.name'];
Given nombre is a field in list page.

Related

prestashop multiple checkboxes do not save values

I can't figure out why the checkbox values are not saved in the database using helpers.
Trying to save some customers ids from my module's setting :
The array :
$custs = Customer::getCustomers();
foreach ($custs as $key => $value) {
$options[] = array(
'id_customer' => (int)$value['id_customer'],
'infos' => $value['firstname'].' '.$value['lastname'].' | '.$value['email']
);
}
The checkboxes :
'input' => array(
array(
'type' => 'checkbox',
'label' => $this->l('Customers'),
'desc' => $this->l('Select the Customers.'),
'name' => 'MY_MODULE_CUSTOMERS',
'values' => array(
'query' => $options,
'id' => 'id_customer',
'name' => 'infos',
),
),
)
The $_POST is always empty but works well with another input. Any help will be appreciated.
Thank you.
I don't think its in PS docs. But with a bit of code inspecting you can see in
Backoffice/themes/default/template/helpers/form/form.tpl
<input type="checkbox" name="{$id_checkbox}" id="{$id_checkbox}" class="{if isset($input.class)}{$input.class}{/if}"{if isset($value.val)} value="{$value.val|escape:'html':'UTF-8'}"{/if}{if isset($fields_value[$id_checkbox]) && $fields_value[$id_checkbox]} checked="checked"{/if} />
{$value[$input.values.name]}
add the porperty 'val' to option.
$options[] = array(
'id_carrier' => $carrier['id_carrier'],
'name' => $carrier['name'],
'val' => $carrier['id_carrier'],
);
Adn you get the desired serialization for the input values.
"transportistas" => array:2 [▼
0 => "73"
1 => "78"
]
Your code is correct, I tried it and this is result
http://screencast.com/t/wfsW86iJj
You have to click at least one checkbox.
Show data on server :
print_r($_POST);
die();
a better could be using groupbox but its quite difficult, take a look to the AdminCustomers controller class in the controllers directory of the prestachop, this has a multiselect group that used a relational table event stored in single field
If you want to be easy, using a single field to store in the database, take a look to THE COMPLETE CODE AND ALL THE STEPS AT: https://groups.google.com/forum/m/?hl=es#!topic/venenuxsarisari/z8vfPsvFFjk
at the begining dont forget to added that line:
// aqui el truco de guardar el multiselect como una secuencia separada por comas, mejor es serializada pero bueh
$this->fields_value['MY_MODULE_CUSTOMERS[]'] = explode(',',$obj->id_employee);
this $obj are the representation of the loaded previous stored value when go to edit ... from that object, get the stored value of the field of your multiselect, stored as "1,3,4,6"
and the in the field form helper list of inputs define the select multiple as:
array(
'type' => 'checkbox',
'label' => $this->l('Select and employee'),
'name' => 'MY_MODULE_CUSTOMERS[]',
'required' => false,
'col' => '6',
'default_value' => (int)Tools::getValue('id_employee_tech'),
'values' => array(
'query' => $options,
'id' => 'id_customer',
'name' => 'infos',
),
),
an then override the post process too
public function postProcess()
{
if (Tools::isSubmit('submitTallerOrden'))
{
$_POST['MY_MODULE_CUSTOMERS'] = implode(',', Tools::getValue('MY_MODULE_CUSTOMERS'));
}
parent::postProcess();
}
this make stored in the db as "1,2,3"

CakePhp $hasAndBelongsToMany not saving multiple select items as expected

I have the following code setup (snipped for brevity)
class BasePackage extends AppModel {
public $name = 'BasePackage';
public $hasAndBelongsToMany = array('ProductSubtype', 'ProductType');
}
class ProductType extends AppModel {
public $name = 'ProductType';
}
class ProductSubtype extends AppModel {
public $name = 'ProductSubtype';
}
Above are the simple Model classes.
/* tables in database */
base_packages
product_types
product_subtypes
base_packages_product_types
base_packages_product_subtypes
The first table is the main package that users are creating with the form, the product_* tables are pre-loaded with appropriate types and subtypes (they don't change very often), the last two are the Join tables that CakePhp wants to have
/* in BasePackage/add.ctp */
// ...
<ul class="nwblock">
<li>
<?php
echo $this->Form->input('ProductType.product_type_id', array(
'label' => 'Choose Product Type',
'type' => 'select',
'class' => 'form-control',
'style' => 'width:300px; margin-bottom:20px;',
'options' => $protypes
));
?>
</li>
</ul>
<ul class="nwblock">
<li>
<?php
echo $this->Form->input('ProductSubtype.product_subtype_id', array(
'label' => 'Choose Subtype(s)',
'multiple' => 'multiple',
'type' => 'select',
'class' => 'form-control',
'style' => 'width:300px;height:390px;margin-bottom:20px;',
'options' => $subtypes
));
?>
</li>
</ul>
// ...
Above we see the two controls that are loaded from the product_* tables. The types are a single select dropdown and the subtypes are a multiple select list.
/* in BasePackageController.php */
public function add() {
$protypes = $this->BasePackage->ProductType->find('list',
array('fields' => array('ProductType.id', 'ProductType.display')));
$subtypes = $this->BasePackage->ProductSubtype->find('list',
array('fields' => array('ProductSubtype.id', 'ProductSubtype.display')));
$this->set('protypes', $protypes);
$this->set('subtypes', $subtypes);
if ($this->request->is('post')) {
$this->BasePackage->create();
if (!empty($this->request->data)) {
$this->BasePackage->saveAll($this->request->data, array('deep' => true));
}
}
}
The process is as follows, while the user creates a new BasePackage, they select a ProductType from a dropdown box and one to many ProductSubtypes from a multiple select list. When the $this->BasePackage->saveAll() call is made, the data to be inserted into base_packages and base_packages_product_types tables is inserted correctly. However, the base_packages_product_subtypes table remains untouched.
UPDATE:
If I remove the 'multiple' => 'multiple', from the form->input options, the code saves both the producttype and the productsubtype (as expected). This is obviously not sufficient, as I need to save 1-to-many. Anyone know how to activate the 'Many' part of the HABTM?
To me BasePackage <> ProductType looks more like it should be a many-to-one relation, ie BasePackage belongsTo ProductType?
Anyways... please follow the conventions as described in the Cookbook:
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-habtm
The form helper should be fed with the model name, ie ProductSubtype, and the view var should be camel backed plural, ie productSubtypes, that way CakePHP will do the rest for you automatically.
public function add() {
// ...
$this->set('productSubtypes', $subtypes);
// ...
}
echo $this->Form->input('ProductSubtype', array(
'label' => 'Choose Subtype(s)',
'class' => 'form-control',
'style' => 'width:300px;height:390px;margin-bottom:20px;'
));
Can you try with BasePackage->saveAssociated ?
http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array

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

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

cakeDC search on concat fields

I am using the cakeDC search plugin for cakephp and I need to search by full name. I have stored the name in the database as separate first and last names. How would I concat the first and last name and then perform my search.
So my form input will search either member number or full name.
cakephp version 2.3.5
public $filterArgs = array(
'member_no' => array(
'type' => 'like' ,
'field' => array(
'member_no',
'name'
)
)
);
Update:
Here is what it would look like as a sql query:
SELECT
CONCAT_WS(' ', firstName,lastName) AS name
FROM
table
WHERE
name LIKE '%$keywords%'
Thanks to Mark here is what I added to the Model. All works great now.
public $virtualFields = array(
'name' => 'CONCAT(Member.first_name, " ", Member.last_name)'
);
If you just want the example above in a clean way why not using the plugin as documented? allowing it to do all the work?
public $virtualFields = array(
'full_name' => 'CONCAT_WS(' ', firstName,lastName)'
);
public $filterArgs = array(
'search' => array(
'type' => 'like' ,
'field' => 'full_name'
)
);
That's all there is to it. Or what is the issue?

Virtual field in 'fields' find condition - Cakephp

I have a Student model with a virtual field:
var $virtualFields = array(
'full_name' => 'CONCAT(Student.fname, " ", Student.lname)'
);
I am doing a find operation to fetch specific fields using 'fields':
$this->Student->find('all', array('fields' => array('Student.fname','Student.lname')));
For some reason, the virtual field is not being created after finding the records. I tried adding Student.full_name but of course it gives an unknown column error in mysql.
Any ideas?
You can't specify in fields a virtual field you need to call it without the fields option so it bring it with the results... you may wanna read the examples in the cookbook
calling all fields is one option another one is to call the field in the fields option, something like this
$this->Student->find('all', array('fields' => array(
$this->student->virtualFields['full_name'].'AS Student__full_name',
'Student.fname','Student.lname')
));
what about placing a method in your app_model.php like so:
/**
* combine virtual fields with fields values of find()
* USAGE:
* $this->Model->find('all', array('fields' => $this->Model->virtualFields('full_name')));
* #param array $virtualFields to include
*/
public function virtualFields($fields = array()) {
$res = array();
foreach ((array)$fields as $field) {
//TODO: if key numeric => value sql!
//TODO: allow combined/other models via Model.field syntax
$sql = $this->virtualFields[$field];
$res[] = $sql.' AS '.$this->alias.'__'.$field;
}
return $res;
}
and then use it like so:
$this->Model->find('all', array('fields' => $this->Model->virtualFields('full_name')))
));
or so:
$fields = $this->Model->virtualFields('full_name');
$fields = am($fields, 'status', 'created');
$this->Model->find('all', array('fields' => $fields));
));
On the CookBook:
http://book.cakephp.org/2.0/en/models/virtual-fields.html
It says that you can use it like:
Model:
public $virtualFields = array(
'name' => 'CONCAT(User.first_name, " ", User.last_name)');
Controller or View:
$results = $this->User->find('first');
Result:
array(
[User]
[first_name] => 'Mark',
[last_name] => 'Story',
[name] => 'Mark Story',
//more fields.
)
So you can use it just like this:
$this->set('list_fields', $this->User->find('list',
array('fields' => array('first_name', 'last_name', 'name'),
'recursive' => -1, 'condition' => ...)));

Resources