How to use RecordExists validator in Callback validator?

in getInputFilterSpecification
to some form element

[
                'name' => 'targetid', //目标的id,例如帖子的id
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'callback' => function($value, $context){
                                if ((int)($context['targettype']) == 1)
                                {
                                    $validatorChain = $this->getInputFilter()->get('targetid')->getValidatorChain();
                                    $validatorChain->attach(new RecordExists([
                                        'table'   => 'thread_main',
                                        'field'   => 'id',
                                        'message' => '帖子不存在',
                                    ]));
                                    $this->getInputFilter()->get('targetid')->setValidatorChain($validatorChain);
                                    return false;
                                }
                                return true;
                            },
                            'message' => 'target有问题',
                        ],
                    ],
                ],
            ],

I refered to this page How use Callback validator when field is conditionally required? · Issue #2 · laminas/laminas-inputfilter · GitHub
I always got message “target有问题”

I my global.php
I configed this:

'validators' => [
        'delegators' => [
            Laminas\Validator\Db\RecordExists::class => [
                Laminas\Db\Adapter\AdapterServiceDelegator::class,
            ],
        ],
    ],

The validator RecordExists is independent from you application because you created the validator yourself via new RecordExists(), this means the delegator has no effect here.

Use the method attachByName of the validator chain and the validator-plugin-manager is used internally. This manager belongs to your application.

Thanks for your reply!

I changed to the following, but still not work, the result is the same as above, it raise message target有问题

               'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'callback' => function($value, $context){
                                if ((int)($context['targettype']) == 1)
                                {
                                    $validatorChain = $this->getInputFilter()->get('targetid')->getValidatorChain();
                                    $validatorChain->attachByName(RecordExists::class,[
                                        'table'   => 'thread_main',
                                        'field'   => 'id',
                                        'message' => '帖子不存在',
                                    ]);
                                    $this->getInputFilter()->get('targetid')->setValidatorChain($validatorChain);
                                    return false;
                                }
                                return true;
                            },
                            'message' => 'target有问题',
                        ],
                    ],
                ],

The following is my whole form model, I imported it into my action via $form = $this->formElementManager->get(ForumRewardForm::class);

<?php
/**
 * forum_reward对应的表单模型
 */

namespace Application\Form;


use Laminas\Filter\ToInt;
use Laminas\Form\Element\Hidden;
use Laminas\Form\Form;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Validator\Db\RecordExists;
use Laminas\Validator\NotEmpty;
use Laminas\Validator\Callback;

class ForumRewardForm extends Form implements InputFilterProviderInterface
{
    public function init() : void
    {
        $this->add([
            'name' => 'id',
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'fromuid', //打赏的uid
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'touid', //被打赏人的uid
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'targettype', //1对帖子打赏
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'targetid', //目标的id,例如帖子的id
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'rewardtype', //1打赏的是金币
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'rewardnum', //打赏的数量,比如10金币
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'ip', //打赏的人的ip
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'addtime', //添加时间戳
            'type' => Hidden::class,
        ]);

    }

    public function getInputFilterSpecification() : array
    {
        return [
            [
                'name' => 'id',
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'id不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'fromuid', //打赏的uid
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'fromuid不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'touid', //被打赏人的uid
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'touid不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'targettype', //1对帖子打赏
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'targettype不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'targetid', //目标的id,例如帖子的id
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'callback' => function($value, $context){
                                if ((int)($context['targettype']) == 1)
                                {
                                    $validatorChain = $this->getInputFilter()->get('targetid')->getValidatorChain();
                                    $validatorChain->attachByName(RecordExists::class,[
                                        'table'   => 'thread_main',
                                        'field'   => 'id',
                                        'message' => '帖子不存在',
                                    ]);
                                    $this->getInputFilter()->get('targetid')->setValidatorChain($validatorChain);
                                    /*$result = $this->getInputFilter()->get('targetid')->getValidatorChain()->isValid($value,$context);
                                    print_r($result);exit;*/
                                    //return false;
                                }
                                //return true;
                            },
                            'message' => 'target有问题',
                        ],
                    ],
                ],
            ],
            [
                'name' => 'rewardtype', //1打赏的是金币
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'rewardtype不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'rewardnum', //打赏的数量,比如10金币
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'rewardnum不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'ip', //打赏的人的ip
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'ip不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'addtime', //添加时间戳
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'addtime不能为空'],
                    ],
                ],
            ],
        ];
    }
}

The validator throws an exception if the adapter is missing:

Please use a debugger to check your input-filter and if the excepted values and validators are included.

Another option is to provide a small code example to reproduce the problem, without database validators and to copy.

no error raise.

It is always invalid when there is a “return false”

I’m sorry, I can not test this, therefore:

The key codes I list as follow:

config/autoload/global.php

return [
    'db' => [
        'driver' => 'Pdo',
        'dsn'    => 'mysql:dbname=discourse;host=localhost;charset=utf8mb4',
    ],
    'validators' => [
        'delegators' => [
            Laminas\Validator\Db\RecordExists::class => [
                Laminas\Db\Adapter\AdapterServiceDelegator::class,
            ],
        ],
    ],
];

module/Application/src/Form/ForumRewardForm.php

<?php
/**
 * forum_reward对应的表单模型
 */

namespace Application\Form;


use Laminas\Filter\StringTrim;
use Laminas\Filter\StripTags;
use Laminas\Filter\ToInt;
use Laminas\Form\Element\Hidden;
use Laminas\Form\Form;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Validator\Between;
use Laminas\Validator\Db\RecordExists;
use Laminas\Validator\InArray;
use Laminas\Validator\Ip;
use Laminas\Validator\NotEmpty;
use Laminas\Validator\Callback;

class ForumRewardForm extends Form implements InputFilterProviderInterface
{
    public function init() : void
    {
        $this->add([
            'name' => 'id',
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'fromuid', //打赏的uid
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'touid', //被打赏人的uid
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'targettype', //1对帖子打赏
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'targetid', //目标的id,例如帖子的id
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'rewardtype', //1打赏的是金币
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'rewardnum', //打赏的数量,比如10金币
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'ip', //打赏的人的ip
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'addtime', //添加时间戳
            'type' => Hidden::class,
        ]);

    }

    public function getInputFilterSpecification() : array
    {
        return [
            [
                'name' => 'id',
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'id不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'fromuid', //打赏的uid
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'fromuid不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'touid', //被打赏人的uid
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'touid不能为空'],
                    ],
                ],
            ],
            [
                'name' => 'targettype', //1对帖子打赏
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'targettype不能为空'],
                    ],
                    [
                        'name' => InArray::class,
                        'options' => [
                            'haystack' => [1,2],
                            'message' => 'type只能是1或者2'
                        ],
                    ]
                ],
            ],
            [
                'name' => 'targetid', //目标的id,例如帖子的id
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'callback' => function($value, $context){
                                if ((int)($context['targettype']) == 1)
                                {
                                    $validatorChain = $this->getInputFilter()->get('targetid')->getValidatorChain();
                                    $validatorChain->attachByName(RecordExists::class,[
                                        'table'   => 'thread_main',
                                        'field'   => 'id',
                                        'message' => '帖子不存在',
                                    ]);
                                    $this->getInputFilter()->get('targetid')->setValidatorChain($validatorChain);
                                    return false;
                                }
                                return true;
                            },
                            'message' => 'target有问题',
                        ],
                    ],
                ],
            ],
            [
                'name' => 'rewardtype', //1打赏的是金币
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'rewardtype不能为空'],
                    ],
                    [
                        'name' => InArray::class,
                        'options' => [
                            'haystack' => [1,2],
                            'message' => 'way只能是1或者2'
                        ],
                    ]
                ],
            ],
            [
                'name' => 'rewardnum', //打赏的数量,比如10金币
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'rewardnum不能为空'],
                    ],
                    [
                        'name' => Between::class,
                        'options' => [
                            'min' => 0,
                            'max' => 10000,
                            'inclusive' => false,
                            'message' => '打赏数量0-9999',
                        ],
                    ]
                ],
            ],
            [
                'name' => 'ip', //打赏的人的ip
                'required' => true,
                'filters' => [
                    ['name' => StripTags::class],
                    ['name' => StringTrim::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'ip不能为空'],
                    ],
                    [
                        'name' => Ip::class,
                        'options' => [
                            'allowipv6' => false,
                            'message' => '不支持ipv6'
                        ],
                    ],
                ],
            ],
            [
                'name' => 'addtime', //添加时间戳
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => NotEmpty::class,
                        'options' => ['message' => 'addtime不能为空'],
                    ],
                ],
            ],
        ];
    }
}

module/Applicaiton/src/Controller/RewardController.php

<?php
/**
 * forum_reward 相关的控制器
 */

namespace Application\Controller;


use Application\Controller\Plugin\AuthPlugin;
use Application\Controller\Plugin\ThreadPlugin;
use Application\Form\ForumRewardForm;
use Application\Model\BaseFuncModel;
use Application\Model\ForumReward;
use Application\Model\ForumRewardTable;
use Laminas\Form\FormElementManager;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\JsonModel;

class RewardController extends AbstractActionController
{
    protected $baseFuncModel;
    protected $forumRewardTable;
    protected $formElementManager;

    /** DI */
    public function __construct(BaseFuncModel $baseFuncModel, ForumRewardTable $forumRewardTable, FormElementManager $formElementManager)
    {
        $this->baseFuncModel = $baseFuncModel;
        $this->forumRewardTable = $forumRewardTable;
        $this->formElementManager = $formElementManager;
    }

    public function rewardingAction()
    {
        $request = $this->getRequest();

        /** @var AuthPlugin $authPlugin */
        $authPlugin = $this->plugin('auth');
        $identityArr = $authPlugin->getIdentity($request);

        $ip = $this->baseFuncModel->getIp($request);
        if (!$ip) return new JsonModel([
            'status' => false,
            'msg' => '获取不到ip',
            'code' => 111,
            'data' => null,
        ]);

        $muteArr = $authPlugin->getMute([$identityArr['uid']]);
        if (isset($muteArr[$identityArr['uid']]) && $muteArr[$identityArr['uid']]) return new JsonModel([
            'status' => false,
            'msg' => '你已经被封禁',
            'code' => 111,
            'data' => null,
        ]);

        $frequencyAllow = $this->baseFuncModel->frequencyForbidden($identityArr['uid'],'rewarding',$ip);
        if (!$frequencyAllow) return new JsonModel([
            'status' => false,
            'msg' => '频率太高',
            'code' => 111,
            'data' => null,
        ]);

        $targettype = (int) $request->getQuery('type', 1); //先强行按照只能打赏帖子,且只能打赏金币来处理
        $targetid = (int) $request->getQuery('target', 0);
        $rewardtype = (int) $request->getQuery('way', 1);
        $rewardnum = (int) $request->getQuery('amount', 0);

        $getArr = compact('targettype', 'targetid', 'rewardtype', 'rewardnum', 'ip');

        $form = $this->formElementManager->get(ForumRewardForm::class);
        $form->setValidationGroup('targettype', 'targetid','rewardtype', 'rewardnum', 'ip');
        $form->setData($getArr);

        if (!$form->isValid()) return new JsonModel([
            'status' => false,
            'msg' => $this->baseFuncModel->formErrorOneByOne($form->getMessages()),
            'code' => 111,
            'data' => null,
        ]);

        /** @var ThreadPlugin $threadPlugin */
        $threadPlugin = $this->plugin('thread');
        $threadStatic = $threadPlugin->threadDetailStatic($targetid);
        $threadStatic = current($threadStatic);

        $forumReward = new ForumReward();
        $forumReward->exchangeArray($form->getData());
        $result = $this->forumRewardTable->save($forumReward, $identityArr, $threadStatic);

        return new JsonModel([
            'status' => $result['status'],
            'msg' => $result['msg'],
            'code' => 111,
            'data' => null,
        ]);
    }
}

Any other codes needed for you to figure out the problem?

Please provide a form or input-filter without database validators and without a controller!
And keep in mind: I do not have a database here and I do not have a full application. So reduce the example.

I understood. It is hard because the documentation shows example which is not embedding inside a real project. In reality, the validators are configurated via a configuration file.

[
                'name' => 'targetid', //目标的id,例如帖子的id
                'required' => true,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'callback' => function($value, $context){
                                if ((int)($context['targettype']) == 1)
                                {
                                    $validatorChain = $this->getInputFilter()->get('targetid')->getValidatorChain();
                                    $validatorChain->attach(new Between(['min' => 0, 'max' => 10]));
                                    $this->getInputFilter()->get('targetid')->setValidatorChain($validatorChain);
                                    return false;
                                }
                                return true;
                            },
                            'message' => 'target有问题',
                        ],
                    ],
                ],
            ],

always fail to pass the validating.

I feel that the attach step has no problem. but the validator does not execute for some reason.

Although, the exit() I wrote inside the Between’s isValid(), However it won’t change the result of the validating. It is always false when I use a suitable value. It should pass if everything works correctly.

Please adjust two points:

  • The callback validator extends only the validator chain of another input, therefore the return value must be always true because the validator itself checks nothing.
  • You can remove the call of setValidatorChain.

And after the call of $form->isValid() try to fetch the database adapter:

foreach ($form->getInputFilter()->getInputs() as $input) {
    foreach ($input->getValidatorChain()->getValidators() as $validator) {
        if ($validator['instance'] instanceof Laminas\Validator\Db\RecordExists) {
            var_dump($validator['instance']->getAdapter());
        }
    }
}

Thanks for your reply.

I followed your advises, and do the following testing:

Another test

I setup a debug environment via phpstorm, xdebug, DBGp Proxy, use the 9003 port.
with a debug extension work in my chrome web browser.

Ps: whatever the id is, it always pass the validate. I use a very big id 9999999999999999, it should not be exist in my database, it reture true. no form error message got.

I got some screen shot as follow:

========================================

To clarify: you use an example from the issue tracker of laminas-inputfilter and nobody knows if this works like that.
Unfortunately I cannot check the result myself.

Please try the reproduce the result in a separate form, isolated from your application.


Another option is to use the isValid method of your form. Something like that:

public function isValid(): bool
{
    if ($this->hasValidated) {
        return $this->isValid;
    }
    
    $inputFilter = $this->getInputFilter();

    if (isset($this->data['first_input']) && $inputFilter) {
        $inputFilter->get('second_input')->setRequired(true);
    }

    return parent::isValid();
}