Securely evaluate simple maths - php

I would like to know if there was a secure way to evaluate mathematics like
2+2
10000+12000
10000-20
2 + 2
40 - 20 + 23 - 12
Without having to use eval() because the input can come from any users. The things I'd need to implement are only additions and subtractions of whole numbers.
Is there any snippets that already exists for that, or any PHP functions I haven't come across?

I would question using eval, considering the variety of mathematic functions available in PHP. You've said you only want to do simple math -- the only reason to use eval is to perform more complex operations, or to accept the equations whole-cloth from the user.
If you just want to add or subtract, sanitize the input with intval and go to town:
$number1 = '100';
$number2 = 'shell_exec(\'rm -rf *\')';
echo intval($number1) + intval($number2); // 100
Try it: http://codepad.org/LSUDUw1M
This works because intval ignores anything non-numeric.
If you are indeed getting the whole equation from user input (ie 100 - 20), you can use preg_replace to remove anything except the allowed operators and numbers:
$input = '20 + 4; shell_exec(\'rm *\')';
$input = preg_replace(
'/[^0-9+-]/',
'',
$input
);
eval('$result = '.$input.';');
echo 'result: '.$result; // 24
Try it: http://codepad.org/tnISDPJ3
Here, we're using the regex /[^0-9+-]/, which matches anything NOT 0-9 OR + OR - and replaces it with an empty string.
If you want to get more in to depth with allowed equations, taken straight from the eval manual page:
// credit for code to bohwaz (http://www.php.net/manual/en/function.eval.php#107377)
$test = '2+3*pi';
// Remove whitespaces
$test = preg_replace('/\s+/', '', $test);
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functions = '(?:abs|a?cosh?|a?sinh?|a?tanh?|exp|log10|deg2rad|rad2deg|sqrt|ceil|floor|round)'; // Allowed PHP functions
$operators = '[+\/*^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?2))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $q))
{
$test = preg_replace('!pi|π!', 'pi()', $test); // Replace pi with pi function
eval('$result = '.$test.';');
}
else
{
$result = false;
}
Documentation
preg_replace - http://php.net/manual/en/function.preg-replace.php
intval - http://php.net/manual/en/function.intval.php
eval - http://php.net/manual/en/function.eval.php
PHP Math functions - http://www.php.net/manual/en/ref.math.php

You could parse the expressions yourself.
Something like this:
// Minus is the same as plus a negative
// Also remove spaces after minus signs
$str = preg_replace('/-\s*(\d+)/', '+-$1', $str);
// Split on plusses
$nums = explode('+', $str);
// Trim values
$nums = array_map('trim', $nums);
// Add 'em up
echo array_sum($nums);
DEMO: http://codepad.org/ANc0gh27

I used this method in calculator script.
$field1 = $_GET["field1"];
$field2 = $_GET["field2"];
$answer = $field1 + $field2;
echo "$field1 + $field2 = $answer";

Related

How to check characters alternatively and replace it with Y if it is X?

I have a string, something like this:
$str ="it is a test string.";
// for more clarification
i t i s a t e s t s t r i n g .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Now I need to check all characters that are multiples of 4 (plus first character). like these:
1 => i
4 => i
8 => [space]
12 => t
16 => r
20 => .
Now, I need to compare them with Y (Y is a variable (symbol), for example Y = 'r' in here). So I want to replace Y with X (X is a variable (symbol) too, for example X = 'm' in here).
So, I want this output:
it is a test stming.
Here is my solution: I can do that using some PHP function:
strlen($str): to count the number of characters (named $sum)
$sum / 4: To find characters that are multiples of 4
substr($str, 4,1): to select specific character (named $char) {the problem is here}
if ($char == 'r') {}: to compare
str_replace('r','m',$char): to replace
And then combining all $char to each other.
But my solution has two problem:
substr() does not count [space] character (As I mentioned above)
combining characters is complicated a bit. (It needs to some waste processing)
Well, is there any solution? I like to do that using REGEX, Is it possible?
Could just use a simple regex with callback (add u flag if utf-8, s for . to match newline).
$str = preg_replace_callback(['/^./', '/.{3}\K./'], function ($m) {
return $m[0] == "r" ? "m" : $m[0];
}, $str); echo $str;
See this demo at tio.run > it is a test stming.
1st pattern: ^. any first character
2nd pattern: \K resets after .{3} any three characters, only want to check the fourth .
For use with anonymous function PHP >= 5.3 is required. Here is the workaround (demo).
Update: #Mariano demonstrated in his very nice answer that it is even with a single regex replacement possible. Thank you for the benchmark that reveals a rather bad performance for the preg_replace_callback solution. A more efficient variant without callback (but still two patterns).
$str = preg_replace(['/^r/', '/(?:...[^r])*...\Kr/'], 'm', $str);
I also included #revo's answer from 2017 in Mariano's benchmark and ran it on tio.run (100k loops). With newer PHP and PCRE2 the numbers seem to have changed slightly, "no regex" leads at tio.run.
In .NET or modern browser JS regex it also could be done like this by a variable length lookbehind.
If all characters in your string are in single byte, you can use something from PHP's official language reference:
$str ="it is a test string.";
$y="r";
$x="m";
$len=strlen($str);
if($str[0]==$y)
{
$str=substr_replace($str,$x,0,1);
}
if($len>=3)
{
for($i=3;$i<$len;$i+=4)
{
if($str[$i]==$y)
{
$str=substr_replace($str,$x,$i,1);
}
}
}
var_dump($str);
3v4l demo
Outputs it is a test stming.
Edit:
As #Don'tPanic points out, String is mutable using [] operator, so instead of using
$str=substr_replace($str,$x,$i,1);
you can just use
$str[$i]=$x;
This is an alternative using preg_replace()
$y = 'r';
$y = preg_quote($y, '/');
$x = 'M';
$x = preg_quote($x, '/');
$subject = 'rrrrrr rrrrr rrrrrr rrrr rrrr.';
$regex = "/\\G(?:^|(?(?<!^.).)..(?:.{4})*?)\\K$y/s";
$result = preg_replace($regex, $x, $subject);
echo $result;
// => MrrMrr MrrrM rrMrrr rrrM rrMr.
ideone demo
Regex:
\G(?:^|(?(?<!^.).)..(?:.{4})*?)\Km
\G is an assertion to the end of last match (or start of string)
(?:^|(?(?<!^.).)..(?:.{4})*?) matches:
^ start of string, to check at position 1
(?(?<!^.).) is an if clause that yields:
..(?:.{4})*?) 2 chars + a multiple of 4 if it has just replaced at position 1
...(?:.{4})*?) 3 chars + a multiple of 4 for successive matches
\K resets the text matched to avoid using backreferences
I must say though, regex is an overkill for this task. This code is counterintuitive and a typical regex that proves difficult to understand/debug/maintain.
EDIT. There was a later discussion about performance vs. code readability, so I did a benchmark to compare:
RegEx with a callback (#bobblebubble's answer).
RegEx with 2 replacements in an array (#bobblebubble's suggestion in comment).
No RegEx with substr_replace (#Passerby's answer).
Pure RegEx (this answer).
Result:
Code #1(with_callback): 0.548 secs/50k loops
Code #2(regex_array): 0.158 secs/50k loops
Code #3(no_regex): 0.120 secs/50k loops
Code #4(pure_regex): 0.118 secs/50k loops
Benchmark in ideone.com
Try this
$str ="it is a test string.";
$y="r";
$x="m";
$splite_array = str_split($str);
foreach ($splite_array as $key => $val)
{
if($key % 4 == 0 && $val == $y)
{
$splite_array[$key] = $x;
}
}
$yout_new_string = implode($splite_array);
This piece of code could help you on your way:
// Define variables
$string = "it is a test string.";
$y = 'r';
$x = 'm';
// Convert string to array
$chars = explode('', $string);
// Loop through all characters
foreach ($chars as $key => $char) {
// Array keys start at 0, so we add 1
$keyCount = $key+1;
// Check if deviding the key by 4 doesn't have rest value
// This means it is devisable by 4
if ($keyCount % 4 == 0 && $value == $y) {
$chars[$key] = $x;
}
}
// Convert back to string
$string = implode($chars);
Here is one other way to do this using string access and modification by character. (Consequently, it is only useful for single-byte encoded strings.)
// First character handled outside the loop because its index doesn't match the pattern
if ($str[0] == $y) $str[0] = $x;
// access every fourth character
for ($i=3; isset($str[$i]) ; $i+=4) {
// change it if it needs to be changed
if ($str[$i] == $y) $str[$i] = $x;
}
This modifies the original string rather than creating a new string, so if that shouldn't happen, it should be used on a copy.
Late to the party, puting aside \G anchor, I'd go with (*SKIP)(*F) method:
$str = "it is a test string.";
echo preg_replace(['~\Ar~', '~.{3}\K(?>r|.(*SKIP)(?!))~'], 'm', $str);
Short and clean.
PHP live demo

php or javascript regex - return the first letter and the remaining string as 2 separate values

I have a code in php/mysql that gets ids from the database. The ids are in the form of a letter + a set of 3 digits. For example a123
I would like to be able to split the code to 2 variables $var1 = 'a' and $var2 = '123'
I thought of using php and regular expressions; looked at preg_match. I am not sure on how to get the first letter. I also thought about using explode in php and a limit or str_split like this:
$arr = str_split($str, 1);
but then this will split it into: a 1 2 3 4 and I'll end up with 4 values instead of 2.
Any suggestions please. (I am open to doing it in javascript as well I can pass it through ajax)
If it's always 4 characters:
$var1 = substr($str, 0, 1);
$var2 = substr($str, 1, 3);
If you really want a regex:
([a-zA-Z])([0-9]{3})
Using regexps is really not necessary in this case.
$letter = $id[0];
$number = substr($id, 1);
With JavaScript it's easy (no regex needed):
var str = 'a123';​​
var stra = str.substring(0,1); // returns 'a'
var strb = str.substr(1); // returns '123'

PHP - Parse mathematical equations inside strings

I'm struggling to find the best way to do this. Basically I am provided strings that are like this with the task of printing out the string with the math parsed.
Jack has a [0.8*100]% chance of passing the test. Katie has a [(0.25 + 0.1)*100]% chance.
The mathematical equations are always encapsulated by square brackets. Why I'm dealing with strings like this is a long story, but I'd really appreciate the help!
There are plenty of math evaluation libraries for PHP. A quick web search turns up this one.
Writing your own parser is also an option, and if it's just basic arithmetic it shouldn't be too difficult. With the resources out there, I'd stay away from this.
You could take a simpler approach and use eval. Be careful to sanitize your input first. On the eval docs's page, there are comments with code to do that. Here's one example:
Disclaimer: I know eval is just a misspelling of evil, and it's a horrible horrible thing, and all that. If used right, it has uses, though.
<?php
$test = '2+3*pi';
// Remove whitespaces
$test = preg_replace('/\s+/', '', $test);
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functions = '(?:sinh?|cosh?|tanh?|abs|acosh?|asinh?|atanh?|exp|log10|deg2rad|rad2deg|sqrt|ceil|floor|round)'; // Allowed PHP functions
$operators = '[+\/*\^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?2))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $q))
{
$test = preg_replace('!pi|π!', 'pi()', $test); // Replace pi with pi function
eval('$result = '.$test.';');
}
else
{
$result = false;
}
?>
preg_match_all('/\[(.*?)\]/', $string, $out);
foreach ($out[1] as $k => $v)
{
eval("\$result = $v;");
$string = str_replace($out[0][$k], $result, $string);
}
This code is highly dangerous if the strings are user inputs because it allows any arbitrary code to be executed
The eval approach updated from PHP doc examples.
<?php
function calc($equation)
{
// Remove whitespaces
$equation = preg_replace('/\s+/', '', $equation);
echo "$equation\n";
$number = '((?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?|pi|π)'; // What is a number
$functions = '(?:sinh?|cosh?|tanh?|acosh?|asinh?|atanh?|exp|log(10)?|deg2rad|rad2deg|sqrt|pow|abs|intval|ceil|floor|round|(mt_)?rand|gmp_fact)'; // Allowed PHP functions
$operators = '[\/*\^\+-,]'; // Allowed math operators
$regexp = '/^([+-]?('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; // Final regexp, heavily using recursive patterns
if (preg_match($regexp, $equation))
{
$equation = preg_replace('!pi|π!', 'pi()', $equation); // Replace pi with pi function
echo "$equation\n";
eval('$result = '.$equation.';');
}
else
{
$result = false;
}
return $result;
}
?>
Sounds, like your homework....but whatever.
You need to use string manipulation php has a lot of built in functions so your in luck. Check out the explode() function for sure and str_split().
Here is a full list of functions specifically related to strings: http://www.w3schools.com/php/php_ref_string.asp
Good Luck.

How to use str_replace() to remove text a certain number of times only in PHP?

I am trying to remove the word "John" a certain number of times from a string. I read on the php manual that str_replace excepts a 4th parameter called "count". So I figured that can be used to specify how many instances of the search should be removed. But that doesn't seem to be the case since the following:
$string = 'Hello John, how are you John. John are you happy with your life John?';
$numberOfInstances = 2;
echo str_replace('John', 'dude', $string, $numberOfInstances);
replaces all instances of the word "John" with "dude" instead of doing it just twice and leaving the other two Johns alone.
For my purposes it doesn't matter which order the replacement happens in, for example the first 2 instances can be replaced, or the last two or a combination, the order of the replacement doesn't matter.
So is there a way to use str_replace() in this way or is there another built in (non-regex) function that can achieve what I'm looking for?
As Artelius explains, the last parameter to str_replace() is set by the function. There's no parameter that allows you to limit the number of replacements.
Only preg_replace() features such a parameter:
echo preg_replace('/John/', 'dude', $string, $numberOfInstances);
That is as simple as it gets, and I suggest using it because its performance hit is way too tiny compared to the tedium of the following non-regex solution:
$len = strlen('John');
while ($numberOfInstances-- > 0 && ($pos = strpos($string, 'John')) !== false)
$string = substr_replace($string, 'dude', $pos, $len);
echo $string;
You can choose either solution though, both work as you intend.
You've misunderstood the wording of the manual.
If passed, this will be set to the number of replacements performed.
The parameter is passed by reference and its value is changed by the function to indicate how many times the string was found and replaced. Its initial value is discarded.
There are a few things you could do to achieve this, but I can't think of one specific php function that will easily let you do this.
One option is to create your own replace function and utilize strripos and substr to do the replaces.
Another thing you can do is use preg_replace_callback and count the number of replacements you have done in the callback.
There's probably more ways but that's all I can think of on the fly. If performance is an issue I suggest you give both a try and do some simple benchmarks.
The cleanest, most-direct, single function call is to use preg_replace(). Its replacement limiting parameter makes the task intuitive and readable.
$string = preg_replace('/John/', 'dude', $string, $numberOfInstances);
The function is also attractive because making the search case-insensitive is as simple as adding the i pattern modifier to the end of the pattern. I won't delve into the usefulness of word boundaries (\b).
If a search string might contain characters with special meaning to the regex engine, then preg_quote() will be necessary -- this diminishes the beauty of the technique but not prohibitively so.
$search = '$5.99';
$pattern = '/' . preg_quote($search, '/') . '/';
$string = preg_replace($pattern, 'free', $string, $numberOfInstances);
For anyone who has an unnatural bias against regex functions, this can be done without regex and without looping -- it will be case-sensitive though.
Limited Explode & Implode: (Demo)
$numberOfInstances = 2;
$string = 'Hello John, how are you John. John are you happy with your life John?';
// explode here -^^^^ and ---------^^^^ only to create the following array:
// 0 => 'Hello ',
// 1 => ', how are you ',
// 2 => '. John are you happy with your life John?'
echo implode('dude', explode('John', $string, $numberOfInstances + 1));
Output:
Hello dude, how are you dude. John are you happy with your life John?
Notice the explode's limiting parameter dictates how many elements are generated, not how many explosions are executed on the string.
function str_replace_occurrences($find, $replace, $string, $count = -1) {
// current occrurence
$current = 0;
// while any occurrence
while (($pos = strpos($string, $find)) != false) {
// update length of str (size of string is changing)
$len = strlen($find);
// found next one
$current++;
// check if we've reached our target
// -1 is used to replace all occurrence
if($current <= $count || $count == -1) {
// do replacement
$string = substr_replace($string, $replace, $pos, $len);
} else {
// we've reached our
break;
}
}
return $string;
}
Artelius has already described how the function works, ill just show you how to do this via the manual methods:
function str_replace_occurrences($find,$replace,$string,$count = 0)
{
if($count == 0)
{
return str_replace($find,$replace,$string);
}
$pos = 0;
$len = strlen($find);
while($pos < $count && false !== ($pos = strpos($string,$find,$pos)))
{
$string = substr_replace($string,$replace,$pos,$len);
}
return $string;
}
This is untested but should work.

How to get the last char of a string in PHP?

I need to get the last character of a string.
Say I have "testers" as input string and I want the result to be "s". how can I do that in PHP?
substr("testers", -1); // returns "s"
Or, for multibyte strings :
mb_substr("multibyte string…", -1); // returns "…"
substr($string, -1)
Or by direct string access:
$string[strlen($string)-1];
Note that this doesn't work for multibyte strings. If you need to work with multibyte string, consider using the mb_* string family of functions.
As of PHP 7.1.0 negative numeric indices are also supported, e.g just $string[-1];
From PHP 7.1 you can do this (Accepted rfc for negative string offsets):
<?php
$silly = 'Mary had a little lamb';
echo $silly[-20];
echo $silly{-6};
echo $silly[-3];
echo $silly[-15];
echo $silly[-13];
echo $silly[-1];
echo $silly[-4];
echo $silly{-10};
echo $silly[-4];
echo $silly[-8];
echo $silly{3}; // <-- this will be deprecated in PHP 7.4
die();
I'll let you guess the output.
Also, I added this to xenonite's performance code with these results:
substr() took 7.0334868431091seconds
array access took 2.3111131191254seconds
Direct string access (negative string offsets) took 1.7971360683441seconds
As of PHP 7.1.0, negative string offsets are also supported.
So, if you keep up with the times, you can access the last character in the string like this:
$str[-1]
DEMO
At the request of a #mickmackusa, I supplement my answer with possible ways of application:
<?php
$str='abcdef';
var_dump($str[-2]); // => string(1) "e"
$str[-3]='.';
var_dump($str); // => string(6) "abc.ef"
var_dump(isset($str[-4])); // => bool(true)
var_dump(isset($str[-10])); // => bool(false)
I can't leave comments, but in regard to FastTrack's answer, also remember that the line ending may be only single character. I would suggest
substr(trim($string), -1)
EDIT: My code below was edited by someone, making it not do what I indicated. I have restored my original code and changed the wording to make it more clear.
trim (or rtrim) will remove all whitespace, so if you do need to check for a space, tab, or other whitespace, manually replace the various line endings first:
$order = array("\r\n", "\n", "\r");
$string = str_replace($order, '', $string);
$lastchar = substr($string, -1);
I'd advise to go for Gordon's solution as it is more performant than substr():
<?php
$string = 'abcdef';
$repetitions = 10000000;
echo "\n\n";
echo "----------------------------------\n";
echo $repetitions . " repetitions...\n";
echo "----------------------------------\n";
echo "\n\n";
$start = microtime(true);
for($i=0; $i<$repetitions; $i++)
$x = substr($string, -1);
echo "substr() took " . (microtime(true) - $start) . "seconds\n";
$start = microtime(true);
for($i=0; $i<$repetitions; $i++)
$x = $string[strlen($string)-1];
echo "array access took " . (microtime(true) - $start) . "seconds\n";
die();
outputs something like
----------------------------------
10000000 repetitions...
----------------------------------
substr() took 2.0285921096802seconds
array access took 1.7474739551544seconds
As of PHP 8 you can now use str_ends_with()
$string = 'testers';
if (\str_ends_with($string, 's') {
// yes
}
Remember, if you have a string which was read as a line from a text file using the fgets() function, you need to use substr($string, -3, 1) so that you get the actual character and not part of the CRLF (Carriage Return Line Feed).
I don't think the person who asked the question needed this, but for me, I was having trouble getting that last character from a string from a text file so I'm sure others will come across similar problems.
You can find last character using php many ways like substr() and mb_substr().
If you’re using multibyte character encodings like UTF-8, use mb_substr instead of substr
Here i can show you both example:
<?php
echo substr("testers", -1);
echo mb_substr("testers", -1);
?>
LIVE DEMO
A string in different languages including C sharp and PHP is also considered an array of characters.
Knowing that in theory array operations should be faster than string ones you could do,
$foo = "bar";
$lastChar = strlen($foo) -1;
echo $foo[$lastChar];
$firstChar = 0;
echo $foo[$firstChar];
However, standard array functions like
count();
will not work on a string.
Use substr() with a negative number for the 2nd argument.$newstring = substr($string1, -1);
Siemano, get only php files from selected directory:
$dir = '/home/zetdoa/ftp/domeny/MY_DOMAIN/projekty/project';
$files = scandir($dir, 1);
foreach($files as $file){
$n = substr($file, -3);
if($n == 'php'){
echo $file.'<br />';
}
}

Categories