I'm trying to implement my zend navigation from a container in ZF3. I have successfully created navigation with this quick start tutorial introducing navigation directly in config/autoload/global.php or config/module.config.php files:
https://docs.zendframework.com/zend-navigation/quick-start/
But now I need to make it work these with the helpers to allow navigation modifications from the controller, using the "Navigation setup used in examples" section:
https://docs.zendframework.com/zend-navigation/helpers/intro/
This is my Module.php
namespace Application;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\View\HelperPluginManager;
class Module implements ConfigProviderInterface
{
public function getViewHelperConfig()
{
return [
'factories' => [
// This will overwrite the native navigation helper
'navigation' => function(HelperPluginManager $pm) {
// Get an instance of the proxy helper
$navigation = $pm->get('Zend\View\Helper\Navigation');
// Return the new navigation helper instance
return $navigation;
}
]
];
}
public function getControllerConfig()
{
return [
'factories' => [
$this->getViewHelperConfig()
);
},
],
];
}
}
And this is my IndexController.php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Navigation\Navigation;
use Zend\Navigation\Page\AbstractPage;
class IndexController extends AbstractActionController
{
private $navigationHelper;
public function __construct(
$navigationHelper
){
$this->navigationHelper = $navigationHelper;
}
public function indexAction()
{
$container = new Navigation();
$container->addPage(AbstractPage::factory([
'uri' => 'http://www.example.com/',
]));
$this->navigationHelper->plugin('navigation')->setContainer($container);
return new ViewModel([
]);
}
}
But then I get the following error:
Fatal error: Call to a member function plugin() on array in /var/www/html/zf3/module/Application/src/Controller/IndexController.php on line 50
In the tutorial they use the following statement:
// Store the container in the proxy helper:
$view->plugin('navigation')->setContainer($container);
// ...or simply:
$view->navigation($container);
But I don´t know what this $view is, so I assume is my $navigation from my Module.php. The problem is that, because is an array, it throws the error. The questions are:
What am I doing wrong?
Where this $view of the tutorial come from?
What I should pass from my Module.php to get it work?
Thanks in advance!
Add into module.config.php
'service_manager' => [
'factories' => [
Service\NavManager::class => Service\Factory\NavManagerFactory::class,
],
],
'view_helpers' => [
'factories' => [
View\Helper\Menu::class => View\Helper\Factory\MenuFactory::class,
],
'aliases' => [
'mainMenu' => View\Helper\Menu::class,
],
],
Create Factory in Service Directory:
namespace Application\Service\Factory;
use Interop\Container\ContainerInterface;
use Application\Service\NavManager;
class NavManagerFactory {
/**
* This method creates the NavManager service and returns its instance.
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
$authService = $container->get(\Zend\Authentication\AuthenticationService::class);
$viewHelperManager = $container->get('ViewHelperManager');
$urlHelper = $viewHelperManager->get('url');
return new NavManager($authService, $urlHelper);
}
}
Create NavManager file :
namespace Application\Service;
class NavManager {
/**
* Auth service.
* #var Zend\Authentication\Authentication
*/
private $authService;
/**
* Url view helper.
* #var Zend\View\Helper\Url
*/
private $urlHelper;
/**
* Constructs the service.
*/
public function __construct($authService, $urlHelper) {
$this->authService = $authService;
$this->urlHelper = $urlHelper;
}
/**
* Menu render based on user role
*
* #return array
*/
public function getMenuItems() {
$navItem = array();
$url = $this->urlHelper;
$items = [];
$items[] = [
'label' => 'Dashboard',
'icon' => 'dashboard',
'link' => $url('home'),
'route' => ['home'],
];
$items[] = [
'label' => 'About Us',
'icon' => 'business',
'link' => $url('about', ['action'=>'index']),
'route' => ['about'],
];
$items[] = [
'label' => 'Service',
'icon' => 'service',
'link' => $url('service', ['action'=>'index']),
'route' => ['service'],
];
return $items;
}
Create Helper Factory
namespace Application\View\Helper\Factory;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\View\Helper\Menu;
use Application\Service\NavManager;
/**
* This is the factory for Menu view helper. Its purpose is to
instantiate the helper and init menu items. */
class MenuFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
$navManager = $container->get(NavManager::class);
// Get menu items.
$items = $navManager->getMenuItems();
// Instantiate the helper.
return new Menu($items);
}
}
Create Helper :
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
/**
* This view helper class displays a menu bar.
*/
class Menu extends AbstractHelper {
/**
* Menu items array.
* #var array
*/
protected $items = [];
/**
* Active item's ID.
* #var string
*/
protected $activeItemId = '';
/**
* Constructor.
* #param array $items Menu items.
*/
public function __construct($items=[]) {
$this->items = $items;
}
/**
* Sets menu items.
* #param array $items Menu items.
*/
public function setItems($items) {
$this->items = $items;
}
/**
* Sets ID of the active items.
* #param string $activeItemId
*/
public function setActiveItemId($activeItemId) {
$this->activeItemId = $activeItemId;
}
/**
* Renders the menu.
* #return string HTML code of the menu.
*/
public function render() {
if (count($this->items)==0) {
return ''; // Do nothing if there are no items.
}
// Render items
foreach ($this->items as $item) {
$result .= $this->renderItem($item);
}
return $result;
}
protected function renderItem($item) {
// here you can integrate with HTML
return $item;
}
}
After add above codes,just add below code in your layout file :
echo $this->mainMenu()->render();
Related
I'm trying to makes a JWT Filter with CI4, but when I about to set my filter in /Config/Filter.php it always says the class is not found although it definitely in the /App/Filters/ directory. I've been trying to make new directory in /App and named it AuthFilter still no luck
/Config/Filter.php :
<?php
namespace Config;
use App\Filters\FilterJwt;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\SecureHeaders;
class Filters extends BaseConfig
{
/**
* Configures aliases for Filter classes to
* make reading things nicer and simpler.
*/
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'authentification' => FilterJwt::class
];
/**
* List of filter aliases that are always
* applied before and after every request.
*/
public array $globals = [
'before' => [
// 'honeypot',
// 'csrf',
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
];
/**
* List of filter aliases that works on a
* particular HTTP method (GET, POST, etc.).
*
* Example:
* 'post' => ['foo', 'bar']
*
* If you use this, you should disable auto-routing because auto-routing
* permits any HTTP method to access a controller. Accessing the controller
* with a method you don’t expect could bypass the filter.
*/
public array $methods = [];
/**
* List of filter aliases that should run on any
* before or after URI patterns.
*
* Example:
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
* #var array
*/
public $filters = [
'authentification' => [
'before' => [
'users/*',
'users'
]
]
];
}
/App/Filters/FilterJwt.php :
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\API\ResponseTrait;
use Exeption;
class MyFilter implements FilterInterface
{
use ResponseTrait;
public function before(RequestInterface $request, $arguments = null)
{
$header = $request->getServer('HTTP_AUTHORIZATION');
try {
helper('jwt');
$encodedToken = getJwt($header);
checkJWT($encodedToken);
return $request;
} catch (Exeption $e) {
return Services::response()->setJson([
'error' => $e->getMessage()
])->setStatusCode(ResponseInterface::HTTP_UNAUTHORIZED);
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Do something here
}
}
Proof that the file is in directory :
CI4 error:
As you can see, the CI4 cant find the "Filters" folder although its right there.
Rename your class from "MyFilter" to "FilterJwt".
Instead of:❌
/App/Filters/FilterJwt.php
class MyFilter implements FilterInterface
{
//...
Use this:✅
/App/Filters/FilterJwt.php
class FilterJwt implements FilterInterface
{
//...
yii advanced 2.0.13
#property $content keeps array (before saving in DB it will be encoded in json in database.)
#property string $name
So in ActiveForm I have inputs with names like content[image] or content[anykey], also I have not-array properties like name; I want to put new values after Post, well:
echo $model->name; // output: Test
$model->name = 'any new name';
echo $model->name; // any new name
It works, but
print_r($model->content);
/* output
Array
(
[title] => bla
[anykey] =>
[seo-title] => bla-bla
)*/
$model->content['anykey'] = 'bla-bla-bla';
echo $model->content['anykey']; // null output:
It doesn't. We can not set new value in array property, so I tried next tip:
$content = $model->content;
$content['anykey'] = 'bla-bla-bla';
$model->content = $content;
echo $model->content['anykey']; // bla-bla-bla
It works
Can somebody explain, why is this happening?
Controller
<?php
namespace backend\controllers;
use Yii;
use common\models\Categories;
use backend\models\CategoriesCRUD;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii\web\UploadedFile;
/**
* CategoriesController implements the CRUD actions for Categories model.
*/
class CategoriesController extends Controller
{
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
];
}
...
/**
* Updates an existing Categories model.
* If update is successful, the browser will be redirected to the 'view' page.
* #param integer $id
* #return mixed
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
// here I put code above
$model->content['anykey'] = 'bla-bla-bla';
echo $model->content['anykey']; // old value
// die();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('update', [
'model' => $model,
]);
}
}
}
Model
<?php
namespace common\models;
use Yii;
use yii\web\UploadedFile;
/**
* This is the model class for table "{{%categories}}".
*
* #property integer $id
* #property string $name
* #property integer $status
* #property integer $sort_order
* #property integer $parent_id
* #property string $content
*
* #property Categories $parent
* #property Categories[] $categories
* #property CategoriesRelationships[] $categoriesRelationships
* #property CategoriesRelationships[] $categoriesRelationships0
* #property ProductsToCategories[] $productsToCategories
*/
class Categories extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return '{{%categories}}';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['name'], 'required'],
[['status', 'sort_order'], 'integer'],
['content', 'each', 'rule' => ['trim']],
[['name'], 'string', 'max' => 255],
[['parent_id'], 'exist', 'skipOnError' => true, 'targetClass' => Categories::className(), 'targetAttribute' => ['parent_id' => 'id']],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'name' => 'Заголовок',
'status' => 'Показывать',
'sort_order' => 'Порядок сортировки',
'parent_id' => 'Родительская категория',
'content' => 'Содержание',
];
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
/* Обработка данных перед занесением в базу
в content содержится любая контентная информация
*/
// #TODO добавить собственную валидацию изображений и свои методы для их обработки (Для категорий, аватарок и прч)
// here I put solution, but before I found it I've been trying in Controller
$content = (array) $this->content;
$content['image'] = $this->uploadImage($this, $content, 'image', 'content[image]');
$this->content = $content;
$this->content = json_encode($this->content, JSON_UNESCAPED_UNICODE);
return true;
}
return false;
}
}
Although it is not directly an answer to your question:
I'd suggest to use different attributes for the serialized value, say serializedContent. This way - content is always an array, and serializedContent is always a json string.
Then create a setter and a getter in your model:
public function setContent($value) {
$this->serializedContent = json_encode($value);
}
public function getContent() {
return json_decode($this->serializedContent,true);
}
I'm building a small application with ZF2 and Doctrine2. Setting it up in such a way as to have a lot of reusable code and technique. However, getting stumped by the fact that my InputFilter is not automatically injected into the Fieldset that it should get associated to.
I've confirmed that the Form that uses the Fieldset works (without the InputFilter). The InputFilter is also visible as present during debugging.
The question then, what am I doing wrong and how to fix having a separate InputFilter, coupled to a Fieldset in ZF2?
Sidenotes:
1 - I am aware that by using the InputFilterInterface I could have the InputFilter inside of the Fieldset class with the getInputFilterSpecification() function. However, as I'm trying to keep it DRY and reusable, it wouldn't do to have to copy it if I were to create an API that needs to use the Entity and InputFilter, but can only have the latter coupled with a Fieldset.
2 - A lot of Abstract classes are used, where used I'll indicate in the snippets what they have that's relevant
3 - The problem line is in CustomerFieldsetFactory.php
=========================================================================
Entity: Customer.php
/**
* Class Customer
* #package Customer\Entity
*
* #ORM\Entity
* #ORM\Table(name="customers")
*/
class Customer extends AbstractEntity //Contains $id
{
/**
* #var string
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
}
Form: CustomerForm.php
class CustomerForm extends AbstractForm
{
public function __construct($name = null, array $options)
{
parent::__construct($name, $options); // Adds CSRF
}
public function init()
{
$this->add([
'name' => 'customer',
'type' => CustomerFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
//Call parent initializer. Check in parent what it does.
parent::init(); //Adds submit button if not in form
}
}
Fieldset: CustomerFieldset.php
class CustomerFieldset extends AbstractFieldset //Contains EntityManager property and constructor requirement (as we're managing Doctrine Entities here)
{
public function init()
{
$this->add([ //For now id field is here, until InputFilter injection works
'name' => 'id',
'type' => Hidden::class,
'attributes' => [
'id' => 'entityId',
],
]);
$this->add([
'name' => 'name',
'type' => Text::class,
'options' => [
'label' => _('Name'),
],
]);
}
}
InputFilter: CustomerInputFilter.php
class CustomerInputFilter extends AbstractInputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
Above the classes. Below the Factories
FormFactory: CustomerFormFactory.php
class CustomerFormFactory implements FactoryInterface, MutableCreationOptionsInterface
{
/**
* #var array
*/
protected $options;
/**
* #param array $options
*/
public function setCreationOptions(array $options)
{
//Arguments checking removed
$this->options = $options;
}
/**
* #param ServiceLocatorInterface|ControllerManager $serviceLocator
* #return CustomerForm
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$form = new CustomerForm($this->options['name'], $this->options['options']);
$form->setTranslator($serviceManager->get('translator'));
return $form;
}
}
FieldsetFactory: CustomerFieldsetFactory.php
class CustomerFieldsetFactory implements FactoryInterface, MutableCreationOptionsInterface
{
/**
* #var string
*/
protected $name;
public function setCreationOptions(array $options)
{
//Argument checking removed
$this->name = $options['name'];
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$fieldset = new CustomerFieldset($serviceManager->get('Doctrine\ORM\EntityManager'), $this->name);
$fieldset->setHydrator(new DoctrineObject($serviceManager->get('doctrine.entitymanager.orm_default'), false));
$fieldset->setObject(new Customer());
$fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class));
//RIGHT HERE! THE LINE ABOVE IS THE ONE THAT DOES NOT WORK!!!
return $fieldset;
}
}
InputFilterFactory: CustomerInputFilterFactory.php
class CustomerInputFilterFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$repository = $serviceLocator->getServiceLocator()
->get('Doctrine\ORM\EntityManager')
->getRepository(Customer::class);
return new CustomerInputFilter($repository);
}
}
Config: module.config.php
'controllers' => [
'factories' => [
CustomerController::class => CustomerControllerFactory::class,
],
],
'form_elements' => [
'factories' => [
CustomerForm::class => CustomerFormFactory::class,
CustomerFieldset::class => CustomerFieldsetFactory::class,
],
],
'input_filters' => [
'factories' => [
CustomerInputFilter::class => CustomerInputFilterFactory::class,
],
],
'service_manager' => [
'invokables' => [
CustomerControllerService::class => CustomerControllerService::class,
],
],
I am hoping one of you can help me out here.
EDIT: Update with actual error
The following line in the CustomerFieldset.php (above) triggers the error.
$fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class));
The error:
Fatal error: Call to undefined method Customer\Fieldset\CustomerFieldset::setInputFilter() in D:\htdocs\server-manager\module\Customer\src\Customer\Factory\CustomerFieldsetFactory.php on line 57
As seen in the above snippet, the InputFilter (and it's Factory) are known the the InputFilterManager.
The error states it does not know the getInputFilter() function on the Fieldset. Which is correct in a way, it doesn't exist. The question then is, how to have the function exist so that injecting the InputFilter will work, or how to bind this InputFilter to the Fieldset?
EDIT 2: Update based on Wilt's answer
Added use InputFilterAwareTrait to Abstract class AbstractInputFilter to create following (from answer):
use Zend\InputFilter\InputFilterAwareTrait;
abstract class AbstractFieldset extends Fieldset
{
use InputFilterAwareTrait;
// ... Other code
}
Turns out that I had another mistake in the (original) code above as well:
In file module.config.php the input_filters should've been input_filter_specs. This was (after using the Trait) a Invalid Factory registered error (titled ServiceNotCreatedException).
The following might be of use to someone, the Factory to create a Fieldset with Hydrator, Object and Inputfilter has the following createService() function:
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** #var ServiceLocator $serviceManager */
$serviceManager = $serviceLocator->getServiceLocator();
/** #var CustomerRepository $customerRepository */
$customerRepository = $serviceManager->get('Doctrine\ORM\EntityManager')->getRepository(Customer::class);
$fieldset = new CustomerFieldset($serviceManager->get('Doctrine\ORM\EntityManager'), $this->name);
$fieldset->setHydrator(new DoctrineObject($serviceManager->get('doctrine.entitymanager.orm_default'), false));
$fieldset->setObject(new Customer());
$fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class, $customerRepository));
return $fieldset;
}
There is lots of information added to your question. I'd suggest you try to narrow down your question in the future. Read more on the guidelines for a good question here: How to create a Minimal, Complete, and Verifiable example.
Zend framework provides a InputFilterAwareTrait with both the setInputFilter and getInputFilter methods. You can easily implement/use this trait inside your CustomerFieldset class:
use Zend\InputFilter\InputFilterAwareTrait;
class CustomerFieldset extends AbstractFieldset
{
use InputFilterAwareTrait;
//...remaining code
}
In case you want the inputfilter in all classes that extend your abstract AbstractFieldset class you could also decide to add the trait there:
use Zend\InputFilter\InputFilterAwareTrait;
class AbstractFieldset
{
use InputFilterAwareTrait;
//...remaining code
}
See the following question: How to validate nested fieldsets. Fieldsets don't contain InputFilters but you should extend your base InputFilter of your form.
Create an InputFilter for each Fieldset and add them, with the same name as your fieldset, to your InputFilter of your form. As seen within the answer of the other question I linked.
If you don't want to do this you might consider working with InputSpecification.
I am creating a form which needs dynamic options based on the route value of survey_question_reference
'main-surveyquestions'=> [
'type' => 'segment',
'options' => [
'route' => '/survey-questions[/:survey_question_reference][/:answer]',
'constraints' => [
'survey_question_reference' => '[0-9]*',
'answer' => '(answer)',
],
'defaults' => [
'controller' => 'Main\Controller\Main',
'action' => 'surveyquestions'
]
]
],
This is the Form code which calls the FormElement:
/**
* Init
*/
public function init()
{
/**
* Survey Answer
*/
$this->add(
[
'type' => 'Main\Form\Element\SurveyAnswerRadio',
'name' => 'survey_answer',
'options' => [
'label' => 'survey_answer'
],
'attributes' => [
'id' => 'survey_answer'
]
]
);
}
The following is the code from the Form Element. Where I have hard coded 'sqReference' => '1' the 1 needs to be replaced with the value of survey_question_reference from the route.
namespace Main\Form\Element;
use Doctrine\ORM\EntityManager;
use Zend\Form\Element\Radio;
/**
* Class SurveyAnswerRadio
*
* #package Main\Form\Element
*/
class SurveyAnswerRadio extends Radio
{
/**
* #var EntityManager $entityManager
*/
protected $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Get Value Options
*
* #return array
*
* #throws \Exception
*/
public function getValueOptions()
{
$array = [];
$result = $this->entityManager
->getRepository('AMDatabase\Entity\TheVerse\SA')
->findBy(
[
'sqReference' => '1'
],
[
'surveyAnswer' => 'ASC'
]
);
if (is_array($result) && count($result) > '0') {
/**
* #var \AMDatabase\Entity\TheVerse\SA $val
*/
foreach ($result as $val) {
$array[$val->getReference()] = $val->getSurveyAnswer();
}
}
return $array;
}
}
What you're looknig for is to inject the survey_question_reference parameter to your FormElement. You could do that as suggested by #kuldeep.kamboj in his answers. But if you don't want to change your approach and keep your custom SurveyAnswerRadio element, you have to make some fiew changes in your code :
Make SurveyAnswerRadio implements Zend\ServiceManager\ServiceLocatorAwareInterface so that you could implement setServiceLocator and getServiceLocator, which are required by the ServiceManager to automatically inject the service locator when the element is instantiated.
Your form should also implements Zend\ServiceManager\ServiceLocatorAwareInterface.
Implement the getFormElementConfig method in your Module.php.
Let’s look through the code now. You'll have something like this :
SurveyAnswerRadio :
class SurveyAnswerRadio extends Radio implements ServiceLocatorAwareInterface
{
//Add these two methods
public function setServiceLocator(ServiceLocatorInterface $sl)
{
$this->serviceLocator = $sl;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function getValueOptions()
{
$array = [];
$serviceManager = $this->serviceLocator->getServiceLocator();
$em = $serviceManager->get('Doctrine\ORM\EntityManager');
$sqReference = $serviceManager->get('application')->getMvcEvent()
->getRouteMatch()->getParam('survey_question_reference');
$result = $em->getRepository('AMDatabase\Entity\TheVerse\SA')
->findBy(
['sqReference' => $sqReference],
['surveyAnswer' => 'ASC']
);
if (is_array($result) && count($result) > '0') {
foreach ($result as $val) {
$array[$val->getReference()] = $val->getSurveyAnswer();
}
}
return $array;
}
}
Module.php :
Implement the getFormElementConfig method as follows. This allows the class ModuleName\Form\Element\SurveyAnswerRadio to be instantiated, or invoked, with the alias SurveyAnswerRadio.
class Module implements FormElementProviderInterface
{
// other stuff .....
public function getFormElementConfig()
{
return array(
'invokables' => array(
'SurveyAnswerRadio' => 'ModuleName\Form\Element\SurveyAnswerRadio'
)
);
}
}
No changes needed in the Form init method let it as it is.
Note that in your controller, you'll have to instantiate the Form via the FormElementManager :
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('ModuleName\Form\YourForm');
Please see more details in the documentation
See also this post which exaplains how to manage dependencies within a custom Select Element in Form.
I will suggest to change approach. First do not try to extends Radio Element which is not necessary at all. You can do same in your Form Class. Second your entity manager also not work in Radio/Form class until your find mechanism to pass.
So I would suggest solutions like below.
First register your form class into as factory in module.config.php
'form_elements' => array(
'factories' => array(
'Main\Form\YourFormName' => function($sm) {
$form = new Form\YourFormName();
$form->setEntityManager($sm->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
$form->setServiceLocator($sm->getServiceLocator());
return $form;
},
),
),
Then implement entityManager and serviceLocator into your form class.
use DoctrineModule\Persistence\ObjectManagerAwareInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class YourFormName extends Form implements ObjectManagerAwareInterface, ServiceLocatorAwareInterface
{
protected $entityManager;
protected $serviceLocator;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function setEntityManager(ObjectManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function getEntityManager()
{
return $this->entityManager;
}
Then in init method you have serviceLocator/entityManager is already initialized.
public function init()
{
$routeMatch = $this->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch();
$array = [];
$result = $this->entityManager
->getRepository('AMDatabase\Entity\TheVerse\SA')
->findBy(
[
'sqReference' => $routeMatch->getParam('survey_question_reference')
],
[
'surveyAnswer' => 'ASC'
]
);
if (is_array($result) && count($result) > '0') {
/**
* #var \AMDatabase\Entity\TheVerse\SA $val
*/
foreach ($result as $val) {
$array[$val->getReference()] = $val->getSurveyAnswer();
}
}
$this->add(
[
'type' => 'Zend\Form\Element\Radio',
'name' => 'survey_answer',
'options' => [
'label' => 'survey_answer',
'value_options' => $array,
],
'attributes' => [
'id' => 'survey_answer',
]
]
);
I setup a basic proof of concept involving Musicians and Albums for binding a form with a form collection in Zend Framework.
Here is the Musician Class:
<?php
namespace Application\Entity;
class Musician {
protected $name;
protected $albums;
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setAlbums($album)
{
$this->album = $album;
return $this;
}
public function getAlbums()
{
return $this->albums;
}
Here is the Album Class:
<?php
namespace Application\Entity;
class Album {
protected $name;
protected $releaseYear;
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setReleaseYear($releaseYear)
{
$this->releaseYear = $releaseYear;
return $this;
}
public function getReleaseYear()
{
return $this->releaseYear;
}
}
Album Fieldset:
Album Field Set:
<?php
namespace Application\Form\Music;
use Zend\Form\Fieldset;
use Zend\Stdlib\Hydrator\ClassMethods;
use Zend\Validator;
use Zend\Form\Element;
use Application\Entity\Album;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\InputFilter\InputFilterProviderInterface;
class AlbumFieldSet extends Fieldset implements InputFilterProviderInterface, ServiceLocatorAwareInterface
{
public function __construct()
{
parent::__construct('album');
$this->setObject(new Album());
$this->setHydrator(new ClassMethods());
$this->add(array(
'type' => 'Text',
'name' => 'name',
'options' => [
]
));
$this->add(array(
'type' => 'Text',
'name' => 'releaseYear',
'options' => [
]
));
}
public function init()
{
}
/**
* Should return an array specification compatible with
* {#link Zend\InputFilter\Factory::createInputFilter()}.
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'name' => array(
'required' => true,
'validators' => array(
)
),
];
}
/**
* Set service locator
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->sl = $serviceLocator;
}
/**
* Get service locator
*
* #return ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->sl;
}
}
Here is the Musician Form
<?php
namespace Application\Form\Music;
use Application\Entity\Album;
use Zend\Form\Form;
use Zend\Form\Element\Collection;
use Zend\Stdlib\Hydrator\ClassMethods;
use Zend\Stdlib\Hydrator\ObjectProperty;
use Zend\Validator;
use Zend\Form\Element;
use Application\Form\Music\AlbumFieldset;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\InputFilter\InputFilterProviderInterface;
class MusicianForm extends Form implements InputFilterProviderInterface, ServiceLocatorAwareInterface
{
public function __construct()
{
parent::__construct('');
}
public function init()
{
}
public function setMusician($musician) {
$this->setHydrator(new ClassMethods());
$this->add(array(
'type' => 'Text',
'name' => 'name',
'options' => [
]
));
$this->buildFields();
$this->bind($musician);
}
public function buildFields() {
$fs = new AlbumFieldSet();
$fs->setHydrator(new ObjectProperty());
$fs->setObject(new Album());
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'albums',
'options' => array(
'label' => 'Form Values',
'count' => 2,
'allow_add' => false,
'allow_remove' => false,
'should_create_template' => false,
'target_element' => $fs,
'use_as_base_fieldset' => true,
),
));
}
/**
* Should return an array specification compatible with
* {#link Zend\InputFilter\Factory::createInputFilter()}.
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'name' => array(
'required' => true,
'validators' => array(
)
),
];
}
/**
* Set service locator
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->sl = $serviceLocator;
}
/**
* Get service locator
*
* #return ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->sl;
}
}
Controller Code:
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Entity\Musician as Musician;
use Application\Entity\Album as Album;
class MusiciansController extends AbstractActionController
{
public function createMusicianAction()
{
$musician = new Musician();
$albumOne = new Album();
$albumTwo = new Album();
$albumOne->setName('The White Album');
$albumTwo->setName('Sgt. Pepper');
$albumOne->setReleaseYear('1974');
$albumTwo->setReleaseYear('1967');
$albums = array(
$albumOne,
$albumTwo
);
$musician->setName('The Beatles');
$musician->setAlbums($albums);
$form = $this->getServiceLocator()->get('FormElementManager')->get('MusicianForm');
$form->setMusician($musician);
return new ViewModel([
]);
}
}
when I attempt to bind the form, I end up with the following error:
Zend\Form\Element\Collection::setObject expects an array or Traversable object argument; received "Application\Entity\Musician"
I attempted to implement iterator in the musician class, but the solution there seems to be complicated and isn't quite clear. How get this bind to work properly?
I figured it out!
The problem here was that Zend Framework requires all entities related to the form to have their own fieldset for bind to properly work!
In this specific example, I created a musician fieldset, set it as the base fieldset in the musician form, and created the album form collection within the musician fieldset. Voila! Everything populates quite nicely.