Check if the value already exists in the database using Laravel - php

Description: I have a site. I just want to keep track of a suspicious request and possible barn them only if needed. I just started to implement that feature. I have all the records of IP Addresses, but I'm not sure how to increment their visit count each time - they visit.
Goal: To increment visit_count attribute each time user visit a site
In my visitors table, I have an ip attribute
I want to check for an existing first before, I perform the saving, and other logics, but I'm just a little stuck here.
How do I check if the value already exists in the database using Laravel ?
Any hints on this will be much appreciated !
I've tried
Model : Visitor.php
class Visitor extends Model {
protected $table = 'visitors';
//Validation Rules and Validator Function
public static function validator($input, $id = ''){
$rules = array(
'ip' =>'unique:visitors,ip,'.$id,
);
return Validator::make($input,$rules);
}
}
Controller : Visitor.php
// Check for existing
$validator = Visitor::validator($ip);
if ($validator->fails()) {
$ip = Visitor::where('ip', '=', $ip)->firstOrFail();
$id = $ip['attributes']['id']; //<------ Not sure if this is a proper way
if($ip){
$visitor = Visitor::findOrFail($id);
$visitor->visit_count = $visitor->visit_count + 1 ;
$visitor->save();
}
} else {
$visitor = new Visitor;
$visitor->ip = $ip;
$visitor->visit_count = $visitor->visit_count + 1 ;
$visitor->save();
}
Result
I keep getting
Argument 1 passed to Illuminate\Validation\Factory::make() must be of the type array, string given
I believe, it from this line here $validator = Visitor::validator($ip);

The error message kind of gives it away. The Validator expects the values and the rules to be two separate arrays, each with keys denoting the columns name that needs to be validated. You have that for the rules, but not for the values being checked. This will fix your error:
return Validator::make(['ip' => $input], $rules);

Related

Show error message when detected duplicate entry

I wanted to let the system to show error message when detect duplicated entry of full_name column without applying unique in the full_name column from public function rules() in model.
My code is like this :
if ($model->load(Yii::$app->request->post()) ) {
$model->full_name = $model->first_name .'' . $model->last_name ;
$name = StudentInfo::find()->select('full_name')->where(['full_name'=> $model->full_name]);
if($name == $model->full_name ){
echo "<script type='text/javascript'>alert('Same student name is detected');</script>";
}
else{
$model->status ="Active";
$model->call_format = Countries::find()->select('phonecode')->where(['name'=> $model->country]);
$model->date_created = new Expression('NOW()');
$user->user_type ='student';
$user->user_name = $model->full_name;
$user->user_status = $model->status;
$user->authKey = Yii::$app->security->generateRandomString(10);
$user->accessToken = Yii::$app->security->generateRandomString(10);
$user->save();
$model->save();
return $this->redirect(['view', 'id' => $model->id]);
}
}
But it shows error like :missing required parameters: id. When i apply model->save(false) ,it seems that the sql statement wont run because of duplicate entry in full_name column. How do i fix it?
Well, there is a construct exists() for such a purposes (see Yii2: check exist ActiveRecord model in database ).
if(StudentInfo::find()->where(['full_name'=> $model->full_name])->exists()){
echo "<script type='text/javascript'>alert('Same student name is detected');</script>";
}
else{...}
it generates the EXISTS query, which is faster and you don't have to load all the data from DB.
If you don't have such a column in your table, then check it by the first/last name.
change it:
$name = StudentInfo::find()->select('full_name')->where(['full_name'=> $model->full_name]);
To:
$name = StudentInfo::find()->select('full_name')->where(['full_name'=> $model->full_name])->one();
Also, if you use the select() method, to use the update() and save() or updateCounters() ... methods, you need the row ID in the same query.
Example:
->select('id') or ->select(['id', 'full_name'])
info: Multi-parameter is an array in select()
:missing required parameters: id
could mean that it couldn't find id, not by duplicate entry in full_name column. please check again
There are two problems with your code.
$name = StudentInfo::find()->select('full_name')->where(['full_name'=> $model->full_name]);
When this line is executed the $name variable will contain instance of yii\db\ActiveQuery. You want to call some method, that will actually execute your query and return result.
You can use scalar() to get the selected value. In that case the $name will contain the content of full_name column from result.
$name = StudentInfo::find()
->select('full_name')
->where(['full_name'=> $model->full_name])
->scalar();
Or you can use count() to get the number of rows that match condition. In that case you may leave out the select() method call but you will need to modify your condition
$count = StudentInfo::find()
->where(['full_name'=> $model->full_name])
->count();
if ($count > 0) {
echo "<script type='text/javascript'>alert('Same student name is detected');</script>";
} else {
// ...
}
The other problem is that you are not checking whether your $model->save() was successful. If your $model is new instance and the id attribute is auto-generated then when $model->save fails the $model->id is empty and then you are trying to redirect user to view with empty id.
Your code should look like this:
if ($user->save() && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
If the save fails because of validation the validation errors will be stored in models and if you are using ActiveForm widget the errors will be displayed. If you are not using ActiveForm you should do something to tell user that operation failed.
Since you are saving two different models you might want to consider use of transactions to prevent a situations where $user model is saved but save of $model fails.

Laravel moving Controller logic to a Model

I'm at a stage where I'm refactoring my code, and I've come across an interesting conundrum.
In my ArticleController I have a bog standard store method for storing an article in my articles database table.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(StoreArticle $request)
{
$article = new Article();
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($request->get('featuredArticle') == "1" ? true : false);
$IntranetOnly = ($request->get('IntranetOnly') == "1" ? true : false);
$article->title = $request->get('title');
$article->slug = str_slug($request->get('title'));
$article->author = $request->get('author');
$article->category = $request->get('category');
$article->excerpt = $request->get('excerpt');
$article->content = clean($request->get('content'));
$article->featuredImage = $request->get('featuredImage');
$article->featuredVideo = $request->get('featuredVideo');
$article->readingTime = $this->calculateReadTime($request);
$article->featuredArticle = $isFeatured;
$article->IntranetOnly = $IntranetOnly;
$article->published = $defauultPublished;
$article->save();
$article->handleTags($request);
return redirect('editable/news-and-updates')->with('success', 'Article has been added');
}
I also have a function for calculating read time:
/**
* Calculate a rough reading time for an articles by counting the words present
* These words are then divided by a given reading time and rounded to the nearest whole number
* Reading time average is roughly 267 words per minute, so this also accounts for relatively slow readers
*
* #param Request $request
* #return void
*/
public function calculateReadTime(Request $request)
{
$readingSpeed = 200;
$title = str_word_count(strip_tags($request->get('title')));
$excerpt = str_word_count(strip_tags($request->get('excerpt')));
$content = str_word_count(strip_tags($request->get('content')));
$words = ($title + $excerpt + $content);
$minutes = round($words / $readingSpeed);
return $minutes . ' minute' . ($minutes == 1 ? '' : 's');
}
My question is should these methods be moved to the Article model?
Controller should be as slim as possible. Following a resourceful approach (which you seem to be doing), the store() method in your ArticleController class should strive as much as possible to look like this:
class ArticleController extends Controller
{
public function store(CreateArticleRequest $request)
{
$article = Article::create($request->validated());
// Redirect with success message
}
}
Here, your request data is validated in a form request class before it even reaches the controller method; and then an Article model instance is created from that validated data.
A couple of other notes…
Statements like ($data['featuredArticle'] == "1" ? true : false) are overly verbose. You’re doing a condition check which will evaluate to true or false; you don’t need to manually return each value in a ternary operator. So this could be slimmed down to $data['featuedArticle'] == '1'. Furthermore, if you pass a value of 0 by default, then you could just get rid of the check entirely. If in your Blade template you put a hidden input before your checkbox:
<input type="hidden" name="featuredArticle" value="0" />
<input type="checkbox" name="featuredArticle" value="1" />
Then 1 will be send if the checkbox is checked (as it overrides the hidden input’s value, or 0 sent if the checkbox isn’t checked).
Also, try to stick to Laravel conventions to make your life easier. If you use snake_case for your input names, then it just makes life easier matching them up to model attribute and table column names. So use featured_article, have an attribute in your model with the same name, which maps to a database column with the same name again. This allows you to do shorthand calls like create() (as per my controller example) and update().
Finally, methods like calculating reading time definitely belong on your model. Models represent something in your application. It therefore follows that you can do things with your models. Calculating the time to read an Article model instance therefore lends itself to having a calculateReadingTime() method on the Article model.
A bit long-winded, but hopefully there should be some helpful pointers for you in the above. I’ve been working on Laravel projects for around five years now and have found that this approach and conventions is what works best.
Your controller's store article is fine, because it fills your article instance based on request data. It could use some refactoring and you could encapsulate more logic into your Article (for example, assign slug field inside your Article model whenever title is changed and so on).
But the line $article->handleTags($request); is a suspect, because your model should never operate with requests - it will quickly polute your model code with very specialized dependencies that you don't want (what happens when you receive your tags from cache and don't have a request instance? What happens if other type of request contains tags differently? and so on). Your model shouldn't have knowledge about requests or other parts of your app. Your controller is connecting the dots between them, so make sure your handleTags takes some basic abstract types/structures as a parameter (for example, an array) and make sure your controller takes and transforms data from request accordingly before feeding it to your article.
As for your calculateReadTime dilemma, it should definitely be inside your model. Think about it this way - do you have everything you need to calculate read time of your article inside your Article model? The answer is yes, it's a property of an article object, doesn't matter if you store it in DB or calculate it off other properties. Make getReadTime method. You don't want a controller to compute something about your model because it will tie that logic to a specific place in your app which is bad (what happens when you need to calculate read time of an article in other controller? Other model? and so on).
Make sure you read about has and is concepts regarding object-oriented design, it will help you immensely.
I think you should move those assignments to a Service Class. You could also go ahead and create a repository class. This would thus become your code structure:
Controller -> Service -> Repository -> Model.
Doing this $article = new Article(); is bad. You will have a had time when writing a test for your controller store method.
I would suggest you do this:
Create a Service class, say ArticleService.php. Define a store method in it.
ArticleService.php
use Article;
class ArticleService {
protected $article;
public function __construct(Article $article){
$this->article = $article;
}
public function store(array $data){
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($data['featuredArticle'] == "1" ? true : false);
$IntranetOnly = ($data['IntranetOnly'] == "1" ? true : false);
$this->article->title = $data['title'];
$this->article->slug = str_slug($data['title']);
$this->article->author = $data['author'];
$this->article->category = $data['category'];
$this->article->excerpt = $data['excerpt'];
$this->article->content = clean($data['content']);
$this->article->featuredImage = $data['featuredImage'];
$this->article->featuredVideo = $data['featuredVideo'];
$this->article->readingTime = $data['reading_time'];
$this->article->featuredArticle = $isFeatured;
//Capital letter I? You should be consistent with your naming convention
$this->article->IntranetOnly = $IntranetOnly;
$this->article->published = $defauultPublished;
if($this->article->save()){
$this->article->handleTags($request);
return true;
}
return false;
}
}
And your Controller now becomes:
class ArticleController{
protected $articleService;
public function __construct(ArticleService $articleService){
$this->articleService = $articleService;
}
public function store(Request $request){
//Some Validation Logic
$readingTime = $this->calculateReadTime($request)
$data = array_merge(['reading_time' => $readTime], $request->all());
return $this->articleService->store($request->all());
}
}
I also see that you are not validating the incoming Request. You should always do that because you can/should never trust your users to always provide/input the right data. It is your duty to force them to do that. e.g I as your user might decide to enter my name in your email field. If you don't validate that data, you will end up with wrong data.
There is also the issue of individually assigning your request parameter to their corresponding Model attribute. I decided to leave it that way so as not to overload you with information.
In summary, just take a look at the following resources for more insight.
https://laravel.com/docs/5.1/quickstart-intermediate
https://laravel.com/docs/5.6/validation
In short, read up the whole Laravel documentation! Goodluck!

PHPActiveRecord validates_uniqueness_of not working

I am currently getting the following error in my User model.
Slim Application Error
The application could not run because of the following error:
Details
Type: ActiveRecord\UndefinedPropertyException
Message: Undefined property: User->Array in /var/www/public_html/devsite/vendor/php-activerecord/php-activerecord/lib/Model.php on line 521
File: /var/www/public_html/devsite/vendor/php-activerecord/php-activerecord/lib/Model.php
Line: 521
My model only crashes the program when I add the line below to it.
static $validates_uniqueness_of = array(
'username'
);
If I remove the line above then the program runs again just fine. So I know it has something to do with this.
According to the documentation this should indeed be the format.
(http://www.phpactiverecord.org/projects/main/wiki/Validations#validates_uniqueness_of)
Reference to the validate function from the library is below:
Line 563 --
https://github.com/jpfuentes2/php-activerecord/blob/master/lib/Validations.php
I'm using PHP Version 7.0.15-0 on ubuntu0.16.04.4
If this is truly a bug, does anyone have any workarounds?
Ok I created a complete solution now. Anyone who can improve this is welcome.
First create a separate file and call it uniquecheck.php.
Inside it put this trait code.
trait uniquecheck {
//#JA - This function can do single and multi-unique checks.
//#JA - This is programmed to be replaced at a later date when validates_uniqueness_of is fixed (http://www.phpactiverecord.org/projects/main/wiki/Validations#validates_uniqueness_of)
//#JA - EXAMPLES
//SINGLE -- array('name','message' => 'Can't do this')
//MULTIPLE -- array( array('name1','name2'), 'message' => 'can't do this and that together')
//#JA - To be clear multiple does not mean 2 different uniques but a unique on 2 columns. Just use this function twice for 2 separate unique checks.
//#JA - Refer to (https://github.com/jpfuentes2/php-activerecord/issues/336)
public function uniquecheck($rules = array()) {
//#JA - If its an array use the MULTIPLE method
$dirty = $this->dirty_attributes();//#JA - Get list of attributes that have been modified since loading the model
if(is_array($rules[0])){
//#JA - Generate first part of condition string
$uniques = $rules[0];
foreach($uniques as $unique){
$conditionstring .= "$unique = ? AND ";
}
$conditionstring = substr($conditionstring, 0, -5);
$dirtyfound = false;
//#JA - Then generate the array we will use for the conditions
$conditionarray['conditions'][] = $conditionstring;
foreach($uniques as $unique){
$conditionarray['conditions'][] = $this->read_attribute($unique);
if(array_key_exists($unique, $dirty)){
$dirtyfound = true;
}
}
if ($dirtyfound == true) { //#JA - If one of the parts that makes the record unique is dirty then...
try {
//#JA - Whatever the primary key currently is return the object for that. This will be the object reference for what is not modified
$currently = Self::find($this->id);
}
catch (Exception $e) {
$currently = false;
}
foreach($uniques as $unique){
if ((
(is_object($currently) && $currently->$unique != $this->$unique)
|| !is_object($currently)
) && static::exists($conditionarray))
$this->errors->add($unique, $rules['message']);
}
}
}else{ //#JA - Otherwise use the SINGLE method
$unique = $rules[0];
if (array_key_exists($unique, $dirty)) { //#JA - If the value we are checking to be unique has been modified...
try {
//#JA - Whatever the primary key currently is return the object for that. This will be the object reference for what is not modified
$currently = Self::find($this->id);
}
catch (Exception $e) {
$currently = false;
}
//#JA - The dirty attributes array simply contains fields that have been set by our code.
//#JA - Ergo if we have re-applied the same value to our model, it will be classed as dirty even though it has not changed
//#JA - If $currently was returned as an object type AND its original value does not equal the current dirty value of the property on the model
//#JA - OR If the object returned was not an object (meaning it does not currently exists in the database)...
//#JA - OR it could mean that the table is just empty for the first time... Thus
//#JA - AND if the dirty value of the unique was found to exist then a unique was found.
if ((
(is_object($currently) && $currently->$unique != $this->$unique)
|| !is_object($currently)
) && static::exists(array($unique => $this->$unique)))
$this->errors->add($unique, $rules['message']);
}
}
}
}
To use this in your model just use the statement 'use uniquecheck;' after including the php file with reference to the trait. For example...
require_once('traits/uniquecheck.php');//#JA - Helper to check if values are unique
class Client extends ActiveRecord\Model {
use uniquecheck;
public function validate() {
$this->uniquecheck(array(array('company_id','contactfirstname','contactlastname', 'contactphonenumber', 'contactaddress'),'message' => 'Can\'t have duplicate client.'));
}
}
The above shows an example of how to check a multiple unique. This will work for new records AND for editing records since the trait is smart to know which fields are dirty or not.
If you are not using a multi-unique it works just like this instead.
public function validate() {
$this->uniquecheck(array('username','message' => 'Username already in use'));
}
I copied the format that they use on PHPActiveRecords documentation so it should work exactly the same now.
Hope this helps someone else!
public static $validates_uniqueness_of = array(
'username'
)
shot in the dark:
static $validates_uniqueness_of = array(
array('username')
);

Message: SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters

I have come across this Error I've not seen before:
Message: SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters
Referring to the following code (have simplified the function for ease of reading):
if ($frm->isValid($this->_getAllParams()) || !count($frm->getMessages())) //error points to this line of an array
{
//set session with id of user
$session = new Zend_Session_Namespace('rg');
$session->userid = $this->getRequest()->getPost('id');
//update the user
$mdl->createClient($this->_getAllParams());
//add to log - do in model
$this->_redirect('/.../...');
}
6 C:\xampp\htdocs\portal-gep-2\library\Null\Validate\Db\NoRecordExists.php(7): Zend_Validate_Db_NoRecordExists->isValid('7505020152089')
class Null_Validate_Db_NoRecordExists extends Zend_Validate_Db_NoRecordExists
{
public function isValid($value)
{
$response = parent::isValid($value);//this line
if(!$response){
$this->_messages =
array(self::ERROR_RECORD_FOUND=> "Please correct this error before continuing <a href='/data-control/idnum/id/$value'>Correct Issue</a>");
}
return $response;
}
}
The form in question has a constructor of the form:
public function __construct($formState = 'createlocal', $currentTask = null)
based on whether the form is updating an existing record or a new record it will make use of a custom validator: Null_Validate_Db_NoRecordExists.
In the specific case you talk of the form is using a Db_NoRecordExists on an existing record and hence we whould like to exclude a specific record (the current one) using one of the constructor parameters in this case: $currentTask.
$currenctTask may be an array and the validator may make use of an array variable while ensuring the Record does not exist (in the where clause). However the variable is not parsed when initially constucting the form.
Hence check that $currentTask contains what it needs to contain. The error is of your own making.

PHP OOP filter validation on email array

I'm using a validation class by David Powers (from the book PHP Object Oriented Solutions) for filtering input data. Below is a function to filter a single email address. How do I modify this function to filter an array of email addresses? (I have a dynamic form which may contain a variable number of email addresses and some other data relating to it: both arrays are the same length and these filters should not remove values from the arrays, just to report if any of the values are invalid.)
/**
* Checks whether the input conforms to the format of an email address.
*
* It does not check whether the address is genuine. Multiple addresses are
* rejected, guarding against email header injection attacks.
*
* #param string $fieldName Name of submitted value to be checked.
*/
public function isEmail($fieldName)
{
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName] = FILTER_VALIDATE_EMAIL;
}
Here's the function for validating integers. What would it require to make this function accept an array of integers? (I commented out the min, max options from the function. They are not mandatory in this array validator. But feel free to help in one way or another...)
public function isInt($fieldName/*, $min = null, $max = null*/)
{
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName] = array('filter' => FILTER_VALIDATE_INT);
// Add filter options to test for integers within a specified range
//if (is_int($min)) {
// $this->_filterArgs[$fieldName]['options']['min_range'] = $min;
//}
//if (is_int($max)) {
// $this->_filterArgs[$fieldName]['options']['max_range'] = $max;
//}
}
These were two of the public methods of validation class. Please let me know if I should attach the class constructor or the public function which actually performs the validation. Right now I'm guessing you don't need to see them but my OOP skills are quite poor. Any help is very much appreciated.
UPDATED: added the constructor
/**
* Constructs a validator object for $_POST or $_GET input.
*
* The constructor checks the availability of the PHP filter functions for which it
* acts as a wrapper. If the optional first argument (an array of required fields) is
* supplied, it checks that each one contains at least some input. Any missing items are
* stored as an array in the $missing property.
*
* By default, the constructor sets the input type to "post". To create a validator for
* the $_GET array, set the optional second argument to "get".
*
* #param array $required Optional array containing the names of required fields or URL variables.
* #param string $inputType Type of input; valid options: "post" and "get" (case-insensitive); defaults to "post"
* #return Pos_Validator object
*/
public function __construct($required = array(), $inputType = 'post')
{
if (!function_exists('filter_list')) {
throw new Exception('The Pos_Validator class requires the Filter Functions in >= PHP 5.2 or PECL.');
}
if (!is_null($required) && !is_array($required)) {
throw new Exception('The names of required fields must be an array, even if only one field is required.');
}
$this->_required = $required;
$this->setInputType($inputType);
if ($this->_required) {
$this->checkRequired();
}
$this->_filterArgs = array();
$this->_errors = array();
$this->_booleans = array();
}
UPDATE 2: I realized that after filtering any invalid array values are labeled as bool(false). So the function below works but it doesn't generate validation error same way as the single value filter functions do.
public function isIntArray($fieldName)
{
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_INT;
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
//is is possible to mark the whole array to false or
//somehow break the whole script?
}
The function above strips away invalid values and database cells are filled with blanks if no other precaution are taken. I think I'll check the validated array (with foreach loop) after filtering and if any of the array values is false, I break the script somehow. (There's no point inserting empty values in the database is they suppose to be valid email addresses or integers.) This solution works for now but I bet there is a more elegant and efficient way to do this. Thank you for your help and suggestions. I'm still open for new ideas.
-Jouni
You could just call the isInt function multiple times and have the loop on the outside of the function. What does the constructor look like?
If you really want to do it you way you could change the function to look something like this:
public function isInt( $my_array ) {
foreach( $my_array as $element ) {
$this->checkDuplicateFilter( $element );
$this->_filterArgs[$element] = array('filter' => FILTER_VALIDATE_INT);
}
}
If you like, post the constructor on here and we can show you how to instantiate it and validate your inputs that way instead.
UPDATE (Now constructor has been posted)
What's the name of the class? Let's assume it's called Validator (change to what it is really called)
$validator = new Validator( /* Add your optional arguments here */ );
/* change $inputs to the name of the array containing all your inputs */
foreach( $inputs as $input ) {
// validate each input
if( $validator->isInt( $input ) ) {
// int is valid, do something
} else {
// int isn't valid, do something else
}
}
Then do something similar for isEmail for your e-mail inputs.
you can also use array_map.
From php.net:
<?php
function cube($n)
{
return($n * $n * $n);
}
$a = array(1, 2, 3, 4, 5);
$b = array_map("cube", $a);
print_r($b);
?>
Array
(
[0] => 1
[1] => 8
[2] => 27
[3] => 64
[4] => 125
)
So in your case, you'd use:
(Updated to use object)
array_map(array($this, $this->isEmail), $emailArray);
array_map(array($this, $this->isInt), $integerArray);
Fairly simple I think...
UPDATED, see below.
I think I have a acceptable solution for this so I just answer this little more precisely than in the comments. Here's the whole class but I made some modifications to it.
http://codepad.org/sCC0sruO
First I added a new protected value to the class: protected $_arrayHasFalse;
These two validating functions valide arrays of values. They mark the invalid values as false so a little more work has to be done.
public function isIntArray($fieldName) {
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_INT;
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
}
public function isEmailArray($fieldName){
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_EMAIL;
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
}
This function checks if any of the array values is invalid. It assigns the arrayHasFalse to true if everything is not valid. (This is a minor flaw as it does not remember all the invalid values. But I'm using it in a context where everything should validate or nothing is inserted into database so I am ok with this solution.)
public function checkArrayForFalseValues($array){
foreach($array as $key => $value){
if ($value == false){
$this->_arrayHasFalse = true;
}
}
}
Finally we need a getter function.
public function getArrayFalseRevealer(){
return $this->_arrayHasFalse;
}
So this is the deal in the form process file...
$val = new validator;
$val->isIntArray('AllShouldBeIntegers'); //dynamically expandable form field
$val->isEmailArray('AllShouldBeEmails'); //dynamically expandable form field
//these are in the original class (pretty self explanatory functions I think)
//validated input is stored in $filtered
$filtered = $val->validateInput();
$missing = $val->getMissing();
$errors = $val->getErrors();
//after the validation we go through a bit more work
$val->checkArrayForFalseValues($filtered['AllShouldBeIntegers']);
$val->checkArrayForFalseValues($filtered['AllShouldBeEmails']);
//mastervalue is set to true if anything is invalid
//maybe this function should be named: $doesAnyCheckedArrayHasEvenASingleInvalidValue
$arrayHasFalse = $val->getArrayFalseRevealer();
//proceed if everything is OK
if (!$missing && !$errors && !$arrayHasFalse) {// Everything passed validation.
//do the database magic here...
}
I guess this does the trick. I'll mark this as an accepted answer. Feel free to comment if you have suggestions for improvements.
Thanks everyone.
UPDATE: Improved the array validator. Let's pass the filter as an argument (or is it a parameter?). It is called for example like this: $val->isArrayAcceplable('AllShouldBeEmails', 'email');
public function isArrayAcceplable($fieldName, $filter){
//Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
//set the desired filter
//add more options for your convenience
switch($filter){
case 'email':
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_EMAIL;
break;
case 'int':
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_INT;
break;
}
//we are validating an array
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
}
//just remember to check the array for false values. (See above.)
-Jouni

Categories