Parsing Classes, Functions and Arguments in PHP - php

I want to create a function which receives a single argument that holds the path to a PHP file and then parses the given file and returns something like this:
class NameOfTheClass
function Method1($arg1, $arg2, $arg2)
private function Method2($arg1, $arg2, $arg2)
public function Method2($arg1, $arg2, $arg2)
abstract class AnotherClass
function Method1($arg1, $arg2, $arg2)
private function Method2($arg1, $arg2, $arg2)
public function Method2($arg1, $arg2, $arg2)
function SomeFunction($arg1, $arg2, $arg3)
This function should return all the classes, methods and function that exist in the given file with all the defined identifiers (abstract, public, private, protected, static, extends, interfaces, ...).
My first tought was to use regular expressions to do this, however these behave quite badly with comments, ie: /* this function returns(max(salary)) */ and become quite complex if I want to properly support scopes.
Another possible solution was to use the following built-in PHP functions:
get_declared_classes
get_declared_interfaces
get_defined_functions
get_class_methods
However these functions don't allow me to see the file where the classes / methods / functions are defined and thus it's not very useful.
I believe the Tokenizer extension is the solution for my problem, however I have never used this extension before.

If you are using PHP 5, the Reflection API is your tool.
Example:
$class = new ReflectionClass("NameOfTheClass");
$methods = $class->getMethods();
foreach($methods as $m) {
print $m->name;
$m->isPrivate() ? print "Private" : print "";
$m->isPublic() ? print "Public" : print "";
$params = $m->getParameters();
foreach($params as $p) {
print $p->getName();
}
}

I suggest the following procedure:
store the current output of get_declared_classes, get_declared_interfaces and get_defined_functions(if you really need to support them)
include the file
compare get_declared_classes, get_declared_interfaces and get_defined_functions with the ones you stored to see what's new
use reflection to analyze them
goto step 2 for the next file

Like you found out yourself, regex are quite not the right tool for the job, here ^^
And, like you said, the built-in functions you proposed are not that helpful either -- only thing that might be helpful is that they allow you to know which class exists... But they'll return builtin classes too :-(
Using the Tokenizer extension seems a bit overkill/hard to me ; I would probably not go that way, actually : too "low-level", I suppose.
Instead, I would take a look at PHP's Reflection API : it exists exactly to reverse-engineer classes, interfaces, functions, ...
So, I suppose it would be quite well-suited for what you are trying to do.
Edit : here is a quick example :
First, let's try to do reflection on a class :
include dirname(__FILE__) . '/temp-2.php';
$rC = new ReflectionClass('MyFirstClass');
You can now find out in which file it was declared, and which methods are in it :
var_dump($rC->getFileName());
var_dump($rC->getMethods());
Which will get you :
string '/home/squale/developpement/tests/temp/temp-2.php' (length=48)
array
0 => &
object(ReflectionMethod)[2]
public 'name' => string '__construct' (length=11)
public 'class' => string 'MyFirstClass' (length=12)
1 => &
object(ReflectionMethod)[3]
public 'name' => string 'glop' (length=4)
public 'class' => string 'MyFirstClass' (length=12)
And now, to get informations on each method :
foreach ($rC->getMethods() as $rM) {
var_dump($rM, $rM->getParameters());
echo '-----';
}
You'll get :
object(ReflectionMethod)[3]
public 'name' => string '__construct' (length=11)
public 'class' => string 'MyFirstClass' (length=12)
array
0 => &
object(ReflectionParameter)[4]
public 'name' => string 'arg1' (length=4)
1 => &
object(ReflectionParameter)[5]
public 'name' => string 'arg2' (length=4)
-----
object(ReflectionMethod)[2]
public 'name' => string 'glop' (length=4)
public 'class' => string 'MyFirstClass' (length=12)
array
0 => &
object(ReflectionParameter)[5]
public 'name' => string 'a' (length=1)
From there, you should be able to dig a bit more ; and arrive to what you first asked ;-)
As a sidenote : there is one thing I have no idea about is : "how to find which classes / methods are declared in a given file" :-(
If anyone has an idea, it'll be welcome !

Related

How to stop PHP notice: Undefined index | array_map | return

I always get a PHP notice in my error log because of a little language function I am using. Somehow I can't get it right...
My languages array looks like this (EDITED):
$dict = [
'title' => [
'en' => 'Welcome',
'de' => 'Willkommen'
],
'description' => [
'en' => 'Hello',
'de' => 'Hallo'
],
];
And this is my language function (EDITED):
function lang($phrase){
global $l, $dict;
return $dict[$phrase][$l];
}
$l is defined before the $dict array (as "de" or "en").
In my document I am doing:
<?php echo lang('title');?>
And the PHP notice refers to the "return $dict[$phrase][$l];" part.
I don't really understand why. I tried different things and nothing works.
EDIT:
Oh, damn. After posting I finally realized I where using a phrase which is not in my language array anymore. Somehow I was blind. Nevertheless the post was good to clean up my function. Thanks!
As noted my comments on the original post, after fixing the missing bracket (assuming typo) in your array declaration, the function works correctly, if called correctly as echo lang('title'). If you get a notice on Undefined index, it means the $phrase you're looking for doesn't exist in the $dict array. (If you only get the notice in your error logs, you may want to turn on on-screen error reporting when you're developing to catch these in real time!)
Basic debugging aside, let me help you tidy up this code a bit. First, $l or language can be passed in as a function argument. Second, the $dict doesn't need to be a variable, assuming you're not modifying it on the fly. You can declare it as a constant and avoid globals. Third, your array mapping is unnecessary overhead, when you can simply call the relevant indexes in the array. A minimal clean-up would then look something like this:
const DICT = [
'title' => [
'en' => 'Welcome',
'de' => 'Willkommen'
],
'description' => [
'en' => 'Hello',
'de' => 'Hallo'
]
];
function lang($phrase, $lang) {
// If you're using PHP 7, the null-coalescing operator is handy here:
return DICT[$phrase][$lang] ?? "Phrase {$phrase} Unknown!";
}
You'd use it like this:
// Sample usage: Output title in English and description in German:
echo lang('title', 'en') . ' ' . lang('description', 'de');
// Or, if the language variable is pre-defined, then e.g.:
$l = 'en';
echo lang('title', $l) . ' ' . lang('description', $l);
Here, the use of a constant for your dictionary is a basic but very efficient way to handle a global need for pre-defined data. Constants are parsed at compile-time, while variable declarations are parsed at runtime. They are available in all contexts. And you definitely don't want to have the whole $dict inside your function just to avoid a global declaration!
A more elegant way to handle this would be to create a class, which will allow you to avoid the language function parameter. (Class constants and properties are like "local globals", available to all methods in the class.) Here's a sample application of your case as a class, where the dictionary is a class constant, and the language is a class property:
class Dict {
const DICT = [
'title' => [
'en' => 'Welcome',
'de' => 'Willkommen'
],
'description' => [
'en' => 'Hello',
'de' => 'Hallo'
]
];
public static $lang = 'en'; // for a default language
public static function phrase($phrase)
{
return self::DICT[$phrase][self::$lang] ?? "Phrase {$phrase} Unknown!";
}
}
Dict::$lang = 'de'; // Define language
echo Dict::phrase('title') . ' ' . Dict::phrase('description');
// : Willkommen Hallo
If you expect to have a large dictionary that needs to be accessed in other contexts, or simply wish to centralize your language reference, then you may want to define it as a regular constant outside the class, as in the first example. I've used a class constant here mostly for educational purposes. Hope this helps you forward and towards cleaner code.
The notice means that you are trying to access an index of your array that doesn't exist. In your case, it seems that you mixed up the order of language and phrase: You try to access $dict[en][title] instead of $dict[title][en].
More generally, always check if an index exists before you return it:
if (isset($dict[$l])) {
return ...
} else {
throw new Exception... // or whatever
}
As you move further on with PHP, you'll find that using globals will cause more harm than it's worth.

PHP: Sending a list of options as an argument (alternative to named parameters/ argument bag)

I wish to give a list of options as an argument to a function.
The Ideal Scenario: Named Parameters
If PHP has named parameters it would be done like so:
function setOptions($title, $url, $public = true, $placeholder = "type here...") {
...
}
setOptions($title = "Hello World", $url = "example.com", $placeholder = "hi");
Unfortunately PHP does not have named parameters (please tell me if PHP7 is planned to have some as a comment).
The solution everyone else is using: Associative Array
Most PHP scripts I have seen use an alternative array approach like so:
function setOptions($options) {
...
}
setOptions(array(
'title' => "Hello World",
'url' => "example.com",
'placeholder' => "hi"
));
Drawbacks of Associative Array Approach
Although this works fine, there are the following drawbacks:
The user does not benefit from autocompletion (taking a long time to write)
The user can easily makes mistakes in spellings
The don't know what options is available, so may frequently revert back to documentation
Is there a better way?
Is there a better way that can address these issues (either in current PHP or PHP7 or maybe even hacklang(?)).
In Hack, you can use Shapes. Shapes define a structure for associative arrays so that things can be autocompleted (depending on IDE support) and spelling mistakes are picked up by the type checker.
For instance, your example could be reworked like:
function setOptions(shape(
'title' => string,
'url' => string,
'public' => ?bool,
'placeholder' => ?string,
) $options) {
$title = $options['title'];
$url = $options['url'];
$public = Shapes::idx($options, 'public', true);
$placeholder = Shapes::idx($options, 'placeholder', 'type here...');
...
}
setOptions(shape(
'title' => 'Hello World',
'url' => 'example.com',
'placeholder' => 'hi',
));
This marks title and url to both be required options and public and placeholder are optional (all nullable types in shapes are considered to be optional). Shapes::idx is then used to get the value provided, or the default value (the third argument) if a value was not passed in.
Solution: Using fluent setters
A potential solution I have found to this problem is to use classes and fluent setters like so:
class PostOptions {
protected
$title,
$url,
$public = TRUE,
$placeholder = "type here..."; //Default Values can be set here
static function getInstance(): PostOptions {
return new self();
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setUrl($url) {
$this->url = $url;
return $this;
}
public function setPublic($public) {
$this->public = $public;
return $this;
}
public function setPlaceholder($placeholder) {
$this->placeholder = $placeholder;
return $this;
}
}
You can then send the options like so:
function setOptions(PostOptions $postOptions) {
//...
}
setOptions(
PostOptions::getInstance()
->setTitle("Hello World")
->setUrl("example.com")
->setPlaceholder("hi")
);
Doing it quickly! (This looks long)
Although this may look long, it can actually be implemented VERY quickly using IDE tools.
e.g. In InteliJ or PHPStorm, just type ALT+INS > Select setters > Select the fields you want to set and check the checkbox for fluent setters > click OK
Why Fluent Setters? Why Not just make all the fields public?
Using public fields is a LOT slower. This is because fluent setters can make use of chained methods, whilst the public fields way must be written like this:
$options = new PostOptions();
$options->title = "hello";
$options->placeholder = "...";
$options->url "..."
setOptions($options);
Which is a lot more typing compared to the proposed solution
Why is this better?
It's faster in IDE's when using autocomplete than the array approach
Unlikely to make mistakes in spellings (thanks to autocomplete)
Easy to see what options is available (again thanks to autocomplete)
Can give individual documentation for individual fields using PHPDoc
Can use nested options more easily e.g. If you had a list of options, and that option also had more list of options
Other OOP advantages e.g. Inheritance & Abstract Classes
How much faster is this approach?
I implemented a quick class for Wordpress labels array in: https://codex.wordpress.org/Function_Reference/register_post_type
I found that setting a property for each value (with the documentation next to you on a 2nd monitor) that the fluent setters approach is approximately 25% faster than the array approach thanks to autocomplete! However, if the documentation was not next to you, I expect this approach will far exceed 25%, as discovery of options is much quicker!
Alternative approaches are welcome
Declaration from array
This is how I normally declare my class structure. The only drawback is that it takes a while longer to write, but it allows optional parameters, defaults values, etc.
public static $defaults = array(
'user_id' => null,
'username' => null,
'avatar' => null,
'email' => null,
'description' => null,
);
public function __construct(array $args = array()) {
$this->dbc = Database::connection();
$defaults = self::$defaults;
$args = array_merge($defaults, $args);
//Assign the object properites
$this->user_id = (is_numeric($args['user_id'])) ? $args['user_id'] : null;
$this->username = $args['username'];
$this->avatar = AVATAR_DIR . $args['avatar'];
$this->email = $args['email'];
$this->description = $args['description'];
}
This way, you can declare an object like $x = new User(), and it will work perfectly fine. Let's say you've only selected a few columns from your SQL statement. You can make the keys in the public static $defaults into the same name as the columns you've selected, that way to instantiate your object, you can easily do:
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
$object = new User($row);
The array_merge takes care of having any extraneous keys that you don't need in the argument they provided. If you need to change options, you can declare them the same way for __construct() with a default array and array_merge to catch arguments and mimic named parameters and defaults values (like in Python)
With Syntactic: https://github.com/topclaudy/php-syntactic
you can just do:
function foo($a = 1, $b = 2, $c = 3, $d = 4){
return $a * $b * $c * $d;
}
And call it with the arguments you want:
//Call with argument b only
echo s('foo')->in('b', 5)->out(); //Outputs 60
//Call with argument a and argument at index/position 1 (b),
echo s('foo')->in('a', 7)->in(1, 5)->out(); //Outputs 420
//Call with argument c only through dynamic method
echo s('foo')->c(9)->out(); //Outputs 72
If U have that much parameters I'd think about creating an object that you'll pass to class instead of n parameters and every parameter is one field there. In constructor you put required parameters and this is then clean solution.

Pass Dynamic Values from one function to another function in class

Hi all i have a function in a class which will print all the variable pass for this function
Class Code :
<?php
class MyPrintClass {
public function printVaraible() {
var_dump(func_get_args())
}
}
$printVar = new MyPrintClass;
?>
if i use like below its correctly working and printing all the values pass.
$print->printVaraible('value1','Value2',20);
The output i got for above cmd
array (size=1)
0 => string 'value1' (length=6)
1 => string 'value2' (length=6)
2 => string 'value3' (length=6)
if i use like blow its showing as a single array.. i want the value seperated.. how its possible ?
function getVal(){
global $printVar;
$print->printVaraible(func_get_args());
}
I need to pass the value like below
getVal('value1','Value2',20);
and i need the output as
array (size=1)
0 => string 'value1' (length=6)
1 => string 'value2' (length=6)
2 => string 'value3' (length=6)
Currently i get NULL as the output
Updated Question Based On the Answer Given By deceze
** I Also had a small change in my code**
Class Code :
<?php
class MyPrintClass {
public function printVaraible($tag,$value) {
echo $tag.' == '.$value;
var_dump(func_get_args());
}
}
$printVar = new MyPrintClass;
?>
Converted Class To Function By
<?php
function getVal($tag,$value) {
global $printVar;
call_user_func_array([$printVar, 'printVariable'], $tag,$value,func_get_args());
}
?>
If i try to use it as below i get error
<?php getVal('first','second','third,'fourth'); ?>
Warning: call_user_func_array() expects exactly 2 parameters, 4 given
call_user_func_array([$print, 'printVariable'], func_get_args());
This calls the function with separate parameters from an array. I'd question the usefulness of this though, since you'll just end up with an array from func_get_args anyway. Why not pass arrays around in the first place?
Your getting null in the return because you never return anything, from the functions above.
if you want to call a class method I suggest you look into Refection or you can also do
$a = 'printVaraible';
$class->$a(1,3); //
besides using call_user_func_array, I would also suggest looking into closures (php > 5.3 ), which will let you write them this way.
$a = function foo(){ echo 'foo'};
$a();
Last thing I would generally avoid using global, as it obscures in your code where the values come from. It is always better to inject values into the function scope and not mix that with the global, imaging hunting that global down in a large project, I just avoid them at all cost and forget they exist.

var_dump - how to get the particular value from dump PHP

I know there is alot of information available in regards to PHP - var_dump my question is if I need to pick the particular information from the dump I am receiving from the API which is returning different array object....for example the below dump I need the information of realm under characterData Array how can I pick the that information and stored in String variable. Brief example of php code to explain my question will be highly appreciated...
object(Character)[3]
private 'name' => string 'XXXX' (length=6)
private 'region' => string 'eu' (length=2)
private 'realm' => string 'Defias Brotherhood' (length=18)
private 'characterData' =>
array (size=24)
'lastModified' => float 1363345999000
'name' => string 'Growar' (length=6)
'realm' => string 'Defias Brotherhood' (length=18)
'battlegroup' => string 'Rampage / Saccage' (length=17)
basically this information is coming from battlegroup api
$armory = new BattlenetArmory('EU','Defias Brotherhood');
$armory->setLocale('ru_RU');
// To reset back to default server locale
$armory->setLocale(FALSE);
//initialize the character to get the character object
$character = $armory->getCharacter('XXXX');
If you don't have a getter for the field characterData, you won't have access to it because it is a private property, unless you use Reflection:
$ref = new ReflectionClass( 'Character');
$prop = $ref->getProperty( 'characterData');
$prop->setAccessible( true);
$array = $prop->getValue( $yourCharacterObject);
echo $array['realm'];
This can be tested with a simple class that mimics the OPs:
class Character {
private $characterData;
public function __construct() {
$this->characterData['realm'] = 'Defias Brotherhood';
}
}
Currently, others have the proposed this solution:
$yourCharacterObject = new Character();
// echo $yourCharacterObject->characterData['realm'];
Which results in:
Fatal error: Cannot access private property Character::$characterData in X on line 11
However, Reflection will be able to grab the private value and display it without error, as shown in this demo, which uses the above class and code to print the desired property value.
Character is a class that you create instance from - for example:
$c = new Character();
since characterData is private you can't access it via $c->characterData
You need to use some public access function (like getCharacterData) or if you have access inside the object just changed it according to your needs

bad practice? what's it 'called'

in PHP, I'm considering doing something like this:
function foo(){
echo 'bar';
}
$fn = 'foo';
$fn();
It works, but is it considered bad practice?
I have an multidimensional array of elements that each have a corresponding function. I would like to store that function name, and call the corresponding functions for each element when traversing the array.
something like:
function render_el1(){ echo 'et';}
function render_el2(){ echo 'to';}
$elements = array(
'el_1' => array(
'name' => 'Element One'
, 'func' => 'render_el1'
)
, 'el_2' => array(
'name' => 'Element Two'
, 'func' => 'render_el2'
)
);
foreach($elements as $element => $options){
$fn = $options['func'];
echo '<h1>'.$options['name'].'</h1>';
if (function_exists($fn)) {
$fn();
}
}
Any comments to this approach is highly welcome, and I'd also like to know what this method is called in programming terms.
Not sure it is bad practice, but it makes your code hard to understand : to understand your short (5 lines) example, I've had to think :-(
Using call_user_func() and other functions of the same kind could have at least one advantage : looking at the code, one would immediatly understand you are calling a function in a way that's not the one we're generally used to.
You want to register functions into an array in your second example and then call them for what looks like a render process. This is similar to using function pointers in C (or paint event callbacks etc). It is an okay approach if you don't want to/can't use polymorphism (the feature that makes OOP worthwhile).
Your approach is simpler at that stage, but will probably get more bloated if you are adding more sophisticated code.

Categories