How to load select option from database in zf2 - php

I Have been following zf2 guide for blog I have created everything Controller, Factory, Form, Mapper, Model, Service, view etc
In my form I have a select element
$this->add(array(
'type' => 'select',
'name' => 'roleId',
'attributes' => array(
'id' => 'roleId',
'options' => array(
'1' => 'Admin',
'2' => 'Manager',
),
),
'options' => array(
'label' => 'Role',
),
));
Now in this form I want to load the option for the role from the database.
I tried loading the option by creating a simple function, which can be accessed in the element as below, but Am not able to fetch the result. I have already created Controller, Factory, Form, Mapper, Model, Service and view, Where I can do CRUD operation on Role.
$this->add(array(
'type' => 'select',
'name' => 'roleId',
'attributes' => array(
'id' => 'roleId',
'options' => $this->getAllRoles(),
),
'options' => array(
'label' => 'Role',
),
));
public function getAllRoles()
{
$roles = $this->getServiceLocator()->get('Admin\Service\RoleService');
$allRoles = $this->getAllTheRoles();
return $allroles;
}
Can anybody guide me how can I load all the Roles in option as listed in the IndexAction following Blog Post with ID and Name of the Role.

You could create a reusable form element that is pre-populated with the roles. To do so you must register the service with the form element manager in module.config.php.
return [
'form_elements' => [
'factories' => [
'RoleSelect' => 'MyModule\Form\Element\RoleSelectFactory',
],
],
];
There is not need to extend the standard select class as the changes are configuration only. This is best done in a factory class.
namespace MyModule\Form\Element;
use Zend\Form\Element\Select;
class RoleSelectFactory
{
public function __invoke($formElementManager, $name, $rname)
{
$select = new Select('role_id');
$select->setOptions(['label' => 'Role']);
$select->setAttributes(['id' => 'role_id']);
$serviceManager = $formElementManager->getServiceLocator();
$roleService = $serviceManager->get('Admin\Service\RoleService');
$options = [];
foreach($roleService->getAllTheRoles() as $role){
$options[$role->getId()] => $role->getName();
}
$select->setValueOptions($options);
return $select;
}
}
Adding the element within a form can then be updated to use the name of the service we registered.
$this->add([
'name' => 'role_id'
'type' => 'RoleSelect',
]);
One important point to remember is that the form using this element must be created using the $formElementManager->get('FormWithRoleSelect').

Finally found out the simple way to do this, Am really not sure if this is the correct way.
Added the Role service in the User Controller
Code in my userController.php
use Admin\Service\RoleServiceInterface;
class UserController extends AbstractActionController
{
/**
* #var \Admin\Service\UserServiceInterface
*/
protected $userService;
protected $roleService;
protected $userForm;
public function __construct(
UserServiceInterface $userService,
RoleServiceInterface $roleService,
FormInterface $userForm
)
{
$this->userService = $userService;
$this->roleService = $roleService;
$this->userForm = $userForm;
}
public function addAction()
{
$request = $this->getRequest();
$roles = $this->roleService->findAllRoles();
foreach ($roles as $role) {
if($role->getStatus() == 1) {
$allRoles[$role->getId()] = $role->getName();
}
}
$this->userForm->__construct(null, array('roleOptions'=>$allRoles));
}
}
My UserControllerFactory.php
<?php
namespace Admin\Factory;
use Admin\Controller\UserController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserControllerFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
*
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$userService = $realServiceLocator->get('Admin\Service\UserServiceInterface');
$roleService = $realServiceLocator->get('Admin\Service\RoleServiceInterface');
$userInsertForm = $realServiceLocator->get('FormElementManager')->get('Admin\Form\UserForm');
return new UserController(
$userService,
$roleService,
$userInsertForm
);
}
}
Finally the UserForm.php
<?php
namespace Admin\Form;
use Zend\Form\Form;
use Admin\Model\User;
use Zend\Stdlib\Hydrator\ClassMethods;
use Zend\Form\Element\Select;
class UserForm extends Form
{
public function __construct($name = null, $options = array())
{
$roleOptions = array();
if($options) {
$roleOptions = $options['roleOptions'];
}
parent::__construct($name, $options);
$this->setHydrator(new ClassMethods(false));
$this->setObject(new User());
$this->add(array(
'type' => 'hidden',
'name' => 'id'
));
$this->add(array(
'type' => 'select',
'name' => 'roleId',
'attributes' => array(
'id' => 'roleId',
'options' => $roleOptions
),
'options' => array(
'label' => 'Role',
),
));
}
}
This way using service manager I was successfully able to load the data in my for Select option.

Call getAllRoles() inside the controller then You can pass your custom array as parameter for form when you create form object. In form __construct function you can retrieve that array and set like this
'options' => $roles,

Related

ZF3 FormElementManager: how to get ServiceManager

I'm porting code from ZF2 to ZF3.
In ZF2 when I create a form via FormElementManager I can access the servicelocator on the init method and configure some stuff like this:
public function init()
{
$this->serviceLocator = $this->getFormFactory()->getFormElementManager()->getServiceLocator();
$this->translator = $this->serviceLocator->get('translator');
}
This is convenient in very large applications. In fact all my forms inherit from a BaseForm class.
In ZF3 this is bad pratic and serviceLocator are deprecated.
Which is the best way to get the same result ?
One way is to inject every form in the ControllerFactory or ServiceFactory with the stuff needed but this is very tedious.
Any help is appreciate.
First of, you should not have the ServiceManager and/or childs of it (like the FormElementManager) available in your Form objects.
Using the Factory pattern, you should create fully functional, stand-alone Form, Fieldset and InputFilter objects.
There will definitely be some tedious work, as you put it, but you need only do it once.
Let's say you want to create a Location. A Location consists of a name property and a OneToOne unidirectional Address reference. This creates the following needs:
LocationForm (-InputFilter)
LocationFieldset (-InputFilter)
AddressFieldset (-InputFilter)
Config for the above
Factory for each of the 6 classes above
In this answer I'll mash everything down to bare minimums and use classes and examples from my own repositories, so for full code you can go here and for examples here.
After the creation of the classes themselves, I'll show you the config you need for this use case and the Factories which tie all of it together.
AbstractFieldset
abstract class AbstractFieldset extends Fieldset
{
public function init()
{
$this->add(
[
'name' => 'id',
'type' => Hidden::class,
'required' => false,
]
);
}
}
AbstractInputFilter
abstract class AbstractFieldsetInputFilter extends AbstractInputFilter
{
public function init()
{
$this->add([
'name' => 'id',
'required' => false,
'filters' => [
['name' => ToInt::class],
],
'validators' => [
['name' => IsInt::class],
],
]);
}
}
AddressFieldset
class AddressFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'street',
'required' => true,
'type' => Text::class,
'options' => [
'label' => 'Address',
],
]);
}
}
AddressInputFilter
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'street',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
So far, easy. Now, we need to create the LocationFieldset and LocationFieldsetInputFilter. These will make use of the Address(-Fieldset) classes.
LocationFieldset
class LocationFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => 'Name',
],
]);
$this->add([
'type' => AddressFieldset::class,
'name' => 'address',
'required' => true,
'options' => [
'use_as_base_fieldset' => false,
'label' => 'Address',
],
]);
}
}
LocationFieldsetInputFilter
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/**
* #var AddressFieldsetInputFilter
*/
protected $addressFieldsetInputFilter;
public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter)
{
$this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
}
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'address');
$this->add(
[
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]
);
}
}
Ok, so that's not very exciting yet. Do note, the LocationFieldset uses the AddressFieldset as a type. Instead, in the InputFilter class a full fledged class object (an InputFilter instance) is expected.
So, the Form. I also use an AbstractForm (BaseForm in your case) to handle a few defaults. In my complete one (in linked repo), there's a bit more, but for here this'll suffice. This adds CSRF protection to the Form and adds a submit button if the form does not have one. This only gets done if the Form class does not have either one when you call the init, so you can override these settings.
AbstractForm
abstract class AbstractForm extends \Zend\Form\Form implements InputFilterAwareInterface
{
protected $csrfTimeout = 900; // 15 minutes
public function __construct($name = null, $options = [])
{
$csrfName = null;
if (isset($options['csrfCorrector'])) {
$csrfName = $options['csrfCorrector'];
unset($options['csrfCorrector']);
}
parent::__construct($name, $options);
if ($csrfName === null) {
$csrfName = 'csrf';
}
$this->addElementCsrf($csrfName);
}
public function init()
{
if (!$this->has('submit')) {
$this->addSubmitButton();
}
}
public function addSubmitButton($value = 'Save', array $classes = null)
{
$this->add([
'name' => 'submit',
'type' => Submit::class,
'attributes' => [
'value' => $value,
'class' => (!is_null($classes) ? join (' ', $classes) : 'btn btn-primary'),
],
]);
}
public function get($elementOrFieldset)
{
if ($elementOrFieldset === 'csrf') {
// Find CSRF element
foreach ($this->elements as $formElement) {
if ($formElement instanceof Csrf) {
return $formElement;
}
}
}
return parent::get($elementOrFieldset);
}
protected function addElementCsrf($csrfName = 'csrf')
{
$this->add([
'type' => Csrf::class,
'name' => 'csrf',
'options' => [
'csrf_options' => [
'timeout' => $this->csrfTimeout,
],
],
]);
}
}
LocationForm
class LocationForm extends AbstractForm
{
public function init()
{
$this->add([
'name' => 'location',
'type' => LocationFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
parent::init();
}
}
Now we have everything to make the Form. We still need the validation. Let's create these now:
AddressFieldsetInputFilter
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'street',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
LocationFieldsetInputFilter
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
protected $addressFieldsetInputFilter;
public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter)
{
$this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
}
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'address');
$this->add(
[
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]
);
}
}
LocationFormInputFilter
class LocationFormInputFilter extends AbstractFormInputFilter
{
/** #var LocationFieldsetInputFilter */
protected $locationFieldsetInputFilter;
public function __construct(LocationFieldsetInputFilter $filter)
{
$this->locationFieldsetInputFilter = $filter
}
public function init()
{
$this->add($this->locationFieldsetInputFilter, 'location');
parent::init();
}
}
Right, that's all of the classes themselves. Do you see how they'll be nested together? This creates re-usable components, which is why I said you'll need to do this only once. Next time you need an Address or a Location, you just make sure to load the AddressFieldset and set the InputFilter in the Factory. The latter, setting the right InputFilter, is done via Setter Injection the Factories. Shown below.
AbstractFieldsetFactory
abstract class AbstractFieldsetFactory implements FactoryInterface
{
/**
* #var string
*/
protected $name;
/**
* #var string
*/
protected $fieldset;
/**
* #var string
*/
protected $fieldsetName;
/**
* #var string
*/
protected $fieldsetObject;
public function __construct($fieldset, $name, $fieldsetObject)
{
$this->fieldset = $fieldset;
$this->fieldsetName = $name;
$this->fieldsetObject = $fieldsetObject;
$this->hydrator = new Reflection(); // Replace this with your own preference, either Reflection of ZF or maybe the Doctrine EntityManager...
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$fieldset = $this->fieldset;
$fieldsetObject = $this->fieldsetObject;
/** #var AbstractFieldset $fieldset */
$fieldset = new $fieldset($this->hydrator, $this->name ?: $this->fieldsetName);
$fieldset->setHydrator(
new DoctrineObject($this->hydrator)
);
$fieldset->setObject(new $fieldsetObject());
return $fieldset;
}
}
AddressFieldsetFactory
class AddressFieldsetFactory extends AbstractFieldsetFactory
{
public function __construct()
{
parent::__construct(AddressFieldset::class, 'address', Address::class);
}
}
LocationFieldsetFactory
class LocationFieldsetFactory extends AbstractFieldsetFactory
{
public function __construct()
{
parent::__construct(LocationFieldset::class, 'location', Location::class);
}
}
AbstractFieldsetInputFilterFactory
abstract class AbstractFieldsetInputFilterFactory implements FactoryInterface
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var HydratorInterface
*/
protected $hydrator;
/**
* #var InputFilterPluginManager
*/
protected $inputFilterManager;
/**
* Use this function to setup the basic requirements commonly reused.
*
* #param ContainerInterface $container
* #param string|null $className
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function setupRequirements(ContainerInterface $container, $className = null)
{
$this->inputFilterManager = $container->get(InputFilterPluginManager::class);
}
}
AddressFieldsetInputFilterFactory
class AddressFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
parent::setupRequirements($container, Address::class);
return new AddressFieldsetInputFilter($this->hydrator);
}
}
LocationFieldsetInputFilterFactory
class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
parent::setupRequirements($container, Location::class);
/** #var AddressFieldsetInputFilter $addressFieldsetInputFilter */
$addressFieldsetInputFilter = $this->inputFilterManager->get(AddressFieldsetInputFilter::class);
return new LocationFieldsetInputFilter(
$addressFieldsetInputFilter,
$this->hydrator
);
}
}
That takes care of the FieldsetInputFilterFactory classes. Just the Form left.
In my case I use the same abstract factory class as for the Fieldset classes.
LocationFormInputFilterFactory
class LocationFormInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
parent::setupRequirements($container);
/** #var LocationFieldsetInputFilter $locationFieldsetInputFilter */
$locationFieldsetInputFilter = $this->getInputFilterManager()->get(LocationFieldsetInputFilter::class);
return new LocationFormInputFilter(
$locationFieldsetInputFilter,
$this->hydrator
);
}
}
So, that's all of the classes done. It's a complete setup. You might encounter some bugs as I modified my own code to remove getters/setters, code comments/hinting, error, property and variable checking without testing. But it should work ;)
However, we're nearly done. We still need:
config
usage in a Controller
print/use Form in a View
The config is easy:
'form_elements' => [
'factories' => [
AddressFieldset::class => AddressFieldsetFactory::class,
LocationFieldset::class => LocationFieldsetFactory::class,
LocationForm::class => LocationFormFactory::class,
],
],
'input_filters' => [
'factories' => [
AddressFieldsetInputFilter::class => AddressFieldsetInputFilterFactory::class,
LocationFieldsetInputFilter::class => LocationFieldsetInputFilterFactory::class,
LocationFormInputFilter::class => LocationFormInputFilterFactory::class,
],
],
That's it. That little bit ties all of the above classes together.
To get the Form into a Controller, you would do something like this in the Factory:
class EditControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$hydrator = new Reflection(); // or $container->get('hydrator') or $container->get(EntityManager::class), or whatever you use
/** #var FormElementManagerV3Polyfill $formElementManager */
$formElementManager = $container->get('FormElementManager');
/** #var LocationForm $form */
$form = $formElementManager->get(LocationForm::class); // See :) Easy, and re-usable
return new EditController($hydrator, $form);
}
}
A typical "Edit" action would be like this (mind, this one uses Doctrine's EntityManager as the hydrator):
public function editAction()
{
$id = $this->params()->fromRoute('id', null);
/** #var Location $entity */
$entity = $this->getObjectManager()->getRepository(Location::class)->find($id);
/** #var LocationForm $form */
$form = $this->form;
$form->bind($entity);
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
/** #var Location $object */
$object = $form->getObject();
$this->getObjectManager()->persist($object);
try {
$this->getObjectManager()->flush();
} catch (Exception $e) {
// exception handling
}
return $this->redirect()->toRoute('route/name', ['id' => $object->getId()]);
}
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
And the View Partial would look like this (based on the return in the above action):
form($form) ?>
So, that's it. Fully fledged, re-usable classes. Single setup. And in the end just a single line in the Factory for the Controller.
Please take note though:
Form, Fieldset and InputFilter use "address" input name. Very important to keep these the same throughout as Zend does some magic based on the names to match Fieldset with InputFilter.
If you have any more questions about how this works, please read through the documentation in the repo's I linked first, before asking below this question. There's more there that should help you out more, for example for Collection handling.

Symfony 3 FormTypeTest

Following the Symfony 3.0 manual on testing Form Types, I followed the example for testing forms with dependencies as follows;
<?php
namespace Tests\AppBundle\Form;
use AppBundle\Form\Type\Database\OfficeAssignType;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class OfficeTypeTest extends TypeTestCase
{
private $entityManager;
protected function setUp()
{
// mock any dependencies
$this->entityManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
parent::setUp();
}
protected function getExtensions()
{
// create a type instance with the mocked dependencies
$office = new OfficeType($this->entityManager);
return array(
// register the type instances with the PreloadedExtension
new PreloadedExtension(array($office), array()),
);
}
public function testSubmitValid()
{
$formData = array(
'name' => 'new test office',
'address1' => 'Test new office address',
'city' => 'Test office city',
'phone' => 'testoffice#stevepop.com',
'country' => '235',
'currrency' => '1',
);
// Instead of creating a new instance, the one created in
// getExtensions() will be used
$form = $this->factory->create(OfficeType::class);
// submit data to form
//$form->submit($formData);
//$this->assertTrue($form->isSynchronized());
}
}
When I run the tests, I get the following error
Argument 1 passed to Symfony\Bridge\Doctrine\Form\Type\DoctrineType::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, none given, called in /www/stevepop.com/symfony/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php on line 85 and defined
The OfficeType is below;
namespace AppBundle\Form\Type\Database;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class OfficeType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class, array(
'required' => true,
));
$builder->add('address1', TextType::class, array(
'required' => true,
));
$builder->add('country', EntityType::class, array(
'class' => 'AppBundle:Country',
'attr' => array(
'class' => 'input-sm',
),
'required' => true,
'placeholder' => "Non selected",
));
$builder->add('currency', EntityType::class, array(
'class' => 'AppBundle:Currency',
'attr' => array(
'class' => 'input-sm',
),
'required' => true,
'placeholder' => "Non selected",
));
}
I am not sure what this means to be honest and will appreciate help from anyone who has done FormType unit tests in Symfony 2.8/3.0.
Thank you.
Try to adding this to your tests:
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Component\Form\Extension\Core\CoreExtension;
class OfficeType extends AbstractType {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
public function setUp()
{
$this->em = DoctrineTestHelper::createTestEntityManager();
parent::setUp();
}
protected function getExtensions()
{
$manager = $this->createMock('Doctrine\Common\Persistence\ManagerRegistry');
$manager->expects($this->any())
->method('getManager')
->will($this->returnValue($this->em));
$manager->expects($this->any())
->method('getManagerForClass')
->will($this->returnValue($this->em));
return array(
new CoreExtension(),
new DoctrineOrmExtension($manager),
);
}
// your code...
}

Extends User plugin by adding a profile does not render tab either new added fields in OctoberCMS

I've follow all the steps on the Extending User plugin screencast but for some reason I can not see "Profile" tab and either new added fields. Since I used the second approach, the easy one, this is what I've done:
Create the plugin and models and so on under Alomicuba namespace
Create and make the needed changes to the files as explained in video:
Plugin.php
<?php namespace Alomicuba\Profile;
use System\Classes\PluginBase;
use RainLab\User\Models\User as UserModel;
use RainLab\User\Controllers\Users as UsersController;
/**
* Profile Plugin Information File
*/
class Plugin extends PluginBase
{
public $requires = ['RainLab.User'];
/**
* Returns information about this plugin.
*
* #return array
*/
public function pluginDetails()
{
return [
'name' => 'Profile',
'description' => 'Add extra functionalities for Alomicuba WS by extends RainLab User',
'author' => 'DTS',
'icon' => 'icon-users'
];
}
public function boot()
{
UserModel::extend(function($model){
$model->hasOne['profile'] = ['Alomicuba\Profile\Models\Profile'];
});
UsersController::extendFormFields(function ($form, $model, $context){
if ($model instanceof UserModel)
return;
$form->addTabFields([
'pinCode' => [
'label' => 'PIN',
'tab' => 'Profile'
],
'phone2' => [
'label' => 'Teléfono (2)',
'tab' => 'Profile'
],
'phone3' => [
'label' => 'Teléfono (3)',
'tab' => 'Profile'
],
'phone4' => [
'label' => 'Teléfono (4)',
'tab' => 'Profile'
]
]);
});
}
}
add_profiles_fields_to_user_table.php
<?php namespace Alomicuba\Profile\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class AddProfilesFieldsToUserTable extends Migration
{
public function up()
{
Schema::table('users', function($table)
{
$table->integer('pinCode')->unsigned();
$table->dateTime('pinCodeDateTime');
$table->integer('phone2')->unsigned()->nullable();
$table->integer('phone3')->unsigned()->nullable();
$table->integer('phone4')->unsigned()->nullable();
});
}
public function down()
{
$table->dropDown([
'pinCode',
'pinCodeDateTime',
'phone2',
'phone3',
'phone4'
]);
}
}
version.yaml
1.0.1: First version of Profile
1.0.2:
- Created profiles table
- create_profiles_table.php
- add_profiles_fields_to_user_table.php
Profile.php (Model)
<?php namespace Alomicuba\Profile\Models;
use Model;
/**
* Profile Model
*/
class Profile extends Model
{
/**
* #var string The database table used by the model.
*/
public $table = 'alomicuba_profile_profiles';
/**
* #var array Relations
*/
public $belongsTo = [
'user' => ['RainLab\User\Models\User']
];
// This method is not need anymore since I'll use the second approach
public static function getFromUser($user)
{
if ($user->profile)
return $user->profile;
$profile = new static;
$profile->user = $user;
$profile->save();
$user->profile = $profile;
return $profile;
}
}
But when I edit a existent user I didn't see the 'Profile' tab and also didn't see any new added field. See image below:
Any advice around this? Did I miss something?
Also I have a few question around plugin extends:
How do I add a required field to the register form?
How do I display each new added field on the account form?
I haved tested your code on my machine you need to write
$require instead of $requires in plugin.php
please check documentation
http://octobercms.com/docs/plugin/registration#dependency-definitions
and when extendFormFields() method called for UserController you need to specify that you only want to extends fields for UserModel not for other
if (!$model instanceof UserModel)
return;
so plugin.php code look like this
<?php namespace Alomicuba\Profile;
use System\Classes\PluginBase;
use RainLab\User\Models\User as UserModel;
use RainLab\User\Controllers\Users as UsersController;
/**
* Profile Plugin Information File
*/
class Plugin extends PluginBase
{
public $require = ['RainLab.User'];
/**
* Returns information about this plugin.
*
* #return array
*/
public function pluginDetails()
{
return [
'name' => 'Profile',
'description' => 'Add extra functionalities for Alomicuba WS by extends RainLab User',
'author' => 'DTS',
'icon' => 'icon-users'
];
}
public function boot()
{
UserModel::extend(function($model){
$model->hasOne['profile'] = ['Alomicuba\Profile\Models\Profile'];
});
UsersController::extendFormFields(function ($form, $model, $context){
if (!$model instanceof UserModel)
return;
$form->addTabFields([
'pinCode' => [
'label' => 'PIN',
'tab' => 'Profile'
],
'phone2' => [
'label' => 'Teléfono (2)',
'tab' => 'Profile'
],
'phone3' => [
'label' => 'Teléfono (3)',
'tab' => 'Profile'
],
'phone4' => [
'label' => 'Teléfono (4)',
'tab' => 'Profile'
]
]);
});
}
}
and in add_profiles_fields_to_user_table.php
for dropping column write following code
Schema::table('users', function($table)
{
$table->dropDown([
'pinCode',
'pinCodeDateTime',
'phone2',
'phone3',
'phone4'
]);
}

How to pass options/params to formCollection fieldset in ZendFramework 2?

I have a Form which contains a formCollection with a select element that I want to populate with values (from SQL) depending on a POST param.
Passing this param from controller to form wasn't a problem but now I can't find a way to set/read that param in target_element of formCollection. Any ideas on how make that work?
Here's my code:
Controller
class MyController extends AbstractActionController{
public function indexAction()
{
$form = $this->serviceLocator->get('FormElementManager')->get('Module\Form\myForm');
$form->init([
'param' => $this->params()->fromPost('param')
]);
}
}
Form
class myForm extends Form
{
private $sm;
public function __construct($sm = null)
{
parent::__construct();
$this->sm = $sm;
}
public function init($params=[])
{
$this->add([
'name' => 'choices',
'type' => 'Zend\Form\Element\Collection',
'options' => [
'label' => 'SelectLabel',
'count' => 1,
'should_create_template' => true,
'allow_add' => true,
'template_placeholder' => '__placeholder__',
'target_element' => [
'type' => 'Module\Form\choicesFieldset',
'options' => [
'param' => isset($params['param']) ? $params['param'] : 0,
]
],
],
]);
}
}
Fieldset
class choicesFieldset extends Fieldset{
private $sm;
public function __construct($sm = null){
parent::__construct();
$this->sm = $sm;
}
public function init(){
$param = $this->getOption('param');
$availableChoices = /* SQL_QUERY_BASED_ON_PARAM; */
$this->add([
'name' => 'choice_1',
'type' => 'Select',
'options' => [
'label' => 'First choice',
'value_options' => $availableChoices,
]
]);
}
}
Thanks in advance for your help.
All you would need to do is fetch the Request instance from the service manager; check the parameter you want and then 'inject' it into the form.
It would make more sense to do so in the form factory; rather than repeat yourself within controllers.
For example:
public function getFormElementConfig()
{
return array(
'factories' => array(
'MyModule\Form\MyForm' => function($formElementManager) {
$serviceManager = $formElementManager->getServiceLocator();
$request = $serviceManager->get('Request');
// defaults to 0 if not set
$param = $request->getPost('the_posted_variable_name', 0);
$options = array(
'my_custom_option_name' => $param,
);
// You should maintain the Zend\Form\Element::__construct() method signuture
// as it allows for the 'options' to be passed in.
// Alternatively you could use $form->setOption('param', $options)
// and inject the options as a soft dependency
$form = new Form\MyForm('my_form', $options, $serviceManager);
// ... other form stuff here
return $form;
},
),
);
}
Now you can use the option within the form using:
$param = $this->getOption('my_custom_option_name');

form not binding post values to entity

I have a doctrine entity, a form and 2 fieldsets.
When i populate the entity with values the values get hydrated into the form as expected.
When i try to create a entity from form data it stays empty..
i must be forgetting something but just cant find it, i have several other forms without fieldsets and they work as expected.
any ideas?
posted my code below
Entity:
class User
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=255, unique=true, nullable=true)
*/
protected $username;
..
}
Form:
class CreateUserForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('create-user');
$this->setAttribute('method', 'post');
// The form will hydrate an object
$this->setHydrator(new DoctrineHydrator($objectManager));
$userFieldset = new UserFieldset($objectManager);
$this->add($userFieldset);
// … add CSRF and submit elements …
$baseFieldset = new BaseFieldset($objectManager);
$baseFieldset->setUseAsBaseFieldset(true);
$this->add($baseFieldset);
}
}
UserFIeldset:
class UserFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct($objectManager)
{
parent::__construct($name = 'user');
$this->setHydrator(
new DoctrineHydrator($objectManager, 'YrmUser\Entity\User')
)->setObject(new User());
$this->add(
array(
'name' => 'username',
'attributes' => array(
'type' => 'text',
'placeholder' =>'Username',
),
'options' => array(
'label' => 'Username',
),
)
);
...
}
}
BaseFieldset:
class BaseFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct($objectManager)
{
parent::__construct('base');
$this->setHydrator(new DoctrineHydrator($objectManager));
$this->add(
array(
'name' => 'security',
'type' => 'Zend\Form\Element\Csrf',
'options' => array(
'csrf_options' => array(
'timeout' => 600
)
)
)
);
$this->add(
array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Save',
'class' => 'btn btn-success btn-lg confirm',
),
)
);
}
}
controller action:
public function createAction()
{
$form = new CreateUserForm($this->getObjectManager());
$entity = new User();
$form->bind($entity);
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getObjectManager()->persist($entity);
$this->getObjectManager()->flush();
return $this->redirect()->toRoute($this->redirect);
}
}
return array(
'form' => $form
);
}
Can you var_dump $form->getData() after $form->isValid() ?
Alternatively, you can try $form->isValid($request->getPost()) instead of setData().

Categories