@froschdesign
Hi Frank. thanks for your reply! Can you give me some demo?
I think your problem is not creating a factory, just registering that factory:
Example: (e.g. config/autoload/global.php
)
// …
'validators' => [
'factories' => [
Zend\Validator\Db\NoRecordExists::class => DbValidatorAbstractFactory::class,
Zend\Validator\Db\RecordExists::class => DbValidatorAbstractFactory::class,
],
],
// …
@froschdesign
where it can show which db config it used when i configurated several databases?
sometimes we config db1, db2…etc
This is a new requirement that nobody knows except you. Why is this important information not included in your first post / question?
My suggestion won’t work here!
Extract the input-filter and create a factory for it which sets the required database adapter.
sorry for not mentioned the several db configurations at global.php first. infact, the database i need to use for the recordexist validator is not the default one.
can you give a little more demo codes? i have no idea on the extract of the inputfilter you said above
Nothing special:
CustomerInputFilter extend Zend\InputFilter\InputFilter {}
But you can also register a new element and create a factory:
'validators' => [
'factories' => [
My\Validator\CustomerRecordExists::class => My\Validator\CustomerRecordExistsFactory::class,
],
],
The class CustomerRecordExists
must not exists and in the factory you use the standard RecordExists
class and set the database adapter. Now you can use it in you input-filter configuration.
(This suggestion is based an the fact that your customers always in the same database.)
it is not possible that. all the valid logic use the same database. any other suggestions?
I think I need to write several custom input validates. it is not bad.
thank you so much for your time!
Hi Frank,
This “DbValidatorAbstractFactory” you said is a custom validator I write seperately?
It is called entity I think, I remember I saw this defination somewhere. It implements a inputfilter, I am a little confused by your following method.
I need to create a CustomerInputFilter instead the SomeEntity?
Why I need to create a CustomerInputFilter? I always thought I need to create a CustomerInputValidator…
Forget about the several databases configuration in global.php, just make the RecordExist validator for only one databse. If so, I need to create a CustomerRecordExistValidator class to extend AbstractValidator ?
-
Make sure you have there components. And add modules to
modules.config.php
zendframework/zend-db
zendframework/zend-inputfilter
zendframework/zend-filter
zendframework/zend-validator
-
Create a
DbValidatorAbstractFactory
class. You can direct copy from zfcampus/zf-content-validation > RecordExistsFactory. -
Add validator factory config
Zend\Validator\Db\RecordExists::class => DbValidatorAbstractFactory::class
in your module’smodule.config.php
.
It will overrides the default configuration. -
If your inputfilter is uncomplicated. Recommend you create inputfilter by
InputFilterAbstractServiceFactory
. You just config it. -
Create a controller factory and inject your inputfilter.
There’s example:
File SomeModule/config/module.config.php
code:
use Zend\Validator\Db\RecordExists;
return [
// "db" services factory config.
'db' => [
'adapters' => [
'db.foo' => [
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=admin;host=localhost;charset=utf8',
],
'db.bar' => [
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=admin;host=localhost;charset=utf8',
],
]
],
// `RecordExists:class` validator factory config.
'validators' => [
'factories' => [
//It will overrides the default configuration.
// Default is `RecordExists::class => InvokableFactory::class`
RecordExists::class => DbValidatorAbstractFactory::class,
],
],
// `zend-inputfilter`'s `InputFilterAbstractServiceFactory` config style.
'input_filter_specs' => [
'MyInputFilter' => [
[
'name' => 'username',
'validators' => [
[
'name' => RecordExists::class,
'options' => [
'adapter' => 'db.foo',
'...' => '...',
]
]
]
]
]
],
'controllers' => [
MyController::class => MyControllerFactory::class,
]
];
File SomeModule/src/Validator/DbValidatorAbstractFactory.php
code:
apigility’s RecordExistsFactory
File SomeModule/src/Controller/MyController.php
code:
class MyControllerFactory {
public function __invoke(ContainerInterface $container) {
return new MyController($container->get(InputFilterPluginManager::class)->get('MyInputFilter'));
}
}
class MyController extends AbstractActionController{
public function __construct(InputFilterInterface $inputFilter) {
$this->inputFilter = $inputFilter;
}
public function someAction() {
// ...
$form->setInputFilter($this->inputFilter);
}
}
If all the validation logic uses the same database, then all my suggestions are possible!
No, not validator! The validator is already there: Zend\Validator\Db\RecordExists
.
You need only a factory which creates the validator and adds the database adapter.
I guess I use a different way as you said, as well as @Moln.
You two may have a separate file which store the inputfiler, to me, I put the entity part and inputfilter part together in one file which like
namespace Album\Model;
// Add the following import statements:
use DomainException;
use Zend\Filter\StringTrim;
use Zend\Filter\StripTags;
use Zend\Filter\ToInt;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Zend\Validator\StringLength;
class Album implements InputFilterAwareInterface
{
public $id;
public $artist;
public $title;
// Add this property:
private $inputFilter;
public function exchangeArray(array $data)
{
$this->id = !empty($data['id']) ? $data['id'] : null;
$this->artist = !empty($data['artist']) ? $data['artist'] : null;
$this->title = !empty($data['title']) ? $data['title'] : null;
}
/* Add the following methods: */
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new DomainException(sprintf(
'%s does not allow injection of an alternate input filter',
__CLASS__
));
}
public function getInputFilter()
{
if ($this->inputFilter) {
return $this->inputFilter;
}
$inputFilter = new InputFilter();
$inputFilter->add([
'name' => 'id',
'required' => true,
'filters' => [
['name' => ToInt::class],
],
]);
$inputFilter->add([
'name' => 'artist',
'required' => true,
'filters' => [
['name' => StripTags::class],
['name' => StringTrim::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
],
],
],
]);
$inputFilter->add([
'name' => 'title',
'required' => true,
'filters' => [
['name' => StripTags::class],
['name' => StringTrim::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
],
],
],
]);
$this->inputFilter = $inputFilter;
return $this->inputFilter;
}
}
the official link is Forms and Actions - Tutorials - Zend Framework Docs
I guess you use a different code structure comparing to this way. I thought via your way for several days, and can not get a answer that why I need to crate a RecordExistFactory and create another InputFilterFactory!! Because my inputfilter is not a separate file, it is inside the entity-model file.
You do not need an extra InputFilterFactory
, I never said that. You must create only a factory for the validator and register this factory:
// …
'validators' => [
'factories' => [
Zend\Validator\Db\NoRecordExists::class => DbValidatorAbstractFactory::class,
Zend\Validator\Db\RecordExists::class => DbValidatorAbstractFactory::class,
],
],
// …
With this solution, it does not matter in which way the input-filter is created.
@froschdesign
Hi Frank, thanks!
Now I took the following steps(I have problem on step 4, step 4 solved, now the problem is that, can not overwrite the default validator RecordExists::class and NoRecordExists::class):
step 1. create a factory for the validator named DbValidatorAbstractFactory, it is beyond the module/Application/src/Validator/Factory folder.
namespace Application\Validator\Factory;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Stdlib\ArrayUtils;
use Zend\Validator\Db\RecordExists;
class DbValidatorAbstractFactory implements FactoryInterface
{
private $options;
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
//exit('test whether it run this function'); //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! nothing happened!!!
if (isset($options['adapter'])) {
return new $requestedName(ArrayUtils::merge(
$options,
['adapter' => $container->get($options['adapter'])]
));
}
return new $requestedName($options);
}
public function setCreationOptions(array $options)
{
$this->options = $options;
}
}
step 2 config the new validator factory in the module.config.php like:
'validators' => [
'factories' => [
RecordExists::class => DbValidatorAbstractFactory::class,
NoRecordExists::class => DbValidatorAbstractFactory::class,
],
],
step 3 according the vendor\zendframework\zend-db\src\Adapter\AdapterServiceFactory.php, I made a customer dbadatper named ExpressAdapterServiceFactory in the folder module/Application/src/Db/Factory
namespace Application\Db\Factory;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class ExpressAdapterServiceFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$config = $container->get('config');
return new Adapter($config['express']);
}
}
step 4, My problem is step 4, I wanna to config this new customer db adatper in the top level global.php, how to config it? Can you teach me?
'service_manager' => [
'factories' => [
'adapter.express' => Application\Db\Factory\ExpressAdapterServiceFactory::class, //I dont think it is correct!
],
],
step 5, my Logistics.php model file is a combination of entity and inputfilter the same way as the official tutorial way as following:
namespace Application\Model\Main;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Filter\StringToUpper;
use Zend\Filter\StringTrim;
use Zend\Filter\StripTags;
use Zend\Filter\ToFloat;
use Zend\Filter\ToInt;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Zend\Validator\Between;
use Zend\Validator\Db\NoRecordExists;
use Zend\Validator\Db\RecordExists;
use Zend\Validator\InArray;
use Zend\Validator\NotEmpty;
use Zend\Validator\Regex;
use Zend\Validator\StringLength;
class Logistics implements InputFilterAwareInterface
{
public $id;
public $customer; //customer need to check whether the input coustomerid is exist
...
private $inputFilter;
public function exchangeArray(array $data)
{
$this->id = !empty($data['id']) ? $data['id'] : null;
$this->customer = !empty($data['customer']) ? $data['customer'] : null;
...
}
public function getArrayCopy()
{
return [
'id' => $this->id,
'customer' => $this->customer,
...
];
}
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \DomainException(sprintf(
'%s does not allow injection of an alternate input filter',
__CLASS__
));
}
public function getInputFilter()
{
if ($this->inputFilter) return $this->inputFilter;
$inputFilter = new InputFilter();
$inputFilter->add([
'name' => 'id',
'required' => true,
'filters' => [
['name' => ToInt::class],
],
]);
$inputFilter->add([
'name' => 'customer',
'required' => true,
'filters' => [
['name' => ToInt::class],
],
'validators' => [
[
'name' => RecordExists::class,
'options' => [
'table' => 'main_customers',
'field' => 'id',
'message' => 'customer id input is not exist in the customer table',
'adapter' => 'adapter.express',
],
],
],
]);
....
$this->inputFilter = $inputFilter;
return $this->inputFilter;
}
}
Step 4: config
'service_manager' => [
'factories' => [
// The key name is custom service names, but recommend using fully-qualified class names.
// Read more infomation to see:
// https://docs.zendframework.com/zend-expressive/v3/features/container/intro/#service-names
//
// The value must be a factory class.
'db.express' => Application\Db\Factory\ExpressAdapterServiceFactory::class,
],
],
Get in ServiceManager(ContainerInterface).
class SomeFactory {
public function __invoke(ContainerInterface $container) {
$container->get('db.express');
}
}
You need to learn more about zend-servicemanager factory.
Then, you can configure it in the validator.
[
'name' => RecordExists::class,
'options' => [
'adapter' => 'db.express'
]
]
It is ok that use the ‘db.express’ as the factory key, because I can not find a better one, thank you so much!
Pls look at the above 5 steps I listed(I edited my reply and added the step 5), it is almost the whole logic for this part, pls help me out!
In fact, I took the above 5 steps and submit the form then I got a error when I use
$inputFilter->add([
'name' => 'customer',
'required' => true,
'filters' => [
['name' => ToInt::class],
],
'validators' => [
[
'name' => RecordExists::class,
'options' => [
'table' => 'main_customers',
'field' => 'id',
'message' => 'custome is not in the database',
'adapter' => 'db.express',
],
],
],
]);
Argument 1 passed to Zend\Validator\Db\AbstractDb::setAdapter() must be an instance of Zend\Db\Adapter\Adapter, string given, called in /opt/www/websites/somedomain.com/vendor/zendframework/zend-validator/src/AbstractValidator.php on line 139
It says that, it need a adatper instance, But I passed a string.
I guess the replacement of the default recordexist validator not happen, I can not overwrite the default RecordExists::class and NoRecordExists::class eighter I put the configuration in global.php or module.config.php, why?
I moved the following configs from module.config.php to global.php
'validators' => [
'factories' => [
Zend\Validator\Db\NoRecordExists::class => Application\Validator\Factory\DbValidatorAbstractFactory::class,
Zend\Validator\Db\RecordExists::class => Application\Validator\Factory\DbValidatorAbstractFactory::class,
],
],
I’m sorry, but it is hard to follow you here and what you may have forgotten or overlooked. Can you create a repository and provide the link?
Now forget about the factory stuff, now my question concentrate on that I can not overwrite the built-in validator RecordExists when I use the following configuration in the module.config.php.
'validators' => [
'factories' => [
Zend\Validator\Db\NoRecordExists::class => Application\Validator\Factory\DbValidatorAbstractFactory::class,
Zend\Validator\Db\RecordExists::class => Application\Validator\Factory\DbValidatorAbstractFactory::class,
],
],
The above configuration not work, because when I use RecordExists::class in the inputfilter, it calls the built-in validator, not the factory I registered with the above configuration.
I don’t know why.
Is zend-validator registered as module in application.config.php
?
I have provided an example in this.
I think the class DbValidatorAbstractFactory
merge to zend-validator will be better.