PHP way to execute SQL LIKE matching without a database query? - php

I want to match an input string to my PHP page the same way a match done by the LIKE command in SQL (MySQL) for consistency of other searches. Since (I have seen but don't comprehend) some of the PHP syntax includes SQL commands I am wondering if this is possible?
The reason for this is I am now implementing a search of a keyword versus fields in the DB that are stored in a serialized array, which I have to unserialize in PHP and search depending on the structure of the array. I can't query against the table, just need the matching ability of the query. Otherwise I need to find an alternate matching routine, which won't be consistent. I can't go back and re-structure the DB since this wasn't anticipated wayyy back in the spec. Yes, I need an ugly hack, but am looking for the most elegant.
If it's not possible I could use any recommendation of matching user typed text as a keyword against stored text.
EDIT (for clarification): my main problem is I don't have a thorough grasp on how the LIKE command works (just copying the code) and as the keyword implies some degree of vagueness, I would like that vagueness preserved if I switch to a regular expression. I am better with regex's just not so good with like. My query is "LIKE 'matchme%'"

Update
Based on tomalak's comment and OIS's brilliant idea to use preg_grep, this might be something more along the lines of a final solution for you.
<?php
function convertLikeToRegex( $command )
{
return "/^" . str_replace( '%', '(.*?)', preg_quote( $command ) ) . "$/s";
}
function selectLikeMatches( $haystack, $needle )
{
return preg_grep( convertLikeToRegex( $needle ), $haystack );
}
$likeClauses = array(
'%foo'
,'foo%'
,'%foo%'
);
$testInput = array(
'foobar'
,'barfoo'
,'barfoobaz'
);
foreach ( $likeClauses as $clause )
{
echo "Testing $clause:";
echo '<pre>';
print_r( selectLikeMatches( $testInput, $clause ) );
echo '</pre>';
}
Original Post Below
Is this along the lines of what you're after?
<?php
function convertLikeToRegex( $command )
{
return "/^" . str_replace( '%', '(.*?)', $command ) . "$/s";
}
$likeClauses = array(
'%foo'
,'foo%'
,'%foo%'
);
$testInput = array(
'foobar'
,'barfoo'
,'barfoobaz'
);
foreach ( $testInput as $test )
{
foreach ( $likeClauses as $clause )
{
echo "Testing '$test' against like('$clause'): ";
if ( preg_match( convertLikeToRegex( $clause ), $test ) )
{
echo 'Matched!';
} else {
echo 'Not Matched!';
}
echo '<br>';
}
echo '<hr>';
}

What you need is preg_grep actually.
$arr = array("tstet", "duh", "str");
$res = preg_grep("#st#i", $arr); //i for case insensitive
var_dump($res);
results in
array(2) {
[0]=>
string(5) "tstet"
[2]=>
string(3) "str"
}
edit:
the user supplies the text, I add the
wildcards behind the scenes. I do use
one %. LIKE 'text%'
here is how you specify it in regex
"#st#i" regex is the same as in sql "%st%"
"#^st#i" regex is the same as in sql "st%"
"#st$#i" regex is the same as in sql "%st"
Also, remember to use preg_quote on any text you get from a third party.
$regex = "#" . preg_quote($text) . "#i";
$res = preg_grep($regex, $arr);

I'd think you'd need preg_match but that's not exactly the same behavior as a LIKE.
<?php // The "i" after the pattern delimiter indicates a case-insensitive search
if (preg_match("/php/i", "PHP is the web scripting language of choice.")) {
echo "A match was found.";
} else {
echo "A match was not found."; }
?>

Do you mean you want to be able to check if the input string is LIKE var% ?
You could use strpos(haystack, needle) to match %var%.
if( strpos($source, "var") == 0 ) echo "matches var%";
if( strlen($source) - (strpos($source, "var")) == strlen("var") ) echo "matches %var";
That is pretty ugly. And actually probably not the most elegant.

Related

preg_match_all Output with Multiple Possibilities

I am searching a line using preg_match_all, but it is not known exactly what this line will look like. For example, it could look like this:
XXX012-013-015-######
Or it could look like this:
XXX012-013-015-XXX001-002-######
Where the 'X's are any letter and the '#'s are any number.
This is the relevant portion of the preg_match_all code that works exactly as expected if the line was always setup like the first example:
if (preg_match_all('([A-Z]{3})((?:[0-9]{3}[->]{1}){1,32})([0-9]{2})([0-9]{2})([0-9]{2})...rest of code...#', $wwalist, $matches)) {
$wwaInfo['locationabbrev'][$wwanum] = $matches[2][$keys[$wwanum]];
}
The $matches[2] will display "012-013-015" as expected. Since the first part, xxx012-013-015, can repeat, I need for the preg_match_all $matches[2] to display the following if it is run on the second example:
012-013-015-001-002
This was my attempt, but it does not work:
if (preg_match_all('#([A-Z]{3})((?:[0-9]{3}[->]{1}){1,32})((?:[A-Z]{3}){0,1})(?:((?:[0-9]{3}[->]{1}){1,3}){0,3})([0-9]{2})([0-9]{2})([0-9]{2})...rest of code...#', $wwalist, $matches)) {
Hopefully this makes sense. Any help would be much appreciated! Thanks!
You aren't going to be able to match and join matches in the same step.
Will this work for you:
Code: (Pattern Demo) (PHP Demo)
$strings=[
'ABC012-013-015-XYZ001-002-345435',
'ABC012-013-015-345453',
'XYZ013-014-015-016-EFG017-123456'
];
foreach($strings as $s){
if(preg_match('/[A-Z]{3}\d{3}/',$s)){ // check if string qualifies
echo 'Match found. Prepared string: ';
$s=preg_replace('/([A-Z]{3}|-\d{6})/','',$s); // remove unwanted substrings
echo "$s\n";
}
}
Output:
Match found. Prepared string: 012-013-015-001-002
Match found. Prepared string: 012-013-015
Match found. Prepared string: 013-014-015-016-017
You could use a replace call and then output a new string with the matches, so for example:
ABC012-013-015-XYZ001-002-345435
ABC012-013-015-345453
XYZ013-014-015-016-EFG017-123456
$rep = preg_replace( '/(?mi-Us)([^0-9-\n]{3,})|-[0-9]{4,}/', '', $str) ;
echo ( $rep );
Should result in:
012-013-015-001-002
012-013-015
013-014-015-016-017
To output to an array:
$mat = preg_match_all( '/([0-9-]+)\n/', $rep, $res) ;
print_r( $res[1] ) ;
foreach( $res[1] as $result ) {
echo $result . "\n" ;
}
For the code you've shown you could probably do:
$rep = preg_replace( '/(?mi-Us)([^0-9-\n]{3,})|-[0-9]{4,}/', '', $wwalist ) ;
if (preg_match_all('/([0-9-]+)\n/', $rep, $matches)) {
$wwaInfo['locationabbrev'][$wwanum] = $matches[1][$keys[$wwanum]];
print_r( $wwaInfo['locationabbrev'][$wwanum] ); // comment out when done testing
}
Which should return the array:
Array
(
[0] => 012-013-015-001-002
[1] => 012-013-015
[2] => 013-014-015-016-017
)

Concatenate variables in a regular expression string with preg_match()

I'm using preg_match() function which determines whether a function is executed.
if( preg_match( '/^([0-9]+,){0,3}[0-9]+$/', $string ) ) { ... }
However I have to use a integer variable and integrate it in the regular expression:
$integer = 4;
if( preg_match( '/^([0-9]+,){0,' . $integer . '}[0-9]+$/', $string ) ) { ... }
but it doesn't match when it should. How is it that I can't concatenate a variable in the regex string?
Edit:
strval($integer) has solved my problem. I had to convert the integer value into a string before concatenating it (although I don't understand why):
$integer = 4;
if( preg_match( '/^([0-9]+,){0,' . strval($integer) . '}[0-9]+$/', $string ) ) { ... }
Whenever concatenating a variable into a regex pattern, you should do so by passing the variable to the preg_quote function.
However, if the variable var is, like it is in your example 4, that won't make any difference. The pattern you're using will be:
/^([0-9]+,){0,4}[0-9]+$/
In which case, if it doesn't work: check the $string value, and make sure the pattern matches. BTW, /^(\d+,){,4}\d+$/ is shorter and does the same thing.
Calling strval doesn't solve anything, AFAIK... I've tested the code without strval, using the following snippet:
$string = '1234,32';
if (preg_match( '/^([0-9]+,){0,4}[0-9]+$/', $string) )
{
echo 'matches',PHP_EOL;
$count = 4;
if (preg_match( '/^([0-9]+,){0,'.$count.'}[0-9]+$/', $string ) )
echo 'matches, too',PHP_EOL;
}
The output was, as I expected:
matches
matches, too
In your case, I'd simply write:
$count = 4;
preg_match('/^(\d+,){,'.preg_quote($count, '/').'}\d+$/', $string);
This is undeniably safer than just calling strval, because you're not accounting for possible special regex chars (+[]{}/\$^?!:<=*. and the like)

Find exact string inside a string

I have two strings "Mures" and "Maramures". How can I build a search function that when someone searches for Mures it will return him only the posts that contain the "Mures" word and not the one that contain the "Maramures" word. I tried strstr until now but it does now work.
You can do this with regex, and surrounding the word with \b word boundary
preg_match("~\bMures\b~",$string)
example:
$string = 'Maramures';
if ( preg_match("~\bMures\b~",$string) )
echo "matched";
else
echo "no match";
Use preg_match function
if (preg_match("/\bMures\b/i", $string)) {
echo "OK.";
} else {
echo "KO.";
}
How do you check the result of strstr? Try this here:
$string = 'Maramures';
$search = 'Mures';
$contains = strstr(strtolower($string), strtolower($search)) !== false;
Maybe it's a dumb solution and there's a better one. But you can add spaces to the source and destination strings at the start and finish of the strings and then search for " Mures ". Easy to implement and no need to use any other functions :)
You can do various things:
search for ' Mures ' (spaces around)
search case sensitive (so 'mures' will be found in 'Maramures' but 'Mures' won't)
use a regular expression to search in the string ( 'word boundary + Mures + word boundary') -- have a look at this too: Php find string with regex
function containsString($needle, $tag_array){
foreach($tag_array as $tag){
if(strpos($tag,$needle) !== False){
echo $tag . " contains the string " . $needle . "<br />";
} else {
echo $tag . " does not contain the string " . $needle;
}
}
}
$tag_array = ['Mures','Maramures'];
$needle = 'Mures';
containsString($needle, $tag_array);
A function like this would work... Might not be as sexy as preg_match though.
The very simple way should be similar to this.
$stirng = 'Mures';
if (preg_match("/$string/", $text)) {
// Matched
} else {
// Not matched
}

preg_match(_all) with a rule for every space character

I am trying to create a way of making sure that every space has at least three characters (a-zA-Z and single quotes are allowed) on each side of it. It does exactly that, however only with the first space. Not the rest of them. I tried preg_match_all() to no avail, hence my question/post to you guys.
<?PHP
function validateSpaces($str) {
if ( strpos( $str, ' ' ) !== FALSE && !preg_match( '/(([a-z\']{3,})([ ]{1})([a-z\']{3,}))/i', $str ) )
return FALSE;
return TRUE;
}
echo validateSpaces( 'Hey There' ); // Valid (correct)
echo validateSpaces( 'He There' ); // Invalid (correct)
echo validateSpaces( 'Hey Ther e' ); // Valid (incorrect)
?>
As you can see, the first two examples are working like they should, but the second one validates although the second space only has one character on the right side of it. Which is not what I want.
Any help or attempt to help is greatly appreciated!
Thank you in advance,
Chris.
Last modification, will macth only if we have ony one space (trim string before trying to match it):
^([a-z']{3,} ?)+$
You could explode the string on spaces and check the array's contents.
Not complete solution:
$temb=explode(' ', $str);
$valid=true;
foreach ($temb as $tt) {
if (strlen($tt)<3 || !{a preg_match for the right characters in $tt}) {
$valid=false;
break;
}
}
Use preg_match_all() rather than preg_match(), and compare the number of results with a substr_count($str,' ') to see that every space matches your regexp criteria
How about this - you could combine the patterns into one if performance is an issue; I find more than one level of conditional logic difficult to read:
function validate( $value )
{
$ptn_a = '/(^| )[A-Za-z\']{0,2} /';
$ptn_b = '/ [A-Za-z\']{0,2}($| )/';
if ( preg_match( $ptn_a, $value ) )
return false;
if ( preg_match( $ptn_b, $value ) )
return false;
return true;
}
var_dump( validate('Hey There') );
var_dump( validate('He There') );
var_dump( validate('Hey Ther e') );
function validate($string = '')
{
$regexp = '/([^\w]|^)([\w]{1,2})([^\w]|$)/';
if (strlen(trim($string)) && !preg_match($regexp, $string)) {
return 'TRUE';
}
return 'FALSE';
}
print validate(' ');
print "\n";
print validate('Hey There');
print "\n";
print validate('He There');
print "\n";
print validate('Hey ');
print "\n";
print validate('Hey Ther e');
print "\n";
print validate('Hey Th ere');
This can also help.

preg_replace all characters up to a certain one

I have a string
&168491968426|mobile|3|100|1&185601651932|mobile|3|120|1&114192088691|mobile|3|555|5&
and i have to delete, say, this part &185601651932|mobile|3|120|1& (starting with amp and ending with amp) knowing only the first number up to vertical line (185601651932)
so that in result i would have
&168491968426|mobile|3|100|1&114192088691|mobile|3|555|5&
How could i do that with PHP preg_replace function. The number of line (|) separated values would be always the same, but still, id like to have a flexible pattern, not depending on the number of lines in between the & sign.
Thanks.
P.S. Also, I would be greatful for a link to a good simply written resource relating regular expressions in php. There are plenty of them in google :) but maybe you happen to have a really great link
preg_replace("/&185601651932\\|[^&]+&/", ...)
Generalized,
$i = 185601651932;
preg_replace("/&$i\\|[^&]+&/", ...);
if you want real flexibility, use preg_replace_callback. http://php.net/manual/en/function.preg-replace-callback.php
Important: don't forget to escape your number using preg_quote():
$string = '&168491968426|mobile|3|100|1&185601651932|mobile|3|120|1&114192088691|mobile|3|555|5&';
$number = 185601651932;
if (preg_match('/&' . preg_quote($number, '/') . '.*?&/', $string, $matches)) {
// $matches[0] contains the captured string
}
It seems to me you ought to be using another data structure than a string to manipulate this data.
I'd want this data in a structure like
Array(
[id] => Array(
[field_1] => value_1
[field_2] => value_2
)
)
Your massive string can be massaged into such a structure by doing something like this:
$data_str = '168491968426|mobile|3|100|1&185601651932|mobile|3|120|1&114192088691|mobile|3|555|5&';
$remove_num = '185601651932';
/* Enter a descriptive name for each of the numbers here
- these will be field names in the data structure */
$field_names = array(
'number',
'phone_type',
'some_num1',
'some_num2',
'some_num3'
);
/* split the string into its parts, and place them into the $data array */
$data = array();
$tmp = explode('&', trim($data_str, '&'));
foreach($tmp as $record) {
$fields = explode('|', trim($record, '|'));
$data[$fields[0]] = array_combine($field_names, $fields);
}
echo "<h2>Data structure:</h2><pre>"; print_r($data); echo "</pre>\n";
/* Now to remove our number */
unset($data[$remove_num]);
echo "<h2>Data after removal:</h2><pre>"; print_r($data); echo "</pre>\n";

Categories