I used Zend Framework 2.1(not 2.0x) method to populate a select/drop down that is described in following links:
http://zf2.readthedocs.org/en/develop/modules/zend.form.advanced-use-of-forms.html#handling-dependencies
http://www.michaelgallego.fr/blog/2012/11/09/discover-whats-coming-for-zendform-in-zf-2-1/
Though it seems I have done as they told I got a error message like:
*... ::__construct() must be an instance of Zend\Db\TableGateway\TableGateway, none given, called in ...*
which seems service locator is not used properly.
My form code that adds my FieldSet SupplierFieldset:
namespace Inventory\Form;
use Zend\Form\Form;
use Inventory\Model;
class ItemForm extends Form
{
public function init()
{
$this->add(array(
'name' => 'sup_code',
'type' => 'Inventory\Form\SupplierFieldset'
));
}
}
My 'SupplierFieldset' class:
namespace Inventory\Form;
use Inventory\Model;
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\InputFilter\InputFilterProviderInterface;
use Inventory\Model\SupplierTable;
use Inventory\Model\Supplier;
class SupplierFieldset extends Fieldset implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
protected $supplierTable;
public function init()
{
parent::__construct('Suppliers Code');
$this->setLabel('Supplier Code');
$this->setName('supplier_code');
$suppliers = $this->getSupplierTable()->fetchAll();
$select = new Element\Select('supplier_code');
$options = array();
foreach ($suppliers as $supplier) {
$options[$supplier->id] = $supplier->sup_code;
}
$select->setValueOptions($options);
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function getSupplierTable()
{
if (!$this->supplierTable) {
$sm = $this->getServiceLocator();
$this->supplierTable = $sm->get('Inventory\Model\SupplierTable');
}
return $this->supplierTable;
}
}
My Module.php getFormElementConfig() function:
public function getFormElementConfig()
{
return array(
'factories' => array(
'SupplierFieldset' => function($sm) {
$serviceLocator = $sm->getServiceLocator();
$supplierTable = $serviceLocator->get('Inventory\Model\SupplierTable');
$fieldset = new SupplierFieldset($supplierTable);
return $fieldset;
}
)
);
}
My SupplierTable.php model:
namespace Inventory\Model;
use Zend\Db\TableGateway\TableGateway;
class SupplierTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
}
I know SupplierTable model's constructor needs a TableGateway $tableGateway parameter. But this model is working properly when called from SupplierController.
Related
I got problem with the Abstract Factories example.
I get ServiceNotFoundException while creating a class with Abstract Factory registered in the ServiceManager.
First I download zend-servicemanager with composer
composer require zendframework/zend-servicemanager
Then I run the ServiceManager example in the single file (for simplicity).
<?php
require_once 'vendor/autoload.php';
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\Factory\InvokableFactory;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
Class that should be obtained with ServiceManager.
class A
{
public $text;
public function __construct()
{
$this->text = "Default text";
}
}
I use MyAbstractFactory from documentation.
class MyAbstractFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
return in_array('Traversable', class_implements($requestedName), true);
}
public function __invoke(ContainerInterface $container,
$requestedName,
array $options = null)
{
return $requestedName();
}
}
I create ServiceManager with registered Abstract Factory.
$serviceManager = new ServiceManager([
// Neither works
//'abstract_factories' => array('MyAbstractFactory')
'abstract_factories' => array( new MyAbstractFactory() )
//'abstract_factories' => array( MyAbstractFactory::class )
/*
'abstract_factories' => [
MyAbstractFactory::class => new MyAbstractFactory()
]
*/
]);
Finally I try to obtain the instance of class A.
$a = $serviceManager->get(A::class);
var_dump($a);
I get
Fatal error: Uncaught Zend\ServiceManager\Exception\ServiceNotFoundException: Unable to resolve service "A" to a factory; are you certain you provided it during configuration?
with the stack trace
.\vendor\zendframework\zend-servicemanager\src\ServiceManager.php(763): Zend\ServiceManager\ServiceManager->getFactory('A') #1
.\vendor\zendframework\zend-servicemanager\src\ServiceManager.php(200): Zend\ServiceManager\ServiceManager->doCreate('A') #2
.\script.php(53): Zend\ServiceManager\ServiceManager->get('A') #3
My formulation of the problem was wrong. #rkeet's comment made it clear.
As my objective was the working example of the Abstract Factory registration within ServiceManager, I post the single-file script where canCreate() is simplified just to check if the class exists.
<?php
// composer require zendframework/zend-servicemanager
require_once 'vendor/autoload.php';
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
class A
{
public $text;
public function __construct()
{
$this->text = "Default text";
}
}
class MyAbstractFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
return class_exists($requestedName);
}
public function __invoke(ContainerInterface $container,
$requestedName,
array $options = null)
{
return new $requestedName();
}
}
$serviceManager = new ServiceManager([
//'abstract_factories' => array('MyAbstractFactory') // works
//'abstract_factories' => array( new MyAbstractFactory() ) // works
'abstract_factories' => array( MyAbstractFactory::class ) // also works
]);
try {
$a = $serviceManager->get(A::class);
var_dump($a);
echo "<br/>\n";
$b = $serviceManager->get(B::class);
var_dump($b);
} catch (Exception $e) {
echo $e->getMessage() . "<br/>\n";
echo "{$e->getFile()} ({$e->getLine()})";
}
?>
As #rkeet pointed, if someone needs the original example working, class A should implement Traversable.
When adding an entry through the rest api how to change a variable before save values?
Below is a part of the controller code:
class RestusersController extends ActiveController
{
public $modelClass = 'app\models\User';
public function actions()
{
$actions = parent::actions();
$actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
return $actions;
}
public function prepareDataProvider()
{
return new ActiveDataProvider([
'query' => User::find()->where(['status_id'=>'1']),
'pagination' => false,
]);
}
}
eg change the variable $ this-> status_id = 1;
public function beforeSave($insert)
{
$this->status_id = 1;
return parent::beforeSave($insert);
}
Here's what I have so far,
Form:
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as Hydrator;
class UserRegisterForm extends
Form implements InputFilterProviderInterface
{
public function __construct($entityManager)
{
parent::__construct('Practitioner');
$this
->setAttribute('method', 'post')
->setHydrator(new Hydrator($entityManager), 'App\Entity\Users', true)
->setObject(new Users())
;
// add fields here ...
}
}
Controller:
class UsersController extends AbstractActionController {
public function addAction()
{
$request = $this->getRequest();
$form = new PractitionerRegisterForm($this->getEntityManager());
$users = new \App\Entity\Users;
$form->bind($users);
if ( $request->isPost() ) {
$form->setData($request->getPost());
// echo pre var_dump($this) /pre
$users->getDetails();
} else {
// do something here
}
return ['form' => $form];
}
but what I'm getting from $users->getDetails() is attributes with NULL
So how do I use Hydrator to populate the entity Users?
Solution: I had to validate the form by using $form->isValid() which validates then hydrates(populates) the entity.
I know this question is already answered here. But this doesnt work for me.
The Form is generated by using the PluginLoader:
$formClass = Zend_Registry::get('formloader')->load('Payment');
$form = new $formClass(array('someval' => $my_arr));
Payment.php:
class Form_Payment extends Zend_Form
{
protected $_someval = array();
public function init()
{
$this->setAction('payment/save');
//....
$this->addElement('multiCheckbox', 'store_id', array('label' => 'Someval:', 'required' => true, 'multiOptions' => $this->getSomeval()))
}
public function setSomeval($someval) {
$this->_someval = $someval;
}
public function getSomeval() {
return $this->_someval;
}
}
As I can see the load method only returns the class name, so new $formClass(); is equal new Form_Payment() but why this isn't accept params?
Ok I found a way by myself. I was looking for a way to inject some params while my Zend_Form was initialised. It seems the only way for this is to pass the params to the constructor - which is executed before the init method.
class Form_Payment extends Zend_Form
{
private $_someval;
public function __construct(array $params = array())
{
$this->_someval = $params['someval'];
parent::__construct();
}
public function init()
{
$this->setAction('payment/save');
//....
$this->addElement('multiCheckbox', 'store_id',
array('label' => 'Someval:',
'required' => true,
'multiOptions' => $this->_someval // passed params now available
))
}
}
You can add custom function to your form class like
class Form_Payment extends Zend_Form
{
public function init()
{
$this->setAction('payment/save');
// and so on
}
public function doSome()
{
$this->setAction('other/action');
}
}
and call it after instanciating form in controller
$form = new $formClass();
$form->doSome();
I've created a small Symfony2-Website (with Symfony PR11) with MongoDB as DB.
I could create a form, that use a normal document, but how do I use a document with an embeddedDocument?
here are the documents:
/**
* #mongodb:Document(collection="location")
*/
class Location
{
/**
* #mongodb:Id
*/
protected $id;
/**
* #mongodb:String
*/
protected $locationName;
/**
* #mongodb:EmbedMany(targetDocument="LocationTerminal")
*/
protected $terminals = array();
// Setter
public function setTerminals(LocationTerminal $terminal)
{
array_push($this->terminals, $terminal);
}
public function setLocationName($locationName)
{
$this->locationName = $locationName;
}
// Getter
public function getId()
{
return $this->$id;
}
public function getLocationName()
{
return $this->locationName;
}
public function getTerminals()
{
return $this->terminals;
}
}
The EmbeddedDocument:
/**
* #mongodb:EmbeddedDocument
*/
class LocationTerminal
{
/**
* #mongodb:String
*/
protected $terminalName;
/**
* #mongodb:Int
*/
protected $since;
/**
* #mongodb:Int
*/
protected $to;
// Setter
public function setTerminalName($terminalName)
{
$this->terminalName = $terminalName;
}
public function setSince($since)
{
$this->since = $since;
}
public function setTo($to)
{
$this->to = $to;
}
// Getter
public function getTerminalName()
{
return $this->terminalName;
}
public function getSince()
{
return $this->since;
}
public function getTo()
{
return $this->to;
}
}
As you can see $terminals holds an EmbedMany-Document
Here's the form:
class LocationForm extends Form
{
public function configure()
{
$this->add(new TextField('locationName', array('max_length' => 255, 'required' => true)));
}
public function addTerminals($dm)
{
$this->add(new ChoiceField('terminals.terminalName', array('choices' => $dm)));
$this->add(new DateField('terminals.since', array('required' => true)));
$this->add(new DateField('terminals.to', array('required' => false)));
}
}
The used Controller looks like this:
class LocationController extends Controller
{
protected $location;
protected $locationTerminal;
protected function getDm()
{
return $this->get('doctrine.odm.mongodb.document_manager');
}
protected function getLocation($name = null)
{
if ($name != null)
{
$dm = $this->getDm();
$this->location = $dm->getRepository('RalfBundle:Location')->findOneBy(array('locationName' => $name));
if (! $this->location)
{
$this->location = new Location();
$this->locationTerminal = new LocationTerminal();
$this->location->setLocation($name);
$this->location->setTerminals($this->locationTerminal);
}
}
else
{
$this->location = new Location();
$this->locationTerminal = new LocationTerminal();
$this->location->setTerminals($this->locationTerminal);
$this->locationTerminal->setSince(0);
$this->locationTerminal->setTerminalName("");
$this->locationTerminal->setTo(0);
}
}
protected function getForm()
{
$form = LocationForm::create($this->get('form.context'), 'location');
$dm = $this->getDm();
$form->addTerminals($dm->getRepository('RalfBundle:Terminal')->findAll()->toArray());
return $form;
}
//... some Actions
public function createAction()
{
$this->getLocation();
$form = $this->getForm();
$form->bind($this->get('request'), $this->location);
if ($form->isValid())
{
$dm = $this->getDm();
$dm->persist($this->location);
$dm->flush();
return $this->redirect($this->generateUrl('Location'));
}
return $this->render('RalfBundle:Ralf:location_create.html.twig', array('form' => $form));
}
I could see, that locationName recieve the entered values in the form, but the EmbedMany-Array terminals is still empty.
What did I wrong?
Thanks for helping :D
UPDATED:
Ok, found a solution.
in public function addTerminals($dm) in LocationForm it should look like this:
public function addTerminals($dm)
{
$this->add(new ChoiceField('terminals.0.terminalName', array('choices' => $dm)));
$this->add(new DateField('terminals.0.since', array('required' => true, 'type'=> 'timestamp')));
$this->add(new DateField('terminals.0.to', array('required' => false, 'type' => 'timestamp')));
}
'type' => 'timestamp' is necessary, 'cause DateField will create an DateTime-Object, but the document expected an Int for timestamp.
a field from the terminals-array could be accessed by normal dot-notation.
Ok, found a solution.
in public function addTerminals($dm) in LocationForm it should look like this:
public function addTerminals($dm)
{
$this->add(new ChoiceField('terminals.0.terminalName', array('choices' => $dm)));
$this->add(new DateField('terminals.0.since', array('required' => true, 'type'=> 'timestamp')));
$this->add(new DateField('terminals.0.to', array('required' => false, 'type' => 'timestamp')));
}
'type' => 'timestamp' is necessary, 'cause DateField will create an DateTime-Object, but the document expected an Int for timestamp.
a field from the terminals-array could be accessed by normal dot-notation.
Symfony 2 actually gives you a tool to handle embedded documents in forms: It's called "collection field type" and it enables you to embed other formtypes (from other embedded documents) in a parent form.
It can be configured to allow/forbid addition/deletion of embedded documents and is actually quite powerful.