PHP Number Cleaner RegEx - php

I have a function I use in PHP to work with numbers. The intent is to clean the number and, optionally, convert nulls to zero. It began for me for use in prep for sql, but is now used in more places. Here it is:
function clean_num ($num, $null_to_zero = true) {
$num = preg_replace("/[^-0-9.0-9$]/","",$num);
if (strlen($num) == 0)
$num = ($null_to_zero) ? 0 : null;
else if (strlen($num) == 1 && ($num == '-' || $num == '.'))
$num = ($null_to_zero) ? 0 : null;
return $num;
}
Does anyone have any ideas on a faster, better way of doing this? It works, the regex is simple enough and should cover all cases I need, but... A diff regex might do all the same without other junk. Regex is not my strength. Thanks!

The regex [^-0-9.0-9$] matches any char that is
not a hyphen
not a digit
not a .
not a $
there is no need to have two 0-9 in the char class, so effectively your regex is: [^-0-9.$] or [^-\d.$]

Related

preg_match admits only two consecutive lowercases

I want to check if password contains:
minimum 2 lower cases
minimum 1 upper case
minimum 2 selected special characters
The problem is that when i want to verify this,it admits two lowercases,but only if they are consecutive,like this:paSWORD .
if I enter pASWORd,it returns an error.
This is the code
preg_match("/^(?=.*[a-z]{2})(?=.*[A-Z])(?=.*[_|!|#|#|$|%|^|&|*]{2}).+$/")
I don't see where the problem is and how to fix it.
You're looking for [a-z]{2} in your regex. That is two consecutive lowercases!
I will go out on a limb and suggest that it is probably better to individually check each of your three conditions in separate regexes rather than trying to be clever and do it in one.
I've put some extra braces in which may get your original idea to work for non-consecutive lowercase/special chars, but I think the expression is overcomplex.
preg_match("/^(?=(.*[a-z]){2})(?=.*[A-Z])(?=(.*[_!##$%^&*]){2}).+$/")
You can use this pattern to check the three rules:
preg_match("/(?=.*[a-z].*[a-z])(?=.*[A-Z])(?=.*[_!##$%^&*].*[_!##$%^&*])/");
but if you want to allow only letters and these special characters, you must add:
preg_match("/^(?=.*[a-z].*[a-z])(?=.*[A-Z])(?=.*[_!##$%^&*].*[_!##$%^&*])[a-zA-Z_!##%^&*]+$/");
a way without regex
$str = '*MauriceAimeLeJambon*';
$chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_!##$%^&*';
$state = array('lower' => 2, 'upper' => 1, 'special' => 2);
$strlength = strlen($str);
for ($i=0; $i<$strlength; $i++) {
$pos = strpos($chars, $str[$i]);
if (is_numeric($pos)) {
if ($state['lower'] && $pos<26) $state['lower']--;
elseif ($state['upper'] && $pos<52) $state['upper']--;
elseif ($state['special']) $state['special']--;
} else { $res = false; break; }
$res = !$state['lower'] && !$state['upper'] && !$state['special'];
}
var_dump($res);
(This version give the same result than the second pattern. If you want the same result than the first pattern, just remove the else {} and put the last line out of the for loop.)

Password Validation With Multiple Rules

I'm attempting to write a regex in PHP that validates the following:
At least 10 chars
Has at least 2 Upper-case characters
Has at least 2 Numbers OR Symbols
I've looked at just about every reference I can find but, to no avail.
I guess I can test individually, but that makes me very sad :(
Can someone please help? (And send me to a spot where I can learn in plain English Reg Ex?)
This picture is worth more than 1000 words
(and that's a lot of entropy)
(image via XKCD)
With this in mind you might want to consider dropping rules 2 & 3 if password length is higher than X (say.. 20) or increase the minimum to at least 16 characters (as the only rule).
As for your requirement:
As opposed to having one big, ugly, hard-to-maintain, advanced RegExp you might want to break the problem in smaller parts and tackle each bit separately using dedicated functions.
For this you could look at ctype_* functions, count_chars() and MultiByte String Functions.
Now the ugly:
This advanced RegEx will return true or false according to your rules:
preg_match('/^(?=.{10,}$)(?=.*?[A-Z].*?[A-Z])(?=.*?([\x20-\x40\x5b-\x60\x7b-\x7e\x80-\xbf]).*?(?1).*?$).*$/',$string);
Test demo here: http://regex101.com/r/qE9eB2
1st part (LookAhead) : (?=.{10,}$) will check string length and continue if it has at least 10 characters. You could drop this and do a check with strlen() or even better mb_strlen().
2nd part (also a LookAhead): (?=.*?[A-Z].*?[A-Z]) will check for the presence of 2 UPPERCASE characters. You could also do a $upper=preg_replace('/[^A-Z]/','',$string) instead and count the chars in $upper to be more than two.
3rd LookAhead uses a character class: [\x20-\x40\x5b-\x60\x7b-\x7e\x80-\xbf] with hex escaped character ranges for common symbols (pretty much all the symbols one could find on an average keyboard). You could also do a $sym=preg_replace('/[^a-zA-Z]/','',$string) instead and count the chars in $sym to be more than two. Note: to make it shorter I used a recursive group (?1) to not repeat the same character class again
For learning, the most comprehensive RegExp reference I know of is: regular-expressions.info
You can use lookaheads to make sure that what you are looking for is contained appropriately.
/(?=.*[A-Z].*[A-Z])(?=.*[^a-zA-Z].*[^a-zA-Z]).{10,}/
I have always preferred good old procedural code for handling stuff like this. Regular expressions can be useful but they can also be a little cumbersome, especially for code maintenance and quick scanning (regular expressions are not exactly examples of readability).
function strContains($string, $contains, $n = 1, $exact = false) {
$length = strlen($string);
$tally = 0;
for ($i = 0; $i < $length; $i++) {
if (strpos($contains, $string[$i]) !== false) {
$tally++;
}
}
return ($exact ? $tally == $n : $tally >= $n);
}
function validPassword($password) {
if (strlen($password) < 10) {
return false;
}
$upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$upperCount = 2;
if (strContains($password, $upperChars, $upperCount) === false) {
return false;
}
$numSymChars = '0123456789!"#$%&\'()*+,-./:;<=>?#[\\]^_`{|}~';
$numSymCount = 2;
if (strContains($password, $numSymChars, $numSymCount) === false) {
return false;
}
return true;
}

Simple? Logic flaw with my function

I have a problem, it'll probably become apparent as you read my function, but I can't figure out what to do.
The issue is, I need to use "do, while", because I need the result of "do", to test in "while". The problem is, when the while gets all 4 conditions returning false, it quits, but that leaves me with a bad code.
I need to regenerate a code until it contains no indistinguishable characters.
function make_code(){
do{
$prefix = mt_rand(0, mt_getrandmax());
$code = uniqid($prefix);//good to make sure we always have a unique string to work with, even with no seed supplied.
$sha1 = sha1($code);
$base_convert = base_convert($sha1, 16, 36);//expand hex with the rest of the alphabet.
$substr = substr($base_convert, 0, 12);//we only want the first 12 characters.
$strtoupper = strtoupper($substr);//for aesthetics.
$str_split = str_split($strtoupper, 4);//seperate into chunks.
$voucher_code = $str_split[0] . self::CS . $str_split[1] . self::CS . $str_split[2];//build
}
while(
(stristr($voucher_code, "o") === false)
&& (stristr($voucher_code, "0") === false)
&& (stristr($voucher_code, "1") === false)
&& (stristr($voucher_code, "i") === false));
return $voucher_code;
}
}
Thanks for any help offered.
Wouldn't it be easier to just present this code in a font that DOES make those characters distinguishable? That being said, simply use a regex to "simplify" your multiple string matches:
do {
...
while (preg_match('/[01lo]/i', $voucher_code));
Eliminating those characters from usage just makes it that much more likely you'll end up with a duplicate voucher.

Split a long string not using space

If I have sentences like this:
$msg = "hello how are you?are you fine?thanks.."
and I wish to seperate it into 3 (or whatever number).
So I'm doing this:
$msglen = strlen($msg);
$seperate = ($msglen /3);
$a = 0;
for($i=0;$i<3;$i++)
{
$seperate = substr($msg,$a,$seperate)
$a = $a + $seperate;
}
So the output should be..
hello how are
[a space here->] you?are you [<-a space here]
fine?thanks..
So is it possible to separate at middle of any word instead of having a space in front or end of the separated message?
Such as "thank you" -> "than" and "k you" instead of "thank" " you ".
Because I'm doing a convert function and with a space in front or end it will effect the convertion , and the space is needed for the conversion,so I can't ignore or delete it.
Thanks.
I take it you can't use trim because the message formed by the joined up strings must be unchanged?
That could get complicated. You could make something that tests for a space after the split, and if a space is detected, makes the split one character earlier. Fairly easy, but what if you have two spaces together? Or a single lettered word? You can of course recursively test this way, but then you may end up with split strings of lengths that are very different from each other.
You need to properly define the constraints you want this to function within.
Please state exactly what you want to do - do you want each section to be equal? Is the splitting in between words of a higher priority than this, so that the lengths do not matter much?
EDIT:
Then, if you aren't worried about the length, you could do something like this [starting with Eriks code and proceeding to change the lengths by moving around the spaces:
$msg = "hello how are you?are you fine?thanks..";
$parts = split_without_spaces ($msg, 3);
function split_without_spaces ($msg, $parts) {
$parts = str_split(trim($msg), ceil(strlen($msg)/$parts));
/* Used trim above to make sure that there are no spaces at the start
and end of the message, we can't do anything about those spaces */
// Looping to (count($parts) - 1) becaause the last part will not need manipulation
for ($i = 0; $i < (count($parts) - 1) ; $i++ ) {
$k = $i + 1;
// Checking the last character of the split part and the first of the next part for a space
if (substr($parts[$i], -1) == ' ' || $parts[$k][0] == ' ') {
// If we move characters from the first part to the next:
$num1 = 1;
$len1 = strlen($parts[$i]);
// Searching for the last two consecutive non-space characters
while ($parts[$i][$len1 - $num1] == ' ' || $parts[$i][$len1 - $num1 - 1] == ' ') {
$num1++;
if ($len1 - $num1 - 2 < 0) return false;
}
// If we move characters from the next part to the first:
$num2 = 1;
$len2 = strlen($parts[$k]);
// Searching for the first two consecutive non-space characters
while ($parts[$k][$num2 - 1] == ' ' || $parts[$k][$num2] == ' ') {
$num2++;
if ($num2 >= $len2 - 1) return false;
}
// Compare to see what we can do to move the lowest no of characters
if ($num1 > $num2) {
$parts[$i] .= substr($parts[$k], 0, $num2);
$parts[$k] = substr($parts[$k], -1 * ($len2 - $num2));
}
else {
$parts[$k] = substr($parts[$i], -1 * ($num1)) . $parts[$k];
$parts[$i] = substr($parts[$i], 0, $len1 - $num1);
}
}
}
return ($parts);
}
This takes care of multiple spaces and single lettered characters - however if they exist, the lengths of the parts may be very uneven. It could get messed up in extreme cases - if you have a string made up on mainly spaces, it could return one part as being empty, or return false if it can't manage the split at all. Please test it out thoroughly.
EDIT2:
By the way, it'd be far better for you to change your approach in some way :) I seriously doubt you'd actually have to use a function like this in practice. Well.. I hope you do actually have a solid reason to, it was somewhat fun coming up with it.
If you simply want to eliminate leading and trailing spaces, consider trim to be used on each result of your split.
If you want to split the string into exact thirds it is not known where the cut will be, maybe in a word, maybe between words.
Your code can be simplified to:
$msg = "hello how are you?are you fine?thanks..";
$parts = str_split($msg, ceil(strlen($msg)/3));
Note that ceil() is needed, otherwise you might get 4 elements out because of rounding.
You're probably looking for str_split, chunk_split or wordwrap.

PHP validation question

How do I check and see if a user enters only numbers and is at least 4 numbers long using PHP?
Mark Byers' suggestion is good, but here's another way:
$valid = ctype_digit($number) && strlen($number) >= 4;
You could use a regular expression:
/^\d{4,}$/
Example usage:
$s = "7325";
if (preg_match('/^\d{4,}$/', $s)) {
echo "matches";
}
ctype_digit() && strlen() wins
<?php
function benchmark($callback){
echo sprintf('%-30s: ', $callback);
$t = microtime(true);
foreach(range(1, 10000) as $n){
call_user_func($callback);
}
echo (microtime(true)-$t)."\n";
}
function mark_byers_preg_match(){
$s = "7325";
preg_match('/^\d{4,}$/', $s);
}
function notjim_ctype_digit_strlen(){
$number = 7325;
ctype_digit($number) && strlen($number) >= 4;
}
function tomalak_intval_broken(){
$check = 7325;
intval($check) == $check && $check >= 1000 && $check <= 9999;
}
benchmark('mark_byers_preg_match');
benchmark('notjim_ctype_digit_strlen');
benchmark('tomalak_intval_broken');
?>
results
mark_byers_preg_match : 0.029040098190308
notjim_ctype_digit_strlen : 0.026585817337036
tomalak_intval_broken : 0.019872903823853
Note: #Tomalak's does not work with numbers starting with 0 so it does not qualify
Edit: #kiethjgrant's solution was removed because intval(0000) evaluates as false when it should be true.
Do you have any example code to start with?
To strictly answer your question, you could use a regex like if(preg_match('/^\d{4,}$/', $input)....
But there's a lot more to consider here: you need to consider both validation and filtering (and you're best to keep the two separate issues). If you're strictly checking for an integer, then I suppose you're safe from SQL injection, XSS, etc., but you really need to have a handle on those issues, because sooner or later you're going to need to filter & validate something other than a simple integer.
you should always use the most efficient way to do it
if ( is_numeric($imput) && isset($input[3]) )
{
// your code
}
isset() is a language construct, which is always faster than strlen().
isset($input[n-1]) tells you whether string(data which passes through form is always string) has at least n long.
is_numeric() checks it is a valid num string.
i think it is better than ctype_digit() && strlen().

Categories