Laravel validate multiple exclusive requires - php

I'm working with Laravel 5.5 and I'm trying to do some complex field validations and I don't seem to be able to find a solution for this.
I have three fields:
field_a (Boolean)
field_b (String)
field_c (String)
I need to validate that if field_a is true then field_b OR field_c are not empty.
I've tried to do something like:
'field_b' => 'required_with:field_a|required_if:field_c,',
'field_c' => 'required_with:field_a|required_if:field_b,',
But this way makes both fields required if field_a has been passed to the request.
Thank you in advance for your help.

I haven't been able to use Laravel's built-in validation system to do this kind of validation so I have created my own custom rule. I leave it here just in case somebody finds it useful.
class IfXOR implements Rule {
private $required;
private $fields;
/**
* Create a new rule instance.
*
* #param bool $required
* #param mixed $fields
* #return void
*/
public function __construct($required, $fields) {
$this->required = $required;
$this->fields = $fields;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value) {
if(!$this->required) return true;
if(!is_array($this->fields)) return $value || !empty($this->fields);
return $value || !empty(array_filter(function($f) { return !empty($f); }, $this->fields));
}
/**
* Get the validation error message.
*
* #return string
*/
public function message() {
return 'The validation error message.';
}
}

Related

encountered an error and could not solve it : count(): Argument #1 ($value) must be of type Countable|array, string given

I encountered the error when I try to sign in my application mede by laravel 8.41.0 and PHP 8.0.3.
error
TypeError
count(): Argument #1 ($value) must be of type Countable|array, string given
location
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php:235
file
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Support\Str;
trait GuardsAttributes
{
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [];
/**
* The attributes that aren't mass assignable.
*
* #var string[]|bool
*/
protected $guarded = ['*'];
/**
* Indicates if all mass assignment is enabled.
*
* #var bool
*/
protected static $unguarded = false;
/**
* The actual columns that exist on the database and can be guarded.
*
* #var array
*/
protected static $guardableColumns = [];
/**
* Get the fillable attributes for the model.
*
* #return array
*/
public function getFillable()
{
return $this->fillable;
}
/**
* Set the fillable attributes for the model.
*
* #param array $fillable
* #return $this
*/
public function fillable(array $fillable)
{
$this->fillable = $fillable;
return $this;
}
/**
* Merge new fillable attributes with existing fillable attributes on the model.
*
* #param array $fillable
* #return $this
*/
public function mergeFillable(array $fillable)
{
$this->fillable = array_merge($this->fillable, $fillable);
return $this;
}
/**
* Get the guarded attributes for the model.
*
* #return array
*/
public function getGuarded()
{
return $this->guarded === false
? []
: $this->guarded;
}
/**
* Set the guarded attributes for the model.
*
* #param array $guarded
* #return $this
*/
public function guard(array $guarded)
{
$this->guarded = $guarded;
return $this;
}
/**
* Merge new guarded attributes with existing guarded attributes on the model.
*
* #param array $guarded
* #return $this
*/
public function mergeGuarded(array $guarded)
{
$this->guarded = array_merge($this->guarded, $guarded);
return $this;
}
/**
* Disable all mass assignable restrictions.
*
* #param bool $state
* #return void
*/
public static function unguard($state = true)
{
static::$unguarded = $state;
}
/**
* Enable the mass assignment restrictions.
*
* #return void
*/
public static function reguard()
{
static::$unguarded = false;
}
/**
* Determine if the current state is "unguarded".
*
* #return bool
*/
public static function isUnguarded()
{
return static::$unguarded;
}
/**
* Run the given callable while being unguarded.
*
* #param callable $callback
* #return mixed
*/
public static function unguarded(callable $callback)
{
if (static::$unguarded) {
return $callback();
}
static::unguard();
try {
return $callback();
} finally {
static::reguard();
}
}
/**
* Determine if the given attribute may be mass assigned.
*
* #param string $key
* #return bool
*/
public function isFillable($key)
{
if (static::$unguarded) {
return true;
}
// If the key is in the "fillable" array, we can of course assume that it's
// a fillable attribute. Otherwise, we will check the guarded array when
// we need to determine if the attribute is black-listed on the model.
if (in_array($key, $this->getFillable())) {
return true;
}
// If the attribute is explicitly listed in the "guarded" array then we can
// return false immediately. This means this attribute is definitely not
// fillable and there is no point in going any further in this method.
if ($this->isGuarded($key)) {
return false;
}
return empty($this->getFillable()) &&
strpos($key, '.') === false &&
! Str::startsWith($key, '_');
}
/**
* Determine if the given key is guarded.
*
* #param string $key
* #return bool
*/
public function isGuarded($key)
{
if (empty($this->getGuarded())) {
return false;
}
return $this->getGuarded() == ['*'] ||
! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) ||
! $this->isGuardableColumn($key);
}
/**
* Determine if the given column is a valid, guardable column.
*
* #param string $key
* #return bool
*/
protected function isGuardableColumn($key)
{
if (! isset(static::$guardableColumns[get_class($this)])) {
static::$guardableColumns[get_class($this)] = $this->getConnection()
->getSchemaBuilder()
->getColumnListing($this->getTable());
}
return in_array($key, static::$guardableColumns[get_class($this)]);
}
/**
* Determine if the model is totally guarded.
*
* #return bool
*/
public function totallyGuarded()
{
return count($this->getFillable()) === 0 && $this->getGuarded() == ['*'];
}
/**
* Get the fillable attributes of a given array.
*
* #param array $attributes
* #return array
*/
protected function fillableFromArray(array $attributes)
{
if (count($this->getFillable()) > 0 && ! static::$unguarded) {
return array_intersect_key($attributes, array_flip($this->getFillable()));
}
return $attributes;
}
}
Some questioners ask same question and I saw that in latest version of PHP count issue the error. But i could not understood how to resolve the problem, so please tell me where and how to fix in my file.
thanks for responses!!
$fillable is declared with squared brackets, all files is added in above.
How can I modify it?
model.php (only fillable related)
public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key, get_class($this)
));
}
}
return $this;
}
app\model user.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class user extends Model
{
use HasFactory;
protected $fillable = 'name';
}
Triple check that $this->getFillable() (line 235) is giving you an array.
I bet it is not, you probably just forgot the square brackets in your model when you declared the $fillable attributes.
In your model, it should looks like this:
protected $fillable = [
'attribute_a',
'attribute_b',
'attribute_c',
//...
];
TypeError is a new type of errors introduced in PHP 8.0. As the name suggests, it is thrown when the type you give to a function is not the type PHP expects.
In your case, count() expects an array but since the $fillable properties of your model is very likely a simple string, a TypeError is thrown.
Comparison between PHP versions:
< 8.0
count('helloworld'); // 1
>= 8.0
count('helloworld'); // TypeError
You can swap count() with isset()
We ran into the same problem on a client's Drupal website. Its' forum module would not load and was giving out this error.
The issue was rooted in a .svg file posted by one of the users. We went back through the comments in the admin panel and came across a comment made a few hours earlier followed by a .svg file. Drupal could not load the file and had replaced it with
that is how we realized this is the issue. We think it only caused the issue when the post was heading to page 2 of the forum.
Removing the SVG solved the problem. We are not planning to find and fix the piece which has caused this, instead, we plan to prevent the addition of SVG files in the forum. That is how we solved the problem. Hope it helps someone.

Laravel Complex Validation Rule inside Array

I wrote an API (Rest, JSON) and would like to validate incoming requests.
The API expects an field "Plan".
Within this field, the client have two choices: "o2_Plan" or "telekom_Plan".
One of them are required. So my validation looks like:
'Plan.o2_Plan' => 'required_without:Plan.telekom_Plan|array',
'Plan.telekom_Plan' => 'required_without:Plan.o2_Plan',
This is working fine. But there are another conditional rules within the plans:
'Plan.o2_Plan.tariff_variation_code' => 'required_without:Plan.o2_Plan.article_id|string',
'Plan.o2_Plan.article_id' => 'sometimes|required_without:Plan.o2_Plan.tariff_variation_code|string',
That means, you have to enter a tariff_variation_code OR an article_id or both of them.
But if the client only transfer the telekom_Plan (which must possible), the validation failed, with this errors:
{
"errors": {
"Plan.o2_Plan.tariff_variation_code": [
"The tariff_variation_code field is required when article_id is not present."
],
"Plan.o2_Plan.article_id": [
"The article_id field is required when tariff_variation_code is not present."
]
}
}
How can I achive, that the validation inside the o2_Plan only works, if the o2_Plan is present.
Thanks in advance.
best regards
Martin
I have the following solution:
Changed
'Plan.o2_Plan' => 'required_without:Plan.telekom_Plan|array',
to
'Plan.o2_Plan' => ['required_without:Plan.telekom_Plan', 'array', new o2Plan(request('Plan.o2_Plan'))],
My o2Plan rule class looks like:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class o2Plan implements Rule
{
protected $request;
protected $message_text = '';
/**
* Create a new rule instance.
*
* #param array $request
*/
public function __construct($request)
{
$this->request = $request;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
if ((!isset($this->request['tariff_variation_code']) || empty($this->request['tariff_variation_code']))
&&
(!isset($this->request['article_id']) || empty($this->request['article_id']))
) {
$this->message_text = 'tariff_variation_code or article should not be empty';
return false;
}
return true;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return $this->message_text;
}
}
And it is working fine :-)

Does this concept to add methods to an existing PHP interface scale?

I am using Nicolas Widart's Laravel Modules package to help manage a large app, and keep everything separated into logical modules. I would like to be able to drop in different modules and have them play nicely without any extra configuration.
All of my modules will define interfaces and default implementations that allow the application (the system controlling which modules are loaded) to specify that it wants to use a specific implementation instead, through dependency injection.
I am able to make some assumptions by having some modules require others, for example a payment processing module (Module PP) can assume that a payment is tied to a user (with which the interface for a user is defined in another module, Module U).
My ideal scenario is that I could add to an existing PHP interface that is defined in another required module. For example, being able to retrieve a user from a repository defined in Module U and call a method on it that was defined in Module PP.
Once Module PP resolves the interface (again, through dependency injection) from Module U to a class, I want my method from Module PP to be callable on that class.
I have been able to achieve this using the __call magic method as below.
Extensions Module
This module defines the core operations to add to an existing interface.
IsExtendable Interface
<?php
namespace Modules\Extensions\Contracts;
interface IsExtendable
{
/**
* Get the list of extensions for this entity.
*
* #return array
*/
public static function getExtensions();
/**
* Adds an extension to this entity.
*
* #param string $name
* #param mixed $function
*/
public static function addExtension($name, $function);
/**
* Checks whether the entity has the given extension.
*
* #param string $name
*
* #return bool
*/
public static function hasExtension($name);
/**
* Call the extension if it exists, or pass it further up the chain.
*
* #param string $name
* #param mixed $arguments
*
* #return mixed
*/
public function __call($name, $arguments);
}
IsExtendable Trait
<?php
namespace Modules\Extensions;
trait IsExtendable
{
/** #var $extensions */
private static $extensions = [];
/**
* Get the list of extensions for this entity.
*
* #return array
*/
public static function getExtensions()
{
return self::$extensions;
}
/**
* Adds an extension to this entity.
*
* #param string $name
* #param mixed $function
*/
public static function addExtension($name, $function)
{
if(is_callable($function) == FALSE)
{
throw new \InvalidArgumentException('Function must be callable.');
}
self::$extensions[$name] = $function;
}
/**
* Checks whether the entity has the given extension.
*
* #param string $name
*
* #return bool
*/
public static function hasExtension($name)
{
return array_key_exists($name, self::getExtensions()) == TRUE;
}
/**
* Calls the extension if it exists, or passes it further up the chain.
*
* #param string $name
* #param mixed $arguments
*
* #return mixed
*/
public function __call($name, $arguments)
{
if(self::hasExtension($name) == TRUE)
{
$callable = self::getExtensions()[$name];
return call_user_func_array($callable, array_merge(array($this), $arguments));
}
else
{
return parent::__call($name, $arguments);
}
}
}
Service Provider
<?php
namespace Modules\Extensions\Providers;
use Illuminate\Support\ServiceProvider;
use Modules\Extensions\Contracts\IsExtendable as IsExtendableContract;
class ExtensionServiceProvider extends ServiceProvider
{
/**
* #param string $implementation
* #param string $functionName
*
* #return callable
*/
public function prepareExtension($implementation, $functionName)
{
return $implementation . '::' . $functionName;
}
/**
* #param string $contract
* #param string $implementation
*
* #return void
*/
public function extractExtensions($contract, $implementation)
{
$reflection = new \ReflectionClass($implementation);
$methods = [];
foreach($reflection->getMethods(\ReflectionMethod::IS_STATIC) as $method)
{
// TODO: May be able to use $method->getClosure() here
// https://stackoverflow.com/questions/8299886/php-get-static-methods
$methods[] = $method->getName();
}
$this->registerExtensions($contract, $methods, $implementation);
}
/**
* #param string $contract
* #param string $name
* #param string $function
*
* #return void
*/
public function registerExtension($contract, $name, $function)
{
// Resolve the contract to an implementation
$base = app($contract);
// Check that it is suitable for extension
if($base instanceof IsExtendableContract)
{
$base::addExtension($name, $function);
}
}
/**
* #param string $contract
* #param array $extensions
* #param string|null $implementation
*
* #return void
*/
public function registerExtensions($contract, array $extensions = [], $implementation = NULL)
{
// Resolve the contract to an implementation
$base = app($contract);
// Check that it is suitable for extension
if($base instanceof IsExtendableContract)
{
foreach($extensions as $name => $function)
{
if(is_int($name) == TRUE)
{
if(is_string($function) == TRUE)
{
$name = $function;
}
else
{
throw new \InvalidArgumentException('All extensions must have a valid name.');
}
}
if(is_string($function) == TRUE)
{
if(strpos($function, '::') === FALSE && $implementation != NULL)
{
$function = $this->prepareExtension($implementation, $function);
}
}
$base::addExtension($name, $function);
}
}
}
}
Module U
User Interface
<?php
namespace Modules\Auth\Contracts\Entities;
interface User
{
/**
* #return int
*/
public function getId();
/**
* #return string
*/
public function getName();
/**
* #return string
*/
public function getEmail();
/**
* #return \DateTime
*/
public function getCreatedAt();
/**
* #return \DateTime
*/
public function getUpdatedAt();
}
User Implementation
<?php
namespace Modules\Auth\Entities;
use Modules\Extensions\Contracts\IsExtendable as IsExtendableContract;
use Modules\Auth\Contracts\Entities\User as UserContract;
use Modules\Extensions\IsExtendable;
class User implements
IsExtendableContract,
UserContract
{
use IsExtendable;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
}
Module PP
User Extension
<?php
namespace Modules\Test\Entities\Extensions;
use Modules\Auth\Contracts\Entities\User;
class UserExtension
{
/**
* #param User $context
*/
public static function getCardLastFour($context)
{
return $context->card_last_four;
}
/**
* #param User $context
*/
public static function getCardBrand($context)
{
return $context->card_brand;
}
/**
* #param User $context
*/
public static function getStripeId($context)
{
return $context->stripe_id;
}
}
Service Provider
<?php
namespace Modules\Test\Providers\Extensions;
use Modules\Auth\Contracts\Entities\User as UserContract;
use Modules\Test\Entities\Extensions\UserExtension;
use Modules\Extensions\Providers\ExtensionServiceProvider;
class StripeExtensionProvider extends ExtensionServiceProvider
{
public function boot()
{
// TODO: Set the contract as a static field on the extension to then automatically extract from all extension files in a folder
$this->extractExtensions(UserContract::class, UserExtension::class);
}
}
My question is, is this method scalable (across maybe 10 modules), and can you foresee any issues with it? Or is there a better/more popular (and supported) way to do this? I don't want to get 2 years into a project and discover that I really hate the way I've implemented this.
I know that this concept won't support IDE autocompletion out of the box but I could build in a way to generate the PHPDocs similar to this package.
I have researched the Decorator pattern but this feels clunky in that I would always need to rely on a new implementation within each module, instead of just adding to the existing one.
I realise this is a big question so my sincere thanks to anyone willing to have a look at it!
Check out Laravel's macroable trait. It's basically the same idea, and Laravel uses it all over the place.
So yes, it scales - up to a certain point. Like almost everything else, this is a tool that can be abused. Use it with a dash of common sense, and you should be OK.

Laravel form validation unique using 2 fields

How can I have a unique validation rule on 2 fields?
a. The application should not allow two people to have the same identical first name and last name.
It is allowed that the users fills in only a first name or only a last name. Because the user may have only one of them.
b. But if the user enters only a first name (Glen), no other person in the table should have the same (first name = 'Glen' and last name = null). another 'Glen Smith' ok.
I tried the following rule. It works great when both fields (first and last name) are not null:
'firstName' => 'unique:people,firstName,NULL,id,lastName,' . $request->lastName
This rule fails on b. when only one field is present.
Any hint?
The built in unique validator wouldn't really support what you're trying to do. It's purpose is to ensure that a single valid is unique in the database, rather than a composite of two values. However, you can create a custom validator:
Validator::extend('uniqueFirstAndLastName', function ($attribute, $value, $parameters, $validator) {
$count = DB::table('people')->where('firstName', $value)
->where('lastName', $parameters[0])
->count();
return $count === 0;
});
You could then access this new rule with:
'firstName' => "uniqueFirstAndLastName:{$request->lastName}"
You'll probably find you might need to tweak your database query a little bit as it's untested.
I think you are looking for something like that:
'unique:table_name,column1,null,null,column2,'.$request->column2.',column3,check3'
This is an extensive answer to this question and how to create Laravel custom validator generally, you can simply copy and paste, and try to understand later:
Step 1: Create a provider app/Providers/CustomValidatorProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
/**
* Provider for custom validators. Handles registration for custom validators.
*
*/
class CustomValidatorProvider extends ServiceProvider {
/**
* An array of fully qualified class names of the custom validators to be
* registered.
*
* #var array
*/
protected $validators = [
\App\Validators\MultipleUniqueValidator::class,
];
/**
* Bootstrap the application services.
*
* #return void
* #throws \Exception
*/
public function boot() {
//register custom validators
foreach ($this->validators as $class) {
$customValidator = new $class();
ValidatorFacade::extend($customValidator->getName(), function() use ($customValidator) {
//set custom error messages on the validator
func_get_args()[3]->setCustomMessages($customValidator->getCustomErrorMessages());
return call_user_func_array([$customValidator, "validate"], func_get_args());
});
ValidatorFacade::replacer($customValidator->getName(), function() use ($customValidator) {
return call_user_func_array([$customValidator, "replacer"], func_get_args());
});
}
}
/**
* Register the application services.
*
* #return void
*/
public function register() {
//
}
}
Step 2: Update your app.php in your config folder config/app.php to include your created provider in the provider array
App\Providers\CustomValidatorProvider::class,
Step 3: Create your custom validator, in my case, I am creating multiple unique fields validator app/Validators/MultipleUniqueValidator.php
<?php
namespace App\Validators;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Validation\Validator;
/**
* Multiple field uniqueness in laravel
*/
class MultipleUniqueValidator{
/**
* Name of the validator.
*
* #var string
*/
protected $name = "multiple_unique";
/**
* Return the name of the validator. This is the name that is used to specify
* that this validator be used.
*
* #return string name of the validator
*/
public function getName(): string {
return $this->name;
}
/**
*
* #param string $message
* #param string $attribute
* #param string $rule
* #param array $parameters
* #return string
*/
public function replacer(string $message, string $attribute, string $rule, array $parameters): string {
unset($parameters[0]);
$replacement = implode(", ", $parameters);
$replacement = str_replace("_", " ", $replacement);
$replacement = Str::replaceLast(", ", " & ", $replacement);
$replacement = Str::title($replacement);
return str_replace(":fields", "$replacement", $message);
}
/**
*
* #param string $attribute
* #param mixed $value
* #param array $parameters
* #param Validator $validator
* #return bool
* #throws \Exception
*/
public function validate(string $attribute, $value, array $parameters, Validator $validator): bool {
$model = new $parameters[0];
if (!$model instanceof Model) {
throw new \Exception($parameters[0] . " is not an Eloquent model");
}
unset($parameters[0]);
$this->fields = $parameters;
$query = $model->query();
$request = app("request");
foreach($parameters as $parameter){
$query->where($parameter, $request->get($parameter));
}
return $query->count() == 0;
}
/**
* Custom error messages
*
* #return array
*/
public function getCustomErrorMessages(): array {
return [
$this->getName() => ":fields fields should be unique"
];
}
}
Now you can do this in your request
'ANY_FIELD_CAN_CARRY_IT' => 'required|numeric|multiple_unique:'.YOUR_MODEL_HERE::class.',FIELD_1,FIELD_2,FIELD_3...',
Laravel now allows you to add where clauses into the unique rule.
In your case you could do something like this:
'firstName' => [
Rule::unique('people', 'firstName')->where(function ($query) use ($lastName) {
return $query->where('lastName', $lastName);
})
],
in my case this works just fine (in controller):
$request->validate([
'firstName' => 'required|min:3|max:255|unique:people,firstName,NULL,id,lastname,' . $request->input('lastname'),
], [
'unique' => 'Some message for "unique" error',
]);
You can do it if the Validator class isn't required for you:
if(Model::query()->where([
'column_1' => 'data_1',
'column_2' => 'data_2'
])->exists())
{
// some code..
}

Validation in Zend Framework 2 with Doctrine 2

I am right now getting myself more and more familiar with Zend Framework 2 and in the meantime I was getting myself updated with the validation part in Zend Framework 2. I have seen few examples how to validate the data from the database using Zend Db adapter, for example the code from the Zend Framework 2 official website:
//Check that the username is not present in the database
$validator = new Zend\Validator\Db\NoRecordExists(
array(
'table' => 'users',
'field' => 'username'
)
);
if ($validator->isValid($username)) {
// username appears to be valid
} else {
// username is invalid; print the reason
$messages = $validator->getMessages();
foreach ($messages as $message) {
echo "$message\n";
}
}
Now my question is how can do the validation part?
For example, I need to validate a name before inserting into database to check that the same name does not exist in the database, I have updated Zend Framework 2 example Album module to use Doctrine 2 to communicate with the database and right now I want to add the validation part to my code.
Let us say that before adding the album name to the database I want to validate that the same album name does not exist in the database.
Any information regarding this would be really helpful!
if you use the DoctrineModule, there is already a validator for your case.
I had the same problem and solved it this way:
Create a custom validator class, name it something like NoEntityExists (or whatever you want).
Extend Zend\Validator\AbstractValidator
Provide a getter and setter for Doctrine\ORM\EntityManager
Provide some extra getters and setters for options (entityname, ...)
Create an isValid($value) method that checks if a record exists and returns a boolean
To use it, create a new instance of it, assign the EntityManager and use it just like any other validator.
To get an idea of how to implement the validator class, check the validators that already exist (preferably a simple one like Callback or GreaterThan).
Hope I could help you.
// Edit: Sorry, I'm late ;-)
So here is a quite advanced example of how you can implement such a validator.
Note that I added a translate() method in order to catch language strings with PoEdit (a translation helper tool that fetches such strings from the source codes and puts them into a list for you). If you're not using gettext(), you can problably skip that.
Also, this was one of my first classes with ZF2, I wouldn't put this into the Application module again. Maybe, create a new module that fits better, for instance MyDoctrineValidator or so.
This validator gives you a lot of flexibility as you have to set the query before using it. Of course, you can pre-define a query and set the entity, search column etc. in the options. Have fun!
<?php
namespace Application\Validator\Doctrine;
use Zend\Validator\AbstractValidator;
use Doctrine\ORM\EntityManager;
class NoEntityExists extends AbstractValidator
{
const ENTITY_FOUND = 'entityFound';
protected $messageTemplates = array();
/**
* #var EntityManager
*/
protected $entityManager;
/**
* #param string
*/
protected $query;
/**
* Determines if empty values (null, empty string) will <b>NOT</b> be included in the check.
* Defaults to true
* #var bool
*/
protected $ignoreEmpty = true;
/**
* Dummy to catch messages with PoEdit...
* #param string $msg
* #return string
*/
public function translate($msg)
{
return $msg;
}
/**
* #return the $ignoreEmpty
*/
public function getIgnoreEmpty()
{
return $this->ignoreEmpty;
}
/**
* #param boolean $ignoreEmpty
*/
public function setIgnoreEmpty($ignoreEmpty)
{
$this->ignoreEmpty = $ignoreEmpty;
return $this;
}
/**
*
* #param unknown_type $entityManager
* #param unknown_type $query
*/
public function __construct($entityManager = null, $query = null, $options = null)
{
if(null !== $entityManager)
$this->setEntityManager($entityManager);
if(null !== $query)
$this->setQuery($query);
// Init messages
$this->messageTemplates[self::ENTITY_FOUND] = $this->translate('There is already an entity with this value.');
return parent::__construct($options);
}
/**
*
* #param EntityManager $entityManager
* #return \Application\Validator\Doctrine\NoEntityExists
*/
public function setEntityManager(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
return $this;
}
/**
* #return the $query
*/
public function getQuery()
{
return $this->query;
}
/**
* #param field_type $query
*/
public function setQuery($query)
{
$this->query = $query;
return $this;
}
/**
* #return \Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* (non-PHPdoc)
* #see \Zend\Validator\ValidatorInterface::isValid()
* #throws Exception\RuntimeException() in case EntityManager or query is missing
*/
public function isValid($value)
{
// Fetch entityManager
$em = $this->getEntityManager();
if(null === $em)
throw new Exception\RuntimeException(__METHOD__ . ' There is no entityManager set.');
// Fetch query
$query = $this->getQuery();
if(null === $query)
throw new Exception\RuntimeException(__METHOD__ . ' There is no query set.');
// Ignore empty values?
if((null === $value || '' === $value) && $this->getIgnoreEmpty())
return true;
$queryObj = $em->createQuery($query)->setMaxResults(1);
$entitiesFound = !! count($queryObj->execute(array(':value' => $value)));
// Set Error message
if($entitiesFound)
$this->error(self::ENTITY_FOUND);
// Valid if no records are found -> result count is 0
return ! $entitiesFound;
}
}

Categories