Skip to content

Instantly share code, notes, and snippets.

@marcguyer
Last active May 1, 2019 12:28
Show Gist options
  • Save marcguyer/02371bcb00bba22346dd52e309ad1450 to your computer and use it in GitHub Desktop.
Save marcguyer/02371bcb00bba22346dd52e309ad1450 to your computer and use it in GitHub Desktop.
Zend Framework Input Validation with Dependencies
<?php
declare(strict_types=1);
namespace ApiTest\InputFilter;
use PHPUnit\Framework\TestCase;
use Zend\InputFilter\InputFilter;
/**
* {@inheritdoc}
*/
abstract class AbstractInputFilterTest extends TestCase
{
/**
* Data provider for testValidation method
* @return array
*/
abstract public function validationDataProvider(): array;
/**
* Get the subject input filter
*
* @return InputFilter
*/
abstract public function getInputFilter(): InputFilter;
/**
* @dataProvider validationDataProvider
* Covers all public methods of @coversDefaultClass set in extension
* @covers ::<public>
*
* @param array $data
* @param bool $expectedIsValid
* @param array $expectedMessages
*/
public function testValidation(
array $data,
bool $expectedIsValid = true,
array $expectedMessages = []
): void {
$inputFilter = $this->getInputFilter();
$inputFilter->init();
$inputFilter->setData($data);
$this->assertSame(
$expectedIsValid,
$inputFilter->isValid(),
'Messages: ' . json_encode($inputFilter->getMessages()) .
' Values: ' . json_encode($inputFilter->getValues())
);
if (!$expectedIsValid) {
$this->assertValidationMessages(
$inputFilter->getMessages(),
$expectedMessages,
$inputFilter->getValues()
);
}
}
/**
* @param array $messages
* @param array $expectedMessages
* @param array $values
*/
private function assertValidationMessages(
array $messages,
array $expectedMessages,
array $values
): void {
foreach ($expectedMessages as $param => $messageKey) {
$this->assertArrayHasKey(
$param,
$messages,
'Messages: ' . json_encode($messages) .
' Values: ' . json_encode($values)
);
if (is_array($messageKey)) {
$this->assertValidationMessages(
$messageKey,
$messagers[$param]
);
continue;
}
$this->assertArrayHasKey(
$messageKey,
$messages[$param],
'Messages: ' . json_encode($messages) .
' Values: ' . json_encode($values)
);
}
}
}
<?php
declare(strict_types=1);
namespace Api\InputFilter;
use Api\Validator\CustomerUniqueCodeValidator;
use Zend\InputFilter\InputFilter;
use Zend\Filter\StringTrim;
use Zend\Validator\EmailAddress;
use Zend\Validator\StringLength;
use Utility\Interfaces\ProductAwareInterface;
use Utility\Traits\ProductAwareTrait;
use Data\Entity\Product;
/**
* Input filter and validator for customer.
*/
class CustomerInputFilter extends InputFilter implements ProductAwareInterface
{
use ProductAwareTrait;
/**
* Set Product, then add that product to any dependent input validators.
*
* @param Product $product
*
* @return mixed
*/
public function setProduct(Product $product)
{
$this->product = $product;
foreach ($this->getInputs() as $i) {
foreach ($i->getValidatorChain()->getValidators() as $v) {
if ($v['instance'] instanceof ProductAwareInterface) {
$v['instance']->setProduct($product);
}
}
foreach ($i->getFilterChain()->getFilters() as $f) {
if ($f instanceof ProductAwareInterface) {
$f->setProduct($product);
}
}
}
return $this;
}
/**
* {@inheritdoc}
*/
public function init()
{
$this->add([
'name' => 'code',
'required' => true,
'filters' => [['name' => StringTrim::class]],
'validators' => [
[
'name' => StringLength::class, 'options' => ['max' => 255],
], [
'name' => CustomerUniqueCodeValidator::class,
],
],
]);
}
}
<?php
declare(strict_types=1);
namespace ApiTest\InputFilter;
use Api\InputFilter\CustomerInputFilter;
use Data\Entity;
use Zend\InputFilter\InputFilter;
/**
* {@inheritdoc}
*
* @coversDefaultClass \Api\InputFilter\CustomerInputFilter
* @uses Api\Validator\CustomerUniqueCodeValidator
*/
class CustomerInputFilterTest extends AbstractInputFilterTest
{
/**
* @inheritDoc
*/
public function validationDataProvider(): array
{
return [
[[], false, ['code' => 'isEmpty'],],
[['code' => 'something']],
];
}
public function getInputFilter(): InputFilter {
$inputFilter = new CustomerInputFilter();
$product = $this->prophesize(Entity\Product::class);
$product->getUnderscoreId()->willReturn(123);
$inputFilter->setProduct($product->reveal());
return $inputFilter;
}
}
<?php
declare(strict_types=1);
namespace Api\Validator;
use Data\Repository\CustomerRepository;
use Zend\Validator\AbstractValidator;
use Utility\Interfaces\ProductAwareInterface;
use Utility\Traits\ProductAwareTrait;
/**
* Validator to enforce unique code within a product.
*/
class CustomerUniqueCodeValidator extends AbstractValidator implements ProductAwareInterface
{
use ProductAwareTrait;
/**
* @var string
*/
const NOT_UNIQUE = 'notUnique';
/**
* @var string
*/
const MISSING_PRODUCT = 'missingProduct';
/**
* @var array
*/
protected $messageTemplates = [
self::NOT_UNIQUE => 'A customer already exists with code=%value%',
self::MISSING_PRODUCT => 'Missing product',
];
/**
* @var CustomerRepository
*/
private $customerRepository;
public function __construct(CustomerRepository $customerRepository)
{
parent::__construct();
$this->customerRepository = $customerRepository;
}
public function isValid($value)
{
$this->setValue($value);
if (null === $this->getProduct()) {
$this->error(self::MISSING_PRODUCT);
return false;
}
$customer = $this->customerRepository
->findOneByCode($value, $this->getProduct());
if (null === $customer) {
return true;
}
$this->error(self::NOT_UNIQUE);
return false;
}
}
<?php
declare(strict_types=1);
namespace Api\Validator\Factory;
use Data\Entity\Customer;
use Api\Validator\CustomerUniqueCodeValidator;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Container\ContainerInterface;
/**
* CustomerUniqueCodeValidatorFactory.
*/
class CustomerUniqueCodeValidatorFactory
{
/**
* @param ContainerInterface $container
*
* @return CustomerUniqueCodeValidator
*/
public function __invoke(ContainerInterface $container): CustomerUniqueCodeValidator
{
return new CustomerUniqueCodeValidator(
$container->get(EntityManagerInterface::class)
->getRepository(Customer::class)
);
}
}
<?php
declare(strict_types=1);
namespace Utility\Interfaces;
use Data\Entity\Product;
/**
* ProductAwareInterface
*/
interface ProductAwareInterface
{
/**
* Set input filter
*
* @param Product $product
* @return ProductAwareInterface
*/
public function setProduct(Product $product);
/**
* Retrieve input filter
*
* @return Product
*/
public function getProduct();
}
<?php
declare(strict_types=1);
namespace Utility\Traits;
use Data\Entity\Product;
/**
* ProductAwareTrait
*/
trait ProductAwareTrait
{
/**
* @var Product
*/
private $product = null;
/**
* Set Product
*
* @param Product $product
* @return mixed
*/
public function setProduct(Product $product)
{
$this->product = $product;
return $this;
}
/**
* Retrieve product
*
* @return Product
*/
public function getProduct()
{
return $this->product;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment