I am building a module for my own PHP framework, so my question is very specific and special. It's difficult to explain my question so I will go ahead and show it on code below.
I have a little piece of PHP in a $code variable, it looks like this:
$code = "___echo(TODAY_IS, date('j.n.Y', time()), time());";
What I need is to parse this $code variable and I want to get this result:
$result = array(
'function_name' => "___echo",
'arguments' => array(
0 => "TODAY_IS",
1 => "date('j.n.Y', time())",
2 => "time()"
)
);
I am thinking and I have tried using some regex, but neither worked sufficiently well. I also tried using Tokenizer, however I wasn't successful either.
Thanks for any hints or help in advance.
Here is a shot using PHP-Parser. It's likely going to be more useful than tokenizer or some freaky regex.
Example:
$code = "___echo(TODAY_IS, date('j.n.Y', time()), time());";
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$statements = $parser->parse("<?php $code");
$result['function_name'] = $statements[0]->name->toString();
foreach ($statements[0]->args as $arg) {
$result['arguments'][] = $prettyPrinter->prettyPrint(array($arg));
}
var_export($result);
Output:
array (
'function_name' => '___echo',
'arguments' =>
array (
0 => 'TODAY_IS',
1 => 'date(\'j.n.Y\', time())',
2 => 'time()',
),
)
token_get_all() function is what you need here:
token_get_all("<?php ___echo(TODAY_IS, date('j.n.Y', time()), time());")
This returns a list of tokens parsed from the given string. See the tokens documentation for recognizing the items of the list.
In my opinion, tokenizer-based solution should be preferred over any regular expressions based on whatever is written in the PHP manual regarding syntax.
Related
I'm working on an issue where users (truck drivers in this case) use SMS to send in information about work status. I want to keep the keying simple as not all users have smart phones so I have adopted some simple short codes for their input. Here are some examples and their meanings:
P#123456-3 (This is for picking up load 123456-3)
D#456789-1 (For the dropping of load 456789-1)
L#345678-9 (Load 345678-9 is going to be late)
This is pretty simple but users (and truck drivers) being what they are will key the updates in somewhat deviant manners such as:
#D 456789-1
D# 456789 - 1
D#.456789-1 This load looks wet to me do weneed to cancelthis order
You can pretty much come up with a dozen other permutations and it's not hard for me to catch and fix those that I can imagine.
I mostly use regular expressions to test the input against all my imagined "bad" patterns and then extract what I assume are the good parts, reassembling them into the correct order.
It's the new errors that cause me problems so I got to wondering if there was a more generic method where I can pass a "pattern" and a "message" to a function that would do it's best to turn the "message" into something matching the "pattern".
My searches have not found anything that really fits what I'm trying to do and I'm not even sure if there is a good general way to do this. I happen to be using PHP for this implementation but any type of example should help. Do any of you have a method?
If the user has problems with your software, fix the software, not the user!
The problem arises because your format looks unnecessary complicated. Why do you need the hash in the first place? How about simplifying it down to the following:
operation-code maybe-space load-number maybe-space and comment
Operation codes are assigned to different phone keys, so that J, K and L mean the same thing. Load-numbers can be sent as digits and as letters as well, e.g. agja means 2452. It's hard for the user to make a mistake using this format.
Here's some code to illustrate this approach:
function parse($msg) {
$codes = array(
3 => 'DROP',
5 => 'LOAD',
// etc
);
preg_match('~(\S)\s*(\S+)(\s+.+)?~', $msg, $m);
if(!$m)
return null; // cannot parse
$a = '.,"?!abcdefghijklmnopqrstuvwxyz';
$d = '1111122233344455566677777888999';
return array(
'opcode' => $codes[strtr($m[1], $a, $d)],
'load' => intval(strtr($m[2], $a, $d)),
'comment' => isset($m[3]) ? trim($m[3]) : ''
);
}
print_r(parse(' j ww03 This load looks wet to me'));
//[opcode] => LOAD
//[load] => 9903
//[comment] => This load looks wet to me
print_r(parse('dxx0123'));
//[opcode] => DROP
//[load] => 990123
//[comment] =>
Try something like this:
function parse($input) {
// Clean up your input: 'D#.456789 - 1 foo bar' to 'D 456789 1 foo far'
$clean = trim(preg_replace('/\W+/', ' ', $input));
// Take first 3 words.
list($status, $loadId1, $loadId2) = explode(' ', $clean);
// Glue back your load ID to '456789-1'
$loadId = $loadId1 . '-' . $loadId2;
return compact('status', 'loadId');
}
Example:
$inputs = array(
'P#123456-3',
'#D 456789-1',
'D# 456789 - 1',
'D#.456789-1 This load looks wet to me do weneed to cancelthis order',
);
echo '<pre>';
foreach ($inputs as $s) {
print_r(parse($s));
}
Output:
Array
(
[status] => P
[loadId] => 123456-3
)
Array
(
[status] => D
[loadId] => 456789-1
)
Array
(
[status] => D
[loadId] => 456789-1
)
Array
(
[status] => D
[loadId] => 456789-1
)
First, remove stuff that shouldn't be there:
$str = preg_replace('/[^PDL\d-]/i', '', $str);
That gives you the following normalised results:
D456789-1
D456789-1
D456789-1ldlddld
Then, attempt to match the data you want:
if (preg_match('/^([PDL])(\d+-\d)/i', $str, $match)) {
$code = $match[1];
$load = $match[2];
} else {
// uh oh, something wrong with the format!
}
Something like
/^[#\s]*([PDL])[#\s]*(\d+[\s-]+\d)/
or to be even more relaxed,
/^[^\d]*([PDL])[^\d]*(\d+)[^\d]+(\d)/
would get you what you want. But I'd prefer HamZa's comment as a solution: throw it back and tell them to get their act together :)
Usually when I search for one related ID I do it like this:
$thisSearch = $collection->find(array(
'relatedMongoID' => new MongoId($mongoIDfromSomewhereElse)
));
How would I do it if I wanted to do something like this:
$mongoIdArray = array($mongoIDfromSomewhereElseOne, $mongoIDfromSomewhereElseTwo, $mongoIDfromSomewhereElseThree);
$thisSearch = $collection->find(array(
'relatedMongoID' => array( '$in' => new MongoId(mongoIdArray)
)));
I've tried it with and without the new MongoId(), i've even tried this with no luck.
foreach($mongoIdArray as $seprateIds){
$newMongoString .= new MongoId($seprateIds).', ';
}
$mongoIdArray = explode(',', $newMongoString).'0';
how do I search '$in' "_id" when you need to have the new MongoID() ran on each _id?
Hmm your rtying to do it the SQL way:
foreach($mongoIdArray as $seprateIds){
$newMongoString .= new MongoId($seprateIds).', ';
}
$mongoIdArray = explode(',', $newMongoString).'0';
Instead try:
$_ids = array();
foreach($mongoIdArray as $seprateIds){
$_ids[] = $serprateIds instanceof MongoId ? $seprateIds : new MongoId($seprateIds);
}
$thisSearch = $collection->find(array(
'relatedMongoID' => array( '$in' => $_ids)
));
That should produce a list of ObjectIds that can be used to search that field - relatedMongoID.
This is what I am doing
Basically, as shown in the documentation ( https://docs.mongodb.org/v3.0/reference/operator/query/in/ ) the $in operator for MongoDB in fact takes an array so you need to replicate this structure in PHP since the PHP driver is a 1-1 with the documentation on most fronts (except in some areas where you need to use an additional object, for example: MongoRegex)
Now, all _ids in MongoDB are in fact ObjectIds (unless you changed your structure) so what you need to do to complete this query is make an array of ObjectIds. The ObjectId in PHP is MongoId ( http://php.net/manual/en/class.mongoid.php )
So you need to make an array of MongoIds.
First, I walk through the array (could be done with array_walk) changing the values of each array element to a MongoId with the old value encapsulated in that object:
foreach($mongoIdArray as $seprateIds){
$_ids[] = $serprateIds instanceof MongoId ? $seprateIds : new MongoId($seprateIds);
}
I use a ternary operator here to see if the value is already a MongoId encapsulated value, and if not encapsulate it.
Then I add this new array to the query object to form the $in query array as shown in the main MongoDB documentation:
$thisSearch = $collection->find(array(
'relatedMongoID' => array( '$in' => $_ids)
));
So now when the query is sent to the server it forms a structure similar to:
{relatedMongoId: {$in: [ObjectId(''), ObjectId('')]}}
Which will return results.
Well... I came across the same issue and the solution might not be relevant anymore since the API might have changed. I solved this one with:
$ids = [
new \MongoDB\BSON\ObjectId('5ae0cc7bf3dd2b8bad1f71e2'),
new \MongoDB\BSON\ObjectId('5ae0cc7cf3dd2b8bae5aaf33'),
];
$collection->find([
'_id' => ['$in' => $_ids],
]);
If there any way to discover the variables required from a Twig template? Example, if I had:
Hello {{ user }}! You're {{ age }} years old, well done big man!
I'd be able to load this template and then gather each of the required variables, eventually allowing me to have something like:
Array ( [0] => user [1] => age )
The end goal of this is to be able to define a view and then have the system create a form based on the required variables in a template file.
Working Solution
Thanks to morg for pointing me towards tokenize I was able to get what I wanted using the following (I placed it in my controller for testing):
$lexer = new \Twig_Lexer(new \Twig_Environment());
$stream = $lexer->tokenize(new \Twig_Source('{{test|raw}}{{test2|raw|asd}}{{another}}{{help_me}}', null));
$variables = array();
while (!$stream->isEOF()) {
$token = $stream->next();
if($token->getType() === \Twig_Token::NAME_TYPE){
$variables[] = $token->getValue();
while (!$stream->isEOF() && $token->getType() !== \Twig_Token::VAR_END_TYPE) {
$token = $stream->next();
}
}
}
$variables = array_unique($variables);
This returns:
Array
(
[0] => test
[1] => test2
[2] => another
[3] => help_me
)
You'll notice I only get variables and not any of the functions (this is through design), although you could remove the nested while loop if you wish to get both variables and functions.
You can use the twig tokenizer for this.
$stream = $twig->tokenize($source, $identifier);
The tokenizer has a toString() Method, whose resulting string you can parse for
VAR_START_TYPE()
NAME_TYPE(varname)
VAR_END_TYPE()
Look at this for more detailed information.
You can try using preg_match_all('{{\s*(\w+)\s*}}', 'template {{string }} with {{ var}}', $matchesArray);. The $matchArray is structured as following:
Array(
0 => array(0 => '{{string }}', 1 => 'string'),
1 => array(0 => '{{ var}}', 1 => 'var')
)
Another way of doing this from inside PHP code is not elegant, but still more reliable than any regex will be:
$source = "My template string with {{ some }} parameters.";
$stream = $twig->tokenize(new \Twig_Source($source, "source"));
$matches = [];
preg_match_all(
"/NAME_TYPE\((.*)\)/", $stream->__toString(), $matches
);
if (count($matches) > 1) {
$params = array_unique($matches[1]);
} else {
$params = [];
}
This works by using Twig internal mechanisms to tokenize the template string and then extract parameters names with a regex.
Edit: The previous version of my answer used the parse method to create a tree of nodes, but it didn’t seem to work anymore, and matching on NAME_TYPE at the previous step seems more reliable, not sure if I missed something there…
Is there any way to include MySQL expressions like NOW() in the current build of ZF2 (2.0.0beta4) through Zend\Db and/or TableGateway insert()/update() statements?
Here is a related post on the mailing list, though it hasn't been answered: http://zend-framework-community.634137.n4.nabble.com/Zend-Db-Expr-and-Transactions-in-ZF2-Db-td4472944.html
It appears that ZF1 used something like:
$data = array(
'update_time' => new \Zend\Db\Expr("NOW()")
);
And after looking through Zend\Db\Sql\Expression I tried:
$data = array(
'update_time' => new \Zend\Db\Sql\Expression("NOW()"),
);
But get the following error:
Catchable fatal error: Object of class Zend\Db\Sql\Expression could not be converted to string in /var/www/vendor/ZF2/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 256
As recommended here: http://zend-framework-community.634137.n4.nabble.com/ZF2-beta3-Zend-Db-Sql-Insert-new-Expression-now-td4536197.html I'm currently just setting the date with PHP code, but I'd rather use the MySQL server as the single source of truth for date/times.
$data = array(
'update_time' => date('Y-m-d H:i:s'),
);
Thanks, I appreciate any input!
Zend_Db_Sql_Expression works for me now.
$data = array(
'update_time' => new \Zend\Db\Sql\Expression("NOW()"),
);
I am not sure if they fixed this in the latest updates, but it was a know bug and even if they said it was fixed, for me it still didn't work after the fix.
It works for me too. Maybe, your problem was like mine. The string error conversion was because the parameter that I passed to method was elements of array and not the array. Check this and have sure that you are passing $object->method($array).
You can try:
\Zend\Db\Sql\Predicate\Literal();
//or
\Zend\Db\Sql\Literal();
------------------------------------
$data = array(
'update_time' => new \Zend\Db\Sql\Literal("NOW()"),
);
https://framework.zend.com/manual/2.2/en/modules/zend.db.sql.html#literal-literal
I am developing a search portal, so I need to find the searched text on more than one field.
I am trying to use the advanced query of MongoDB in PHP.
My code is:
$mongo = new MongoDBCONN();
$jobCollection = $mongo->select('jobs', $mongo);
$advanceQuery=array('orgId' => '21')
$query_q=array('$or'=>array(
array("jobTitle"=>new MongoRegex("/$search/i")),
array("jobLocationCity"=>new MongoRegex("/$search/i")),
array('jobLocationCountry'=>new MongoRegex("/$search/i"))
));
$advanceQuery=array_merge($advanceQuery,$query_q);
$jobCollection->find($advanceQuery);
It returns NULL every time, whereas MongoRegex is working fine, because when I use it to search on only one field it works.
$search is post as input text.
I found the answer on my own, actually this was a version problem. I was using 1.4.4, but after update to 1.7.4 it is working. On the mongo website I found that the "or" operator was included only from version 1.7.x onwards.
$regexObj = new MongoRegex("/$search_term/i");
$where = array(
'$or' => array(
array("Name" => $regexObj),
array("image.caption.text" => $regexObj),
array("image.user.username" => $regexObj)
)
);
$cursor = $collection->find($where);
// Parsing the results
while ($cursor->hasNext())
{
$obj = $cursor->getNext();
$profile_image = $obj['image']['user']['profile_picture'];
}
If you need to combine the regex with another operator, you need to use the $regex clause. That one is missing in your code example. Please read again about how to query with regulars expressions.