PHPSPEC Quick Start
Quick-start guide (by example) to phpspec.
Defining clear domain responsibilities, and speccing objects accordingly is a critical part of development that too many skip (...this actually prompted this post). If you're shaking your head at this point thinking: "Pfft. I get by." ...this article's for you, besides, specs are easy. Here's my two step process:
- Let go of all the complexities driven by your framework and think about the object (no service managers for example). You're not a Laravel programmer, you are a programmer...right?
- Let your specs emerge and define your object (this is why you write your test first, but, it's never too late to graft specs into your existing projects)
Preface 1: The Law of Demeter
If you've never heard of this, it's a good rule to live by when writing highly-testable code. Put simply, it says that you're only allowed to interact with your closest friends.
Its rigor would have that a method in a "Foo" object is only allowed to work with:
- Other methods within Foo
- Methods on Foo's properties ( $this->bar->doIt() )
- Methods on arguments that are in turn passed into your method
- Methods on new objects created within the called method
If you break these rules, your specs aren't likely to emerge into a proper architecture.
Preface 2: What is phpspec?
It bills itself on its homepage as:
A php toolset to drive emergent design by specification.
Which is true, yet I've found in exercises where I'm asked to roll in new libraries on old platforms (read: someone else's mess) that it could doubly be billed as:
Will quickly point out questionable architecture decisions.
Part of the magic lies in the thought process and its application of the LoD. Ever try to write a spec for an object that depends on ZF2's ServiceManager? You can't, but this is good. You shouldn't be using the SM to draw dependencies into your classes and its methods in the first place. Therein lies a third ad:
Will quickly shame you for not having used DI where you should.
Proceed.
The Guide
Here's where the blog post starts. This is the post I wish I'd read when I started off.
Installation
Assuming that you are using composer, modify your composer.json and include:
"require-dev": { "phpspec/phpspec": "@stable" }
That'll give you enough to have composer install phpspec for you. Go ahead and run composer update. Then, symlink the phpspec executable from your project root:
ln -s ./vendor/bin.phpspec ./phpspec
If you issue a ./phpspec after this, you should see success.
Success!
Configuration
You'll need to tell phpspec how to behave using its phpspec.yml (Yaml, yes, ouch) file. You essentially define 'suites' (like phpunit) whose keys contain namespace, spec prefix, where you store your specs and where the source resides. Create a phpspec.yml in your project root, and define it (here's mine, ZF2 project):
suites:
Application:
namespace: Application
spec_prefix: Spec
src_path: module/Application/src/
spec_path: module/Application/bundle
Now we're ready to go!
Create a Spec
Your first step is to describe a class. You can be at this step describing an existing class if you are retrofitting tests to existing code, doesn't matter, do:
./phpspec describe Application/Service/Foo
Notice the forward slashes instead of the usual namespace backslashes.
This yields:
Specification for Application\Service\Foo created in .../module/Application/bundle/Spec/Application/Service/FooSpec.php.
Here's the really cool part if your class doesn't yet exist.
./phpspec run Do you want me to create `Application\Service\Foo` for you? Y/n
Answer 'Y' and phpspec will automatically create the code for Foo for you. Nice!
Now, open up the spec file (FooSpec) it originally created under module/Application/bundle/Spec. Put that on your left screen in a horizontal split with Application\Service\Foo, and put this cheat-sheet on the right: https://github.com/yvoyer/phpspec-cheat-sheet
Quick-Start to "Examples"
Specs are actually fun to write. You can conjure your object's role without worrying about implementation (too much). Worry about spec!
Simple Example
Here's an example spec for a domain-lookup object that should return whether or not a domain is available:
class FooSpec extends ObjectBehavior
{
function it_checks_domain_availability()
{
$this->isAvailable('google.com')->shouldReturn(false);
}
}
Infer from that snippet:
- functions (called examples) start with it (or its)
- you are using '$this' as you would "Foo" despite being "FooSpec" (cool!)
- your Foo objects return something special that the cheat sheet is a big help with (Subjects)
Mocking Objects
It could be then, that the 'isAvailable' method on 'Foo' accepts a Domain object on which it calls 'getName()' rather than a domain name. Simply change your example:
use Application\Entity\Domain;
class FooSpec extends ObjectBehavior
{
function it_checks_domain_availability( Domain $domain )
{
$domain->getName()->willReturn('google.com');
$this->isAvailable($domain)->shouldReturn(false);
}
}
Here then:
- we mock a domain very simply adding a 'use' statement and using the class in the example's signature
- the mocked Domain (Prophecy object) needs to return 'google.com' on any invocation of getName(), so we use ->willReturn to specify what it'll give
Constructor Magic
Chances are that you need to use a Domain object that returns 'google.com' in many places, and, that your Foo service needs something like an EntityManager as constructor argument:
public function __construct( Doctrine\ORM\EntityManager $em ){ ... }
Let serves this purpose:
use Application\Entity\Domain;
use Doctrine\ORM\EntityManager;
class FooSpec extends ObjectBehavior
{
function let( Domain $domain, EntityManager $em )
{
$domain->getName()->willReturn('google.com');
$this->beConstructedWith( $em );
}
function it_checks_domain_availability( $domain )
{
$this->isAvailable($domain)->shouldReturn(false);
}
}
Some magic happens here:
- Simply by virtue of having the same variable name ($domain), the $domain configured in let will actually be the same that is used in it_checks_domain_availability
- We're satisfying a constructor requirement by passing in a mocked EntityManager to beConstructedWith which is a variadic function (accepts n parameters)
These last two are a particular surprise to folks who don't RTM, here is a reference.
Emerging Design
Here's the claim that's made on the phpspec website that you'll understand after a few scars. It will push you into better/good design; the indicator is that things don't feel right or "can't work without a Framework something or other". Consider for example something like this in Zend Framework 2:
namespace Application\Service;
class CarService implements ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;
function changeTires( Car $car )
{
$sm = $this->getServiceLocator();
$tire_manager = $sm->get( TireManager::class );
if( $tire_manager->hasTires( 4, $car->getTireType() )
{
$tires = $tire_manager->markSold( 4, $car->getTireType() );
$car->installTires( $tires );
}
}
}
Too often do we see code that relies on the SLA to invoke everything. If you try to write a spec to test this code you will run into errors. The SL is not available as though the ZF bootstrap had run.
This test would never work:
use Application\Entity\Car;
class CarServiceSpec extends ObjectBehavior
{
function it_changes_tires( Car $car )
{
$this->changeTires($domain)->shouldReturn(false);
}
}
To write a good example, you will have to inject the TireManager into your service -- which is what you should do to keep your model disparate from the framework. In most cases, you can achieve a setup where your model can be hot-swapped between frameworks even.
use Application\Entity\Car;
use Application\Service\TireManager;
class CarServiceSpec extends ObjectBehavior
{
function let( TireManager $m )
{
$m->hasTires( 4, 'Toyo Proxes' )->willReturn(true);
$m->markSold( 4, 'Toyo Proxes' )->willReturn('1234');
$this->beConstructedWith( $m );
}
function it_changes_tires( Car $car )
{
$car->getTireType()->willReturn( 'Toyo Proxes' );
$car->installTires( '1234' )->willReturn(true);
$this->changeTires($car)->shouldReturn(true);
}
}
A rough example, but it works. It follows that you should resort to using a Factory to instantiate your ZF2 Service (which is the right way to go).
So! Good luck in speccing your objects, if you have any questions or clarifications or fixes -- I do scan comments quickly!
Zend Framework Service Manager & PHPSPEC
Use phpspec to test your Zend Framework 2 code.
Most of us have been writing phpunit tests for awhile, and if you're like me, might've read what the 'other side of the fence' has been writing about phpspec. It had been introduced to me as a BDD test suite, but I thought of it as a bit of malarkey since I'd bought into Behat as the synonym to BDD. I decided to give it a go! I think I was sold the second I considered never having to write an XML suite ever again.
What sucked though, was the immediate need for the ServiceManager. There was no glue available for phpspec that I could find, just a bunch of slides about why phpspec or BDD are awesome.
I think I've got it.
Setup
First, install my little phpspec package for zf2:
composer require saeven/phpspec-zf2
Then, set up your phpspec.yml and write tests! I've got some examples of my phpspec.yml and a factory specification in the README which you can see here:
https://github.com/Saeven/phpspec-zf2
Let me know if it helps you, if you run into any snags, or have recommendations!
Where This Fits
For me, this would never replace Behat despite their competition for the BDD acronym in documentation; I also don't think it would do well to qualify things like controllers -- but it has been a godsend to test model and service objects.
I hope this plugin helps you too!
Slow Composer on OS X?
Is your composer running slowly? Give it a quick performance tune with HHVM. You'll be impressed.
If your composer install is painfully slow, then this might be the fix for you. I got to a point where it was taking a REALLY long time to run simple updates using PHP 5.6.15. Sure sure, PHP7 is around the corner, but I couldn't sacrifice my dev environment which reflects our deploy requirements.
First, Homebrew
If you don't have Homebrew on your machine, well, it's never too late to do the right thing. You can get more details at http://brew.sh. Installation is simple:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Then, HHVM
If you aren't familiar with HHVM, it's all here: http://hhvm.com. Essentially, it was a turbocharged PHP created by the folks at Facebook. They did excellent work to be honest, and the performance gains were staggering. Performance differences between PHP7 and HHVM were certainly the elePHPhant in the room.
Get HHVM installed with Homebrew like so:
brew tap hhvm/hhvm
brew install hhvm
That one will take awhile. Go Netflix'n'chill for a few and check it when you're "done".
Last, Composer Alias
This part is very simple. You just need to create an alias in your ~/.bash_profile file. If it doesn't exist, create it. From Terminal, issue this command:
pico ~/.bash_profile
Then, add this alias into it
alias composer="hhvm -v ResourceLimit.SocketDefaultTimeout=30 -v Http.SlowQueryThreshold=30000 composer.phar"
Hit CTRL+X, answer Yes to save, and then refresh your session with:
source ~/.bash_profile
That's it! You should be able to head into your composer project and issue a magical composer update that's 10 times faster than its ever been!
Let me know how things pan out!
Zend Framework 2 Forms, Factories, InputFilters and Hydration
Writing good forms is important. Setting them up can be a bottom-brick break, still want to get into the Kumite? Tool to boost your form-rigging kung fu inside.
TLDR? Get this little tool I rigged. Takes care of all this for you.
It's probably difficult to recall the "thing" that prompts us into crafting the best forms possible, or especially to neophytes, what a clean form might be. Personally, I'd seen a really clean example in a GitHub repo and then mentally compared it with what I'd coded...then...lightbulb! "Damn, that's how that's done!"
In a framework like ZF2 where there's a lot of boilerplate code at play, cleanliness is truly godliness. So can forms be beautiful? I think so.
I've struggled with forms and ZF2 for some time. In hindsight, I even documented my struggle along the way. I think I've had time to distill the outcome; and I'll propose a blanket approach in what follows. Its benefit is that if you need to do 'something', you'll know precisely where it needs to get done.
By adopting this, we on a handshake agree that: forms should be built by factories, that input filters should be separated from form construction logic, and that annotation builders are pure evil (had to squeeze that last one in!).
Some Code...
Consider a form that needs to store tax settings (TaxForm). The contract is:
- It has to be aware of country codes; good opportunity to show how to separate dependencies via Factory.
- I also use Doctrine, and need it to spit out a Tax object.
- When a country is selected, the 'state' select is populated.
- When a country is submitted (on Form post) I always want it to repopulate the "state" select.
Let's get started.
Factory and Form
Going stepwise, the basic skeleton for a factory is this:
namespace Application\Factory\Form;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class TaxFormFactory implements FactoryInterface{
public function createService(ServiceLocatorInterface $serviceLocator){
}
}
Passing config options to the factory is easy with `MutableCreationOptionsInterface`. We modify our skeleton slightly to accept 'options', so that it can receive 'country' information.
namespace Application\Factory\Form;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
class TaxFormFactory implements FactoryInterface, MutableCreationOptionsInterface{
protected $options;
public function setCreationOptions( array $options )
{
$this->options = $options;
}
public function createService(ServiceLocatorInterface $serviceLocator){
}
}
When we create the form through the service manager, we can now pass a second parameter that contains the country like so:
$form = $sm->get( 'FormElementManager' )->get(
TaxForm::class,
['country' => $this->params()->fromPost('country')]
);Finished Factory
namespace Application\Factory\Form;
use Application\Entity\Tax;
use Application\Form\TaxForm;
use Application\InputFilter\TaxInputFilter;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Phine\Country\Loader\Loader;
use Zend\ServiceManager\MutableCreationOptionsInterface;
class TaxFormFactory implements FactoryInterface, MutableCreationOptionsInterface{
protected $options;
public function setCreationOptions( array $options )
{
$this->options = $options;
}
public function createService(ServiceLocatorInterface $serviceLocator){
/**
* @var \Zend\Form\FormElementManager $serviceLocator
* @var \Zend\ServiceManager\ServiceManager $serviceManager
*/
$serviceManager = $serviceLocator->getServiceLocator();
try
{
$country_loader = new Loader();
$countries = $country_loader->loadCountries();
$countries = array_map( function( $a ){ return $a->getShortName(); }, $countries );
$states = [];
if( isset( $this->options['country']) )
{
$subdivisions = $country_loader->loadSubdivisions( $this->options['country'] );
foreach( $subdivisions as $s => $d )
{
list(, $code) = explode( '-', $s, 2 );
$states[$code] = $d->getName();
}
}
$form = new TaxForm( $countries, $states );
$form->setTranslator( $serviceManager->get('translator') );
$form->setHydrator( new DoctrineObject( $serviceManager->get('doctrine.entitymanager.orm_default'), false ) );
$form->setObject( new Tax() );
$form->setInputFilter( $serviceManager->get('InputFilterManager')->get( TaxInputFilter::class ) );
}
}
Reading through the code:
- I'm using Phine to figure out country subdivisions ("phine/country":"dev-master"), dealing with the whole country situation (building a $countries and $states array set in the process)
- I'm creating a new form (code for that form below)
- I'm setting a Doctrine hydrator for later extraction
- I'm setting an Input filter that I create separately (code also below)
Using ::class not only really cleans things up, you'll create fewer strings throughout, and it makes things hella easy to remember while you're coding when introspection kicks in.
Finished Form
I'll spare all the gory field details, only including country and state; important take-away is that the constructor and init, are separate. I needed the translator in here too, the TranslatorAwareTrait really comes in handy.
namespace Application\Form;
use Zend\Form\Element;
use Zend\Captcha;
use Zend\InputFilter;
use Zend\Form\Element\Text;
use Zend\Form\Form;
use Zend\Form\Element\Select;
use Zend\Form\Element\Radio;
use Zend\Form\Element\Hidden;
use Zend\I18n\Translator\TranslatorAwareTrait;
class TaxForm extends Form
{
use TranslatorAwareTrait;
private $countries;
private $states;
public function __construct( $countries = [], $states = [] )
{
$this->countries = $countries;
$this->states = $states;
parent::__construct('taxes', []);
}
public function init()
{
$label_col = 'col-sm-3';
$field_col = 'sm-9';
$this->add([
'name' => 'country',
'type' => Select::class,
'options' => [
'label' => _( "Country" ),
'value_options' => array_merge( [$this->translator->translate( 'All' )], $this->countries ),
'column-size' => $field_col,
'label_attributes' => ['class' => $label_col],
],
'attributes' => [
'maxlength' => 2,
],
]);
$this->add([
'name' => 'state',
'type' => Select::class,
'options' => [
'label' => _( "State" ),
'value_options' => array_merge( [$this->translator->translate( 'All' )], $this->states ),
'column-size' => $field_col,
'label_attributes' => ['class' => $label_col],
],
'attributes' => [
'maxlength' => 2,
],
]);
}
}
An aside, the column size stuff that's in there, is there to support a great Twitter Bootstrap form project I like to use.
That's it - the form is done! Mosey on over to your module config, and add this to your use statements, and form_elements configuration:
use Application\Form\TaxForm;
use Application\Factory\Form\TaxFormFactory;
return [
//...
'form_elements' => [
'factories' => [
TaxForm::class => TaxFormFactory::class,
],
],
//...
];
So, barring our completion of the InputFactory and Doctrine Entity, the "Form" side of things is done. Let's take a look at InputFilters, which are what you should be configuring to validate your forms.
Don't do getInputFilter right inside the form, you'll be thankful when you need your validator down the road (e.g., zf-content-validation, or Apigility). They're good outside of forms! You'll see!
InputFilter and Factory
Input filters are specifications for filtering and validating. Remember that filters run before validators - that'd been a source of hair loss for me when I was starting out.
The factory's need is arguable if your filter only uses baked in filters/validators. I have admittedly skipped factories time and again. Quite often though you'll need to do some Entity-based validation, for example, make sure that another Tax with a same name doesn't exist in the database (or another user with the same email, etc.). I'm using Doctrine, so I need the Factory specifically to inject the Entity Manager's Tax repository into my actual InputFilter. So:
namespace Application\Factory\InputFilter\TaxInputFilterFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Entity\Tax;
use Application\InputFilter\TaxInputFilter;
class TaxInputFilterFactory implements FactoryInterface
{
public function createService( ServiceLocatorInterface $serviceLocator )
{
$repo = $serviceLocator->getServiceLocator()
->get('Doctrine\ORM\EntityManager')
->getRepository( Tax::class );
return new TaxInputFilter( $repo );
}
}
Very basic, but the one big gotcha to note is that when you run an input filter factory, the service locator that you get is not the main service locator. I needed to run ->getServiceLocator() on the passed $serviceLocator to get the main SL (which can then, get me my repository).
The TaxInputFilter is equally simple:
use Zend\Filter\StringTrim;
use Zend\InputFilter\InputFilter;
use Zend\Form\Element;
use CirclicalUser\Form\Filter\ArrayBlock;
use HTMLPurifier;
use Zend\Validator\Hostname;
class TaxInputFilter extends InputFilter
{
private $repository;
public function __construct
public function init()
{
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => ArrayBlock::class],
['name' => StringTrim::class],
['name' => HTMLPurifier::class],
],
]);
$this->add([
'name' => 'identification',
'required' => true,
'filters' => [
['name' => ArrayBlock::class],
['name' => StringTrim::class],
['name' => HTMLPurifier::class],
],
]);
//...
}
}
So, to tie that into your config, add these lines to your module.config.php:
use Application\Factory\InputFilter\TaxInputFilterFactory;
use Application\InputFilter\TaxInputFilter;
'input_filters' => [
'factories' => [
TaxInputFilter::class =>TaxInputFilterFactory::class,
],
],
Now! Here's where you and I probably reach the same conclusion:
Damn that's a lot of boilerplate!
I agree -- maybe I can convince you to consider this tool that does all the work for you?
Making It Work (..and Hydration)
We're not quite done, for all of this to work you have to invoke your Form properly.
Bad:
$form = $sm->get( TaxForm::class, $opts );
Good:
$form = $sm->get( 'FormElementManager' )->get( TaxForm::class, $opts );
I've also taken a lazy approach, and have developed a utility class that lets me process any such form with a mapper.
namespace Application\Service;
use Application\Mapper\AbstractDoctrineMapper;
use Zend\Form\Form;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
class FormService implements ServiceLocatorAwareInterface {
use ServiceLocatorAwareTrait;
/**
* Utility function for all those cases where we process forms to store entities within their respective mappers.
* Psst. This happens often.
*
* @param $form
* @param $mapper
* @param $response
* @return null
*/
public function processFormWithMapper( Form $form, $data, AbstractDoctrineMapper $mapper, Array &$response )
{
$object = null;
$object = null;
try
{
/** @var Form $data */
$object = $form->getObject();
$form->setData( $data );
if( !empty($data['id']) )
if( $object = $mapper->getRepository()->findOneBy(['id' => $data['id']]) )
$form->bind( $object );
if( $form->isValid() )
{
if( $object->getId() )
$mapper->update( $form->getObject() );
else
$mapper->save( $form->getObject() );
$response['success'] = true;
$response['message'] = "The configuration was successfully saved!";
}
else
{
$response['message'] = "Hm. That didn't work - check the errors on the form.";
$response['form_errors'] = $form->getMessages();
}
}
catch( \Exception $x )
{
$response['messsage'] = $x->getMessage();
}
return $object;
}
}
My usage pattern ends up looking like this:
public function saveAction()
{
$response = ['success' => false];
$sm = $this->getServiceLocator();
$post = $this->params()->fromPost();
if( $this->getRequest()->isPost() )
{
$opts = null;
if( $country = $this->params()->fromPost( 'country' ) )
$opts = [ 'country' => $country ];
$form = $sm->get( 'FormElementManager' )->get( TaxForm::class, $opts );
/**
* @var FormService $form_service
* @var TaxMapper $tax_mapper
*/
$tax_mapper = $sm->get( TaxMapper::class );
$form_service = $sm->get( FormService::class );
$tax_object = $form_service->processFormWithMapper( $form, $post, $tax_mapper, $response );
$response['success'] = $tax_object != null;
$response['rows'] = $tax_mapper->getList();
}
return new JsonModel( $response );
}
Thanks for reading! Drop me a note if you find any fixes or have recommendations for the form tool!
Form-Fu With Zend Framework 2
An entry on the path to stronger Forms. Got Fu?
Forms aren't particularly hard at all. They're important, but we don't truly like making them (coder parents in the audience that pack lunches for their kids can empathize on two planes here). I actually set out today to check out the ZF2 Annotations (and Annotation Builder) and got deflated pretty quickly. Adding a DbRecordNoExists Validator to your email field with Annotations? I stumbled a few times, and hit the #zftalk chan on Freenode and asked:
Is there a clean way to specify the database to be used in a no exists validator using Annotations?
<The usual IRC strange-moment-of-silence passes>
"Using annotations? Are you crazy?"
Revisiting the Usual Approach
Alright, shit - I guess I should try to clean up "the usual". That thought was quickly put on hold as an IRC member (whose name eludes me, but whose blog I still have tabbed) said "Here I do forms like this! Rig this once, and the forms are easy thereafter".
If you check the source at that link, there's an interesting nugget in there, which is reusing a single config syntax to define both validator and element. Pretty low hanging fruit, but quite good!
Then, grizzm0 (a channel regular) posts his take on forms. What struck me is the cleanliness offered by the PHP 5.4+ square-bracket array instantiation. Every ZF2 resource you'll find probably uses array(), and where code is art, [] is a bit more of a masterpiece.
Form-Fu
Combining the blog and gist above, I quickly whittled it down to to a class, EasyForm, that your other forms can extend. You could probably shave out the two managers, but if you use htmlpurifier (you should!) you'll want them there.
namespace CirclicalUser\Form;
use Zend\Filter\FilterChain;
use Zend\Form\Form,
Zend\Form\Element,
Zend\Captcha,
Zend\InputFilter,
Zend\Validator\ValidatorChain;
class EasyForm extends Form implements InputFilter\InputFilterProviderInterface
{
private $raw_elements = [];
public function __construct( $name, $filter_manager, $validator_manager )
{
parent::__construct( $name );
$chain = new FilterChain();
$chain->setPluginManager($filter_manager);
$this->getFormFactory()
->getInputFilterFactory()
->setDefaultFilterChain( $chain );
$chain = new ValidatorChain();
$chain->setPluginManager( $validator_manager );
$this->getFormFactory()
->getInputFilterFactory()
->setDefaultValidatorChain( $chain );
$this->setAttribute('novalidate', '' );
}
public function add( $elementOrFieldset, array $flags = array() )
{
if( is_array( $elementOrFieldset ) )
$this->raw_elements[] = $elementOrFieldset;
return parent::add( $elementOrFieldset, $flags );
}
/**
* Should return an array specification compatible with
* {@link Zend\InputFilter\Factory::createInputFilter()}.
*
* @return array
*/
public function getInputFilterSpecification()
{
$filters = [];
foreach( $this->raw_elements as $e )
{
if( isset( $e['type'] ) )
unset( $e['type'] );
$filters[] = $e;
}
return $filters;
}
}
This form class overrides "add" to store a copy of your config on the way in (if it was an array, since you can also pass Elements to add when creating forms).
Usage is pretty simple, sample form getup:
namespace CirclicalUser\Form;
use Zend\Form\Element,
Zend\Captcha,
Zend\InputFilter,
Zend\Validator\Db\AbstractDb;
class User extends EasyForm implements InputFilter\InputFilterProviderInterface
{
private $slave_table;
/**
* Construct a registration form, with an AuthenticationFormInterface instance to establish minimum field count
*/
public function __construct( $filter_manager, $validator_manager, $slave_table, $config = array() )
{
parent::__construct('registration', $filter_manager, $validator_manager );
$this->slave_table = $slave_table;
$this->add([
'name' => 'email',
'type' => 'email',
'required' => true,
'options' => [
'label' => 'Email',
],
'attributes' => [
'class' => 'form-control',
],
'validators' => [
[
'name' => 'dbnorecordexists',
'options' => [
'table' => 'users',
'field' => 'email',
'adapter' => $this->slave_table,
'messages' => [ AbstractDb::ERROR_RECORD_FOUND => _( "That email is already taken, please log in instead" ), ],
],
],
[
'name' => 'emailaddress',
'options' => [
'useMxCheck' => true,
'useDeepMxCheck' => true,
'useDomainCheck' => true,
'message' => _( "That email address has a typo in it, or its domain can't be checked." ),
],
],
[
'name' => '\CirclicalUser\Form\Validator\SafeEmailAddress',
],
],
'filters' => [
['name' => 'arrayblock'],
['name' => 'stringtrim'],
['name' => 'htmlpurifier'],
['name' => 'stringtolower'],
]
]);
$honeypot = new Element\Hidden( 'axis' );
$this->add( $honeypot );
}
}
I build that form using a Factory, that's quite simply rigged like so:
//...
'user_form' => function( $sm ){
return new User(
$sm->get('FilterManager'),
$sm->get('ValidatorManager'),
$sm->get('slave_database')
);
},
//...
I like it!
If you however, have stronger Kung Fu -- please share (and thanks!).