Is it possible to reflect into or otherwise view the source of a PHP closure object? That is, if I do something like this
$closure = function()
{
return 'Hi There';
};
and then something like this
var_dump($closure);
PHP outputs
object(Closure)[14]
That is, I know the object's a closure, but I have no idea what it does.
I'm looking for a reflection method, function, or debugging extension that will allow me to dump the actual body of anonymous function.
What you can get from PHP is limited, using reflection you can just obtain the parameter signature of the function and the start and ending line of the source code file. I've once wrote a blog article about that: http://www.metashock.de/2013/05/dump-source-code-of-closure-in-php/ ...
It lead me to the following code, using reflection:
function closure_dump(Closure $c) {
$str = 'function (';
$r = new ReflectionFunction($c);
$params = array();
foreach($r->getParameters() as $p) {
$s = '';
if($p->isArray()) {
$s .= 'array ';
} else if($p->getClass()) {
$s .= $p->getClass()->name . ' ';
}
if($p->isPassedByReference()){
$s .= '&';
}
$s .= '$' . $p->name;
if($p->isOptional()) {
$s .= ' = ' . var_export($p->getDefaultValue(), TRUE);
}
$params []= $s;
}
$str .= implode(', ', $params);
$str .= '){' . PHP_EOL;
$lines = file($r->getFileName());
for($l = $r->getStartLine(); $l < $r->getEndLine(); $l++) {
$str .= $lines[$l];
}
return $str;
}
If you have the following closure:
$f = function (Closure $a, &$b = -1, array $c = array())
use ($foo)
{
echo $this->name;
echo 'test';
};
closure_dump() will give the following results:
function (Closure $a, &$b = -1, array $c = array (
)){
use ($foo)
{
echo $this->name;
echo 'test';
};
You see it is imperfect (the array param). Also it will not handle some edge cases properly, especially if closures are nested or multiple inline closures will getting passed to a function in one line. The latter looks most problematic to me. Since, you get only the starting and ending line from reflection, both functions will be on that line in this case and you have no useful information to decide which one of them should get dumped. So far, I didn't found a solution for that, also I'm unsure if there is a solution.
However, in most cases, it should at least being helpful for debugging, as long as you don't rely on it. Feel free to enhance it!
Related
I'm trying to insert a PHP EOT string into a js code and I need to encode it since it has some \n in it. I can't do it with a simple jsonencode because it returns the code with "'s and I don't need them there.
So i tried those two approaches which both return an error:
<?php
$string = $v['survey'];
function escapeJavaScriptText($string)
{
return str_replace("\n", '\n', str_replace('"', '\"', addcslashes(str_replace("\r", '', (string)$string), "\0..\37'\\")));
}
?>
<?php echo escapeJavaScriptText(); ?>
I've also tried:
<?php
$string = $v['survey'];
function javascript_escape($string) {
$new_str = '';
$str_len = strlen($string);
for($i = 0; $i < $str_len; $i++) {
$new_str .= '\\x' . sprintf('%02x', ord(substr($string, $i, 1)));
}
return $new_str;
}
?>
<?php echo javascript_escape(); ?>
Both return:
Missing argument 1 for escapeJavaScriptText() and Undefined variable: string in
But <?php echo $string; ?> returns the code I need (just without the encoding).
What am I missing?
<?php
$string = $v['survey'];
function javascript_escape($string) {
$new_str = '';
$str_len = strlen($string);
for($i = 0; $i < $str_len; $i++) {
$new_str .= '\\x' . sprintf('%02x', ord(substr($string, $i, 1)));
}
return $new_str;
}
?>
<?php echo javascript_escape($string); ?>
It's because of different variable scopes within your function and global code. In PHP, by default, functions don't have access to global variables.
In your case, variable $string, you're trying to use by function javascript_escape, is a global variable. So, you have a few ways to avoid Undefined Variable exception.
1. Use "global" keyword to have acces to global variables inside the function.
After function definition, you just need to specify global variables this way (and remove $string from arguments list):
function javascript_escape() {
global $string;
$new_str = '';
$str_len = strlen($string);
for($i = 0; $i < $str_len; $i++) {
$new_str .= '\\x' . sprintf('%02x', ord(substr($string, $i, 1)));
}
return $new_str;
}
or
function escapeJavaScriptText()
{
global $string;
return str_replace("\n", '\n', str_replace('"', '\"', addcslashes(str_replace("\r", '', (string)$string), "\0..\37'\\")));
}
2. Pass $string as an argument of function call.
If you have a reasons not to use global variables at all, you're able to pass unescaped string each time the function has to be called.
<?php echo javascript_escape($string); ?>
3. Use pre-escaped string.
If you have to use $string encoded by your function multiple times, it'd be a good practice to escape the variable at the beginning of your code and use it without calling escape function every time you need to use this variable:
$escaped_string = javascript_escape($string);
...
<?php echo $escaped_string; ?>
Also, I recommend you to read the official documentation about variable scopes: http://php.net/manual/en/language.variables.scope.php
Is there a way to define few similar php function dynamically in a manner like
define('MAX_LOG_LEVEL', 10);
_log_level( $string, $level = 0 ) {
global $loglevel;
if( $level >= $loglevel ) {
file_put_contents(...);
}
}
for($level = 0; $level < MAX_LOG_LEVEL; $level++) {
function _log$level( $string ) {
_log_level( $string, $level );
}
}
that's because i have to create my own log routines and to quickly add an log information on demand.
I know the php built in create_function that do not really do what i exactly want because will be necessary to import the reference variable in the function/method that use that
I can't also use the eval() function as explained in this question since it is disabled on the final application hosting.
One way to do it is to assign an anonymous function to a variable and call it like so:
foreach (range(1, 9) as $i)
{
${'_log' . $i} = function($string) use ($i)
{
echo 'Log ' . $i . ': ' . $string;
};
}
// Outputs Log 9: Test
$_log9('Test');
Another possibility is to use eval (although you can't use it on your current application it might be possible in future environments):
foreach (range(1, 9) as $i)
{
eval('
function _log' . $i . '($string)
{
echo \'Log ' . $i . ': \' . $string;
}
');
}
// Outputs Log 9: Test
_log9('Test');
If using this latter method take note of the warning in the manual:
Caution The eval() language construct is very dangerous because it allows execution of arbitrary PHP code. Its use thus is
discouraged. If you have carefully verified that there is no other
option than to use this construct, pay special attention not to pass
any user provided data into it without properly validating it
beforehand.
However, as others have mentioned in the comments, it is perhaps best to not define a unique function for each log-level: instead define a single function with the log-level as a parameter:
function _log($i, $string)
{
echo 'Log ' . $i . ': ' . $string;
}
// Outputs Log 9: Test
_log(9, 'Test');
A further extension to this is to define the log levels to make it clearer to both yourself and to future programmers what exactly is being logged:
define('_LOG_CRITICAL_ERROR', 9);
function _log($i, $string)
{
echo 'Log ' . $i . ': ' . $string;
}
// Outputs Log 9: Test
_log(_LOG_CRITICAL_ERROR, 'Test');
Yes, it is possible; you can do this:
$function = '_log' . $level;
$function($string);
This is known as a variable function.
With my custom function below, my aim is to give a specific link to each element of array of tags. My input to the function is a string like (tag1, tag2, tag3). My output is (in linked form) tag1,
“tag1,” is okey but why can not I get what I expect : “tag1, tag2, tag3” (in linked form)
I read examples in php.net and in this site for the terms (array, explode, for, .=) but I couldn’t solve my issue.
Can you guide me please
function tag_linkify ($article_tags)
{
$array_of_tags = explode(",", $article_tags);
$sayac = count($array_of_tags);
$linked_tags ="";
for ($i=0; $i<$sayac; $i++)
{
$linked_tags .= ''.$array_of_tags[$i].', ';
}
echo substr_replace($linked_tags, '', -1, 2);
}
tag_linkify (tag1,tag2,tag3);
ThanksRegards
Improving on Sedz post:
function tag_linkify ($article_tags)
{
$array_of_tags = explode(",", $article_tags);
echo '' . implode(',', $array_of_tags) . '';
}
tag_linkify ("tag1,tag2,tag3");
Btw. the parameters in your tag_linkify call miss their quotation marks and
'<a href="'.'">'
is really the same as
'<a href="">'
If i understand your question correctly i would do:
tag_linkify ($tag1, $tag2, $tag3);
function tag_linkify ()
{
$tags = get_func_args(); // get all tags in an array
$final = '';
// loop through the tags
forech($tags as $tag)
{
// return or echo depends on what you doing with your data
$final .=''. $tag . '';
}
return $final;
}
get_func_args
Check this out with use of implode
function tag_linkify ()
{
$array_of_tags = get_func_args();;
$sayac = count($array_of_tags);
$linked_tags =array();
for ($i=0; $i<$sayac; $i++)
{
$linked_tags[] = ''.$array_of_tags[$i].' ';
}
echo "(".implode(',', $lined_tags).")";
}
tag_linkify (tag1,tag2,tag3);
I hope this can help
I am using Zend_Search_Lucene, to index my website. My site indexes are not entirely similar. Some have, few fields, and some have many fields. I am trying to create a similar index through different types of table, that's why i am encountering this kind of error.
Now, when I display the result. I call some fields, which are not present in all the result which generates the error. i tried to check it with isset but it seems it totally skips the row.
foreach ($hits as $hit) {
$content .= '<div class="searchResult">';
$content .= '<h2>';
$title = array();
if(isset($hit -> name)) $title[] = $hit -> name;
if(isset($hit -> title)) $title[] = $hit -> title;
// This is the part where i get fatal error.
$content .= implode(" » ",$title);
$content .= '</h2>';
$content .= '<p>'.$this->content.'</p>';
$content .= '</div>';
}
How to check if there is anything such as $hit -> name is present in $hit
The problem you are experiencing is very very specific and has to do with the Zend_Lucene_Search implementation, not the field/property exist check in general.
In your loop, $hit is an object of class Zend_Search_Lucene_Search_QueryHit. When you write the expression $hit->name, the object calls the magic __get function to give you a "virtual property" named name. It is this magic function that throws the exception if the value to be supplied does not exist.
Normally, when a class implements __get as a convenience it should also implement __isset as a convenience (otherwise you cannot really use isset on such virtual properties, as you have found out the hard way). Since this particular class does not implement __isset as IMHO it should, you will never be able to get the name "property" blindly without triggering an exception if the relevant data does not exist.
property_exists and all other forms of reflection will also not help since we are not talking about a real property here.
The proper way to solve this is a little roundabout:
$title = array();
$names = $hit->getDocument()->getFieldNames();
if(in_array('name', $names)) $title[] = $hit -> name;
if(in_array('title',$names)) $title[] = $hit -> title;
All said, I 'd consider this a bug in ZF and probably file a report, asking for the __isset magic method to be implemented appropriately on the types it should be.
You can try property_exists.
foreach ($hits as $hit) {
$content .= '<div class="searchResult">';
$content .= '<h2>';
$title = array();
if(property_exists($hit, 'name')) $title[] = $hit -> name;
if(property_exists($hit, 'title')) $title[] = $hit -> title;
// This is the part where i get fatal error.
$content .= implode(" » ",$title);
$content .= '</h2>';
$content .= '<p>'.$this->content.'</p>';
$content .= '</div>';
}
You can also use reflection to query what fields the object has and build your content in a more programmatic way. this is good if you have a ton of fields.
$reflector = new ReflectionClass( get_class( $hit ) );
foreach( $reflector->getProperties() as $property ) {
if( in_array( $property->getName(), $SEARCH_FIELDS )
$title[] = $property->getValue( $hit );
}
more info here : http://php.net/manual/en/book.reflection.php
# Verify if exists
$hits = $index->find('id_field:100');
if (isset($hits[0])) { echo 'true'; } else { echo 'false'; }
isset will work if you simply convert your object to an array first.
<?php
class Obj {
public function GetArr() {
return (array)$this;
}
static public function GetObj() {
$obj = new Obj();
$obj->{'a'} = 1;
return $obj;
}
}
$o = \Obj::GetObj();
$a = $o->GetArr();
echo 'is_array: ' . (\is_array($a) ? 1 : 0) . '<br />';
if (\is_array($a)) {
echo '<pre>' . \print_r($a, \TRUE) . '</pre><br />';
}
echo '<pre>' . \serialize($o) . '</pre>';
?>
when we add a param to the URL
$redirectURL = $printPageURL . "?mode=1";
it works if $printPageURL is "http://www.somesite.com/print.php", but if $printPageURL is changed in the global file to "http://www.somesite.com/print.php?newUser=1", then the URL becomes badly formed. If the project has 300 files and there are 30 files that append param this way, we need to change all 30 files.
the same if we append using "&mode=1" and $printPageURL changes from "http://www.somesite.com/print.php?new=1" to "http://www.somesite.com/print.php", then the URL is also badly formed.
is there a library in PHP that will automatically handle the "?" and "&", and even checks that existing param exists already and removed that one because it will be replaced by the later one and it is not good if the URL keeps on growing longer?
Update: of the several helpful answers, there seems to be no pre-existing function addParam($url, $newParam) so that we don't need to write it?
Use a combination of parse_url() to explode the URL, parse_str() to explode the query string and http_build_query() to rebuild the querystring. After that you can rebuild the whole url from its original fragments you get from parse_url() and the new query string you built with http_build_query(). As the querystring gets exploded into an associative array (key-value-pairs) modifying the query is as easy as modifying an array in PHP.
EDIT
$query = parse_url('http://www.somesite.com/print.php?mode=1&newUser=1', PHP_URL_QUERY);
// $query = "mode=1&newUser=1"
$params = array();
parse_str($query, $params);
/*
* $params = array(
* 'mode' => '1'
* 'newUser' => '1'
* )
*/
unset($params['newUser']);
$params['mode'] = 2;
$params['done'] = 1;
$query = http_build_query($params);
// $query = "mode=2&done=1"
Use this:
http://hu.php.net/manual/en/function.http-build-query.php
http://www.addedbytes.com/php/querystring-functions/
is a good place to start
EDIT: There's also http://www.php.net/manual/en/class.httpquerystring.php
for example:
$http = new HttpQueryString();
$http->set(array('page' => 1, 'sort' => 'asc'));
$url = "yourfile.php" . $http->toString();
None of these solutions work when the url is of the form:
xyz.co.uk?param1=2&replace_this_param=2
param1 gets dropped all the time
.. which means it never works EVER!
If you look at the code given above:
function addParam($url, $s) {
return adjustParam($url, $s);
}
function delParam($url, $s) {
return adjustParam($url, $s);
}
These functions are IDENTICAL - so how can one add and one delete?!
using WishCow and sgehrig's suggestion, here is a test:
(assuming no anchor for the URL)
<?php
echo "<pre>\n";
function adjustParam($url, $s) {
if (preg_match('/(.*?)\?/', $url, $matches)) $urlWithoutParams = $matches[1];
else $urlWithoutParams = $url;
parse_str(parse_url($url, PHP_URL_QUERY), $params);
if (strpos($s, '=') !== false) {
list($var, $value) = split('=', $s);
$params[$var] = urldecode($value);
return $urlWithoutParams . '?' . http_build_query($params);
} else {
unset($params[$s]);
$newQueryString = http_build_query($params);
if ($newQueryString) return $urlWithoutParams . '?' . $newQueryString;
else return $urlWithoutParams;
}
}
function addParam($url, $s) {
return adjustParam($url, $s);
}
function delParam($url, $s) {
return adjustParam($url, $s);
}
echo "trying add:\n";
echo addParam("http://www.somesite.com/print.php", "mode=3"), "\n";
echo addParam("http://www.somesite.com/print.php?", "mode=3"), "\n";
echo addParam("http://www.somesite.com/print.php?newUser=1", "mode=3"), "\n";
echo addParam("http://www.somesite.com/print.php?newUser=1&fee=0", "mode=3"), "\n";
echo addParam("http://www.somesite.com/print.php?newUser=1&fee=0&", "mode=3"), "\n";
echo addParam("http://www.somesite.com/print.php?mode=1", "mode=3"), "\n";
echo "\n", "now trying delete:\n";
echo delParam("http://www.somesite.com/print.php?mode=1", "mode"), "\n";
echo delParam("http://www.somesite.com/print.php?mode=1&newUser=1", "mode"), "\n";
echo delParam("http://www.somesite.com/print.php?mode=1&newUser=1", "newUser"), "\n";
?>
and the output is:
trying add:
http://www.somesite.com/print.php?mode=3
http://www.somesite.com/print.php?mode=3
http://www.somesite.com/print.php?newUser=1&mode=3
http://www.somesite.com/print.php?newUser=1&fee=0&mode=3
http://www.somesite.com/print.php?newUser=1&fee=0&mode=3
http://www.somesite.com/print.php?mode=3
now trying delete:
http://www.somesite.com/print.php
http://www.somesite.com/print.php?newUser=1
http://www.somesite.com/print.php?mode=1
You can try this:
function removeParamFromUrl($query, $paramToRemove)
{
$params = parse_url($query);
if(isset($params['query']))
{
$queryParams = array();
parse_str($params['query'], $queryParams);
if(isset($queryParams[$paramToRemove])) unset($queryParams[$paramToRemove]);
$params['query'] = http_build_query($queryParams);
}
$ret = $params['scheme'].'://'.$params['host'].$params['path'];
if(isset($params['query']) && $params['query'] != '' ) $ret .= '?'.$params['query'];
return $ret;
}