Symfony 2 : Access database inside FormBuilder - database

I'm building a form which contains a category field. I need a choice list to do that, but I don't find out how to fill this choice list with the several categories stored in the database.
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('item', 'text', array('label' => 'Item'));
$builder->add('category', 'choice', array(
'choices' => ???,
'label' => 'Category'
));
}
How can I get the categories from the database?
(I can't seem to access $this->getDoctrine() inside this class.)

Use type entity instead of choice
$builder
->add('entity_property', 'entity', array(
'class' => 'Namespace\\To\\Entity',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('q')
->where('q.a_field = yourvalue');
}
));
Edit:
Two ways for using custom parameters in your query. In both situations, the parameters are injected from outside, so your FormType don't need any references to the session or request objects or whatever.
1- Pass required parameters to your constructor
class TaskType extends AbstractType
{
private $custom_value;
public function __construct($custom_value) {
$this->custom_value = $custom_value;
}
// ...
}
in your buildForm() you must copy the value to local variable and make it available for the query_builder callback:
public function buildForm(/*...*/) {
$my_custom_value = $this->custom_value;
// ...
'query_builder' => function(EntityRepository $repository) use ($my_custom_value) {
return $repository->createQueryBuilder('q')
->where('q.a_field = :my_custom_value')
->setParameter('my_custom_value', $my_custom_value);
}
// ...
}
2- use the $options parameter of the buildForm method.
First you have to define a default value by overriding getDefaultOptions:
public function getDefaultOptions(array $options)
{
return array(
'my_custom_value' => 'defaultvalue'
);
}
Then you can pass it from your controller in the third argument of the createForm method.
$this->createForm(new YourFormType(), $entity, array('my_custom_value' => 'custom_value'));
Now the value should be available through the $options parameter of youru buildForm method. Pass it to the callback as described above.

In Symfony 2.1
You now have to use the OptionsResolverInterface within the setDefaultOptions method. Here is the code you would have to use if you wanted to retrieve the options (using the same example as the accepted answer)
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function buildForm(FormBuilderInterface $builder, array $options){
parent::buildForm($builder, $options);
$my_custom_value = $options[custom_value];
// ...
'query_builder' => function(EntityRepository $repository) use ($my_custom_value) {
return $repository->createQueryBuilder('q')
->where('q.a_field = :my_custom_value')
->setParameter('my_custom_value', $my_custom_value);
}
// ...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'my_custom_value' => 'defaultvalue'
));
}
You still pass the options in the same way:
$this->createForm(new YourFormType(), $entity, array('my_custom_value' => 'custom_value'));

Related

Can not get paginate parameters using func_get_arg() function in Model

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.

How to populate select element in zend 1.12 from db

i'm creating a application in which i need to populate data in select element from a db table.
i need to populate user roles from db
my form code is
$this->pass2->addValidator('Identical', false, array('token' => 'pass1'));
$this->addElement('select', 'userrole', array(
'class' => 'form-control',
'required' => true,
'multiOptions' =>
));
what should i do with multi options ?,
is there any way to load data from db in element using controller ,please helpme
thanks
What I have done in the past is to pass the db-adapter (or a model that knows how to do the required db query) to the form as a constructor parameter.
Something like this:
class Application_Form_MyForm extends Zend_Form
{
protected $db;
public function __construct($db)
{
$this->db = $db;
// Don't forget to call the parent __construct. Ultimately
// it is the parent __construct() that calls your init()
// method that adds your elements
parent::__construct();
}
public function init()
{
// Create your form elements
// $this->addElement('text', 'my_text_field'); // etc
// Now your select field...
$this->addElement('select', 'my_select', array(
'multiOptions' => $this->buildMultiOptions(),
'validators' => array(
// blah, blah
),
);
}
protected function buildMultiOptions()
{
$select = $this->db->select()
->from('my_table', array(
'my_value_column',
'my_display_column'
))
->order(array(
'my_display_column ASC',
));
$results = $this->db->query($select)->fetchAll();
$return = array();
foreach ($results as $row) {
$return[$row['my_value_column']] = $row['my_display_column'];
}
return $return;
}
}
Then in the controller action, when you instantiate your form, you grab the db-adapter and pass it in as a constructor parameter:
$db = $this->getInvokeArg('bootstrap')->getResource('db');
$form = new Application_Form_MyForm($db);
// Then process your form as usual
on case with is necessary populate options outside form class.
$form->getElement( 'ele_name' )
->setConfig(new Zend_Config( array(
'multiOptions' => array('option1','option2') )
)));

How to add custom CSS class to required input label?

I would like to add a CSS class to labels which are before my required inputs field. I can do it via JavaScript but I would like to do it in CakePHP.
Is there some options to tell CakePHP to do it automatically?
For one input, you can simply do this:
$this->Form->input('myfield', [
'label' => [
'text' => 'My Field',
'class' => 'my-label-class'
]
]);
If you need to add it to all required inputs, you could instead create your own FormHelper:
App::import('Helper', 'Form') ;
class MyFormHelper extends FormHelper {
protected function _inputLabel($fieldName, $label, $options) {
// Extract the required option from the $options array.
$required = $this->_extractOption('required', $options, false)
|| $this->_introspectModel($this->model(), 'validates', $fieldName);
// If the input is required, first force the array version for $label,
// then add the custom class.
if ($required) {
if (!is_array($label)) {
$label = array('text' => $label) ;
}
$label = $this->addClass($label, 'my-label-class') ;
}
// Then simply call the parent function.
return parent::_inputLabel($fieldName, $label, $options) ;
}
}
And then in your controller:
public $helpers = array(
'Form' => array('className' => 'MyForm')
);
See FormHelper.php for informtion about _introspectModel, basically:
$this->_introspectModel ($model, 'validates', $fieldName)
...will return true if $fieldName is a required field in your Model::validates array.

Saving associated models in Cakephp 3

I have a form that collects data about an Article, and I want to save that data, as well as for a model called Abstract, where an Article hasMany Abstracts. My models look like this:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AbstractsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Articles');
}
public function validationDefault(Validator $validator)
{
$validator
->notEmpty('body');
return $validator;
}
}
And
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
$this->hasMany('Abstracts');
}
public function validationDefault(Validator $validator)
{
$validator ->notEmpty('category')
return $validator;
}
}
My input form has a field named 'abstracts.body', and in my ArticlesController I have this function:
public function add()
{
$data = $this->request->data;
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
if ($this->request->is('post')) {
$article->user_id = $this->Auth->user('id');
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
$article = $this->Articles->patchEntity($article, $data, [
'associated' => ['Abstracts']
]);
if ($this->Articles->save($article, [ 'validate' => false,
'associated' => ['Abstracts']
]) )
{
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
My Abstracts table is pretty straightforward:
CREATE TABLE 'abstracts' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'article_id' INTEGER , 'user_id' INTEGER , 'body' TEXT, 'approved' BOOLEAN )
From debugging I can see that I have the correct 'abstracts' array within my $data (in add()), but it doesn't appear to ever try to save it to the database. Can someone please point out my error? Thanks!
Got it.
I started going wrong here:
My input form has a field named 'abstracts.body'
Because it's a hasMany relationship, I need to have that input be 'abstracts.0.body'
Then the rest of LeWestopher's answer will work-- adding an index to the fields I want to fill in from the Controller, so $data[abstracts][0]['user_id'] => ... and so on. Thanks!
You're post processing your $data['abstracts'] array incorrectly resulting in the association not saving. $data['abstracts'] is expected to be an array of Abstracts. Your issue lies here:
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
You should be able to fix this pretty easily by changing this to:
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
This should correctly iterate over your array of abstracts, set the user_id and approved keys appropriately and then it should save correctly.
CakePHP 3.x Documentation on Saving Associations
EDIT: Very interesting issue indeed. Try it without using patchEntity, and use newEntity by itself instead:
public function add()
{
if ($this->request->is('post')) {
$data = $this->request->data;
// Post process abstracts objects
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
// Build newEntity
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
// Save our entity with associations
if ($this->Articles->save($article, [
'validate' => false,
'associated' => ['Abstracts']
])) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
// On save fail
$this->Flash->error(__('Unable to add your article.'));
$this->set('article', $article);
}
}
EDIT 2: Your issue looks like it's definitely in your form helper. Your current form helper input creates an $data array that looks like this:
$data = [
'abstracts' => [
'body' => 'example text'
],
'category' => 'Science'
];
Which SHOULD look like:
$data = [
'abstracts' => [
['body' => 'example text'],
['body' => 'Im your second abstract'],
['body' => 'Abstract three!']
],
'category' => 'Science'
];
The issue lies in:
abstracts.body
Which should read as (in array dot notation):
// abstracts.0.body
echo $this->Form->input('abstracts.0.body', [
'label' => 'summary of article',
'maxlength' =>'440',
'rows' => '7'
]);
I believe that should be the last issue you run into.

CakePHP 3.0 build a permission menu using Cells

I would like to create a menu in new CakePHP 3.0 and I found that using cells might be a good way. So let's say I created UserMenuCell
class UserMenuCell extends Cell {
protected $_validCellOptions = [];
public function display() {
$menu = [];
$menu[] = $this ->menu( __('Dashboard'), array( 'controller' => 'Users', 'action' => 'dashboard' ), 'fa-dashboard', [] );
if( $this -> Auth -> isAuthorized(null, ??? ))
$menu[] = $this ->menu( __('Barcodes'), array( 'controller' => 'Barcodes', 'action' => 'index' ), 'fa-table', [] );
$this -> set ( 'menu', $menu );
}
private function menu( $title, $url = [], $icon, $submenu = [] ) {
return ['title' => $title, 'url' => $url, 'icon' => $icon, 'submenu' => $submenu]; }
}
But I want to display Barcodes item only when current user is authorized to manage barcodes. How can I do it? I can't even access $this -> Auth to get current user.
In my cell's template is everything OK. I just need to create this nested array for menu.
According to Cookbook, the session is available from within Cells.
class UsermenuCell extends Cell
{
public function display()
{
var_dump($this->request->session()->read('Auth'));
}
}
Like this you could read the needed informations in your cell display function.
and if you pass the session variable?
<?= $this->cell('userMenu', $this->Session->read('Auth')); ?>
I think the problem is solved:
I can make controller to have static method for example static public function _isAuthorized($user, $request) which would handle the authorization logic (so every controller controls only its own permissions).
And then I can just call from anywhere for example PostsController::_isAuthorized($user, ['action' => 'add']). This should solve all problems I guess.
Also good point is to pass $this -> Auth -> user() into view, so it can be used in Cells (through parameter).
src/Controller/AppController.php
public function beforeFilter(Event $event) {
$this -> set('user', $this -> Auth -> user());
}
src/View/Cell/MenuCell.php
use App\Controller\PostsController; // Don't forget to use namespace of your Controller
class MenuCell extends Cell {
public function display($user) {
$menu = [];
if (PostsController::_isAuthorized($user, ['action' => 'add'])) // In that method you must handle authorization
$menu[] = ['title' => 'Add post', 'url' => array('controller' => 'Posts', 'action' => 'add')];
$this -> set ('menu', $menu); // Handle this in Template/Cell/Menu/display.ctp
}
}
src/Template/Cell/Menu/display.ctp - just to show how to render menu
<ul>
<?php foreach($menu as $item) {
echo '<li>' . $this -> Html -> link ($item['title'], $item['url']);
} ?>
</ul>
src/Template/Layout/default.ctp - render menu in main layout
<?= $this -> cell('Menu', array($user)) /* This is the user passed from beforeFilter */ ?>
Then you can play with isAuthorized methods. For example you can edit your AppController. Always when CakePHP calls isAuthorized function it will be redirected to YourNameController::_isAuthorized() static method (if exists).
src/Controller/AppController.php
public function isAuthorized( $user ) {
$childClass = get_called_class();
if(method_exists($childClass, '_isAuthorized'))
return $childClass::_isAuthorized($user, $this -> request);
return static::_isAuthorized($user, $request);
}
static public function _isAuthorized($user, $request)
{
if ($user['role'] == 'admin')
return true;
return false; // By default deny any unwanted access
}
This is an example of your controller. You can specify only static _isAuthorized($user, $request) method, because for purposes of CakePHP default behavior it will be called from AppController::isAuthorized (see code above).
src/Controller/PostController.php
static public function _isAuthorized($user, $request)
{
$action = ($request instanceof Cake\Network\Request) ? $request -> action : $request['action'];
if($action == 'add' && $user['role'] == 'CanAddPosts')
return true;
return parent::_isAuthorized($user, $request);
}
As you can see I made $request to accept an array or Cake\Network\Request object. That's because CakePHP call it with Request object but when I call it I don't need to create this object, since my parameters are easy (see code above MenuCell.php).
Of course you can now do more complex logic like user can have more roles separated by comma and you can explode this and check if user has permission by in_array.
Now it's really up to you what is your logic behind permissions. Every controller can handle it's own permission managing while you can always access these permissions with every user and every page request.

Resources