Finding PHP dependencies - php

Are there any tools that can list the names of classes used by a PHP file?
For example, if I ran it on this file:
<?
class Test {
public function __construct(Obj1 $x) {
$y = new Obj2();
$str = "Obj3";
$z = new $str();
}
}
?>
it would report "Obj1" and "Obj2". If it were really smart it might report "Obj3" as well, but that's not essential.
I'm trying to package up some code, and I want some help making sure that I didn't miss any dependencies.
There's something called PHP_Depend, which can graph the number of dependencies, but can't report what they are.
UPDATE: I didn't find a real solution, but I figured out something close enough for my purposes. You can tokenize a file, and search for all T_STRING tokens. This will give you all class names mentioned in the file. It will also give you other things, like function names and constants. But if your class names are easy to distinguish (e.g. they have initial caps), then this shouldn't be a problem.
$contents = file_get_contents($path);
$tokens = token_get_all($contents);
foreach ($tokens as $token) {
if (is_array($token) && $token[0] === T_STRING) {
echo $token[1]."\n";
}
}

There's PHPXref, which is a PHP cross referencing document generator.

Related

How to catch php spelling errors with static analyzers?

I was made a mistake yesterday and spent hours to fix it. I have method like this
{
if (isset($data['y'])) {
$this->y = $data['y'];
}
if (isset($data['z'])) {
$this->y = $data['z']; // <- error here
}
}
And yes, I assign $this->y two times instead of one y and one z :-(
So question: can any static analyze tools catch such errors? I have PHP Storm and Rector, PHPStan, PHP CS Fixer in my CI toolchain but they missed this error.
This isn't so much an answer, but it's too complicated to put in a comment.
As the comments pointed out, there's simply no way for a robot to figure out that what you wrote isn't what you intended. The easiest solution is to live with human frailty and debug your code.
But that doesn't mean you can't write your code to better express your intent. Consider:
{
$fields = ['x', 'y'];
foreach ($fields as $field) {
if (isset($data[$field]) {
$this->$field = $data[$field];
}
}
}
Now you have expressed in you code that you only want to assign like-named fields.

How to get variables from a different PHP file without executing the code?

I am trying to loop through all the php files listed in an array called $articleContents and extract the variables $articleTitle and $heroImage from each.
So far I have the following code:
$articleContents = array("article1.php", "article2.php"); // array of all file names
$articleInfo = [];
$size = count($articleContents);
for ($x = 0; $x <= $size; $x++) {
ob_start();
if (require_once('../articles/'.$articleContents[$x])) {
ob_end_clean();
$entry = array($articleContents[$x],$articleTitle,$heroImage);
array_push($articlesInfo, $entry);
}
The problem is, the php files visited in the loop have html, and I can't keep it from executing. I would like to get variables from each of these files without executing the html inside each one.
Also, the variables $articleTitle and $heroImage also exist at the top of the php file I'm working in, so I need to make sure the script knows I'm calling the variables in the external file and not the current one.
If this is not possible, can you please recommend an alternative method?
Thanks!
Don't do this.
Your PHP scripts should be for your application, not for your data. For your data, if you want to keep it file-based, use a separate file.
There are plenty of formats to choose from. JSON is quite popular. You can use PHP's built-in serialization as well, which has support for more PHP-native types but is not as portable to other frameworks.
A little hacky but seems to works:
$result = eval(
'return (function() {?>' .
file_get_contents('your_article.php') .
'return [\'articleTitle\' => $articleTitle, \'heroImage\' => $heroImage];})();'
);
Where your_article.php is something like:
<?php
$articleTitle = 'hola';
$heroImage = 'como te va';
The values are returned in the $result array.
Explanation:
Build a string of php code where the code in your article scripts are wrapped inside a function that returns an array with the values you want.
function() {
//code of your article.php
return ['articleTitle' => $articleTitle, 'heroImage' => $heroImage];
}
Maybe you must do some adaptations to the strings due <?php ?> tags placements.
Anyway, this stuff is ugly. I'm very sure that it can be refactored in some way.
Your problem (probably) comes down to using parentheses with require. See the example and note here.
Instead, format your code like this
$articlesInfo = []; // watch your spelling here
foreach ($articleContents as $file) {
ob_start();
if (require '../articles/' . $file) { // note, no parentheses around the path
$articlesInfo[] = [
$file,
$articleTitle,
$heroImage
];
}
ob_end_clean();
}
Update: I've tested this and it works just fine.

Search for function call in php in order to generate a translation file

I am developing a php website that needs to be multilingual.
For this reason, I implemented a translation function which has the following header:
function t($string, $replace_pairs = array(), $language = NULL)
Basically, this function is called like this in multiples files of my project:
echo '<p>' . t('Hello world!') . '</p>';
$hello_String = t("Hello #name!", array('#name'=>$username));
I haven't generated the translation strings yet and I would like to generate multiple translation file automatically (one for each language).
What I am looking for is a bash program (or a single command, using grep for example) that would look for every call to this t() function and generate a php file with the following structure:
<?php
/* Translation file "fr.php" */
$strings['fr']['Hello world!'] = '';
$strings['fr']['Hello #name!'] = '';
Has anyone ever encountered this situation and could help me with this ?
Thank you very much.
Kind regards,
Matthieu
Yes, you're not exactly the first to come across this. :)
You can use the venerable gettext system for this, you don't need to invent your own functions. Then you'd get to use xgettext, which is a command line utility to extract strings using the _() function.
If you want to roll your own system for whatever reason, your best bet is to write a PHP script which uses token_get_all to tokenize the source, then go through the tokens and look for T_FUNCTIONs with the value t.
No need to reinvent the wheel
Drupal uses the same t() function for its localization and the potx module is your friend.
If you don't already have, or want to install, a drupal instance you can look at the potx.inc file and reuse it in your script.
Here is the complete API documentation for the translation template extractor.
Try this script http://pastie.org/4568713
Usage:
php script.php ./proj-directory lang1 lang2 lang3
This creates lang1.php, lang2.php, lang3.php files in ./lang directory
You need two functions:
1- scan directories for php files. like this
2- match your t function, grep string and generate the language file. like
function genLang($file) {
$content = file_get_contents($file);
preg_match(...);
foreach(...){
echo(...);
}
}
Yii framework also uses same functionality,
see their MessageCommand class
https://github.com/yiisoft/yii/blob/master/framework/cli/commands/MessageCommand.php#L125
What you need is a (very simple) "template system", but there are two instances of templating in your problem.
Transform "Hello $X!" into "Hello Jonh!" or "Hello Maria!", setting $X. (PHP do this for you in string declarations).
Select the adequate template: "Hello $X!" for english, "¡Hola $X!" for spanish.
The item 1 is the more simple, but the algorithm order is 2,1 (item 2 them item 1).
For this simple task you not need a regular expression (to reinvent the "string with place-holder" of PHP).
Illustrating
For item 1, the simplest way is to declare a specialized function to say "Hello",
// for any PHP version.
function template1($name) { return "<p>Hello $name!</p>";}
print template1("Maria");
For item 2 you need a generalization, that PHP do also for you, by a closure,
header('Content-Type: text/html; charset=utf-8'); // only for remember UTF8.
// for PHP 5.3+. Use
function generalTemplate1($K) {
// $K was a literal constant, now is a customized content.
return function($name) use ($K) {return "<p>$K $name!</p>"; };
}
// Configuring template1 (T1) for each language:
$T1_en = generalTemplate1('Hello'); // english template
$T1_es = generalTemplate1('¡Hola'); // spanish template
// using the T1 multilingual
print $T1_en('Jonh'); // Hello Jonh!
print $T1_es('Maria'); // ¡Hola Maria!
For more templates, use generalTemplate2(), generalTemplate3(), etc.; $T2_en, $T2_es, $T2_fr, $T3_en, $T3_es, etc.
Solution
Now, for pratical use, you like to use arrays... Well, there are a datastructure problem,
and more 1 level of generalization. The cost is variable-name parser for place-holders. I used simple regular expression with preg_replace_callback().
function expandMultilangTemplate($T,$K,$lang,$X) {
// string $T is a template, a HTML structure with $K and $X placeholders.
// array $K is a specific language constants for the template.
// array $lang is the language, a standard 2-letter code. "en", "fr", etc.
// array $X is a set of name-value (compatible with $T placeholders).
// Parsing steps:
$T = str_replace('{#K}',$K[$lang],$T); // STEP-1: expand K into T with lang.
// STEP-2: expand X into T
global $_expMultTpl_X; // need to be global for old PHP versions
$_expMultTpl_X = $X;
$T = preg_replace_callback(
'/#([a-z]+)/',
create_function(
'$m',
'global $_expMultTpl_X;
return array_key_exists($m[1],$_expMultTpl_X)?
$_expMultTpl_X[$m[1]]:
"";
'
),
$T
);
return $T;
}
// CONFIGURING YOUR TEMPLATE AND LANGUAGES:
$T = "<p>{#K} #name#surname!</p>";
$K = array('en'=>'Hello','es'=>'¡Hola');
// take care with things like "!", that is generic, and "¡" that is not.
// USING!
print expandMultilangTemplate(
$T, $K, 'en', array('name'=>'Jonh', 'surname'=>' Smith') );
print expandMultilangTemplate($T, $K, 'es', array('name'=>'Maria'));
I tested this script with PHP5, but it runs with older (PHP 4.0.7+).
About "multilingual files": if your translations are into files, you can use somthing like
$K = getTranslation('translationFile.txt');
function getTranslation($file,$sep='|') {
$K = array();
foreach (file($file) as $line) {
list($lang,$words) = explode($sep,$line);
$K[$lang]=$words;
}
}
and a file as
en|Hello
es|¡Hola
Simplest with PHP 5.3
If you using PHP 5.3+, there are a simple and elegant way to express this "simplest multilingual template system",
function expandMultilangTemplate($T,$K,$lang,$X) {
$T = str_replace('{#K}',$K[$lang],$T);
$T = preg_replace_callback(
'/#([a-z]+)/',
function($m,$X=NULL) use ($X) {
return array_key_exists($m[1],$X)? $X[$m[1]]: '';
},
$T
);
return $T;
}

Is it possible to get the statements within a method in PHP?

function mainFunction() {
functionA(5, "blah");
functionB("ok", "whatever");
}
How to write a function GetFunctions that returns the functions within mainFunction?
How to call them with the parameters given in mainFunction?
How to call them as follows?
foreach (GetFunctions(mainFunction) as $function) {
print "Calling function $function: ";
call($functionA); // called with parameters(5, "blah")
}
Working in PHP 5.2.8
EDIT: OK, here's a more complete explanation. I tried to keep it simple to make it easy to understand, but apparently that wasn't a good idea.
The goal is to call each assertion within a given static method. I am writing a testing framework. Each assertion returns true or false.
I am calling the methods as follows.
$methods = get_class_methods('LibraryTests');
foreach ($methods as $method) {
if ( StartsWith($method, 'Test') ) {
print "calling: " . $method . ": ";
call_user_func('LibraryTests::' . $method);
}
}
The above code calls each method within the class, but I want to call each assertion individually and track the result (true/false). CallAssertion is supposed to call each assertion (such as TestUnit::AssertEqual(GetFormattedHour(5), "5 PM");). This is the method that I am asking about.
Here is the class:
class LibraryTests extends TestUnit {
static $success = 0;
static $failure = 0;
static $total = 0;
static function CallAssertion($assertion) {
self::$total += 1;
if ($assertion) { self::$success += 1; }
else { self::$failure += 1; }
}
static function TestGetFormattedHour() {
TestUnit::AssertEqual(GetFormattedHour(5), "5 PM");
TestUnit::AssertEqual(GetFormattedHour(16), "4 PM");
}
So, the question is, how to write CallAssertion?
You can't.
Instead, create a class and use reflection to get its methods.
Regardless, you'll want to figure out why this is necessary and see if there is an entirely different approach you can use.
(If this is for debugging purposes, you can use debug_backtrace to inspect but its purpose is not for calling functions as you have described in your question.)
Hmm, what problem are you actually trying to solve. To me it sounds like you're trying to inspect the call stack at runtime. If so, I'd suggest just using debug_backtrace() (src).
I wouldn't suggest using that function in production as much though, as it's a rather heavy hit on your code.
One possibility would be to do a file_get_contents on the PHP file that contains main_function, then go through it to parse out main_function and the functions it calls. Of course, I don't know your situation so that might not work.
You can do this with:
http://php.net/manual/en/function.token-get-all.php
Probably a bad idea, but good luck!

Making Smarty supporting multiple languages

first of all, let me tell ya, that I'm from Germany. So my English will not be very well. Please forgive me. =P
I'm about to develop a multilingual website with PHP5. In order to seperate the presentation layer from the business logic, I'm using the SmartyTemplateEngine (v3.0.8). To make it multilingual I had to edit this SmartyPlugin sometimes. But finally it is working for me. I'm using it that way:
{lang}language.string{/lang}.
In the language file I have:
language.string = <![CDATA[Hello world!]]> (So it says Hello world!)
Works fine. But I want to expend the script a bit further. I want to pass a variable to the language string. Something like this:
{lang s=$userName}language.string{/lang}
In the language file I want to have:
language.string = <![CDATA[Hello %s!]]> (So it says Hello username!)
I tried to find my solution with Google, but I didn't find something good. As far as I'm not a professional in PHP, I'm not capable of editing it myself. I hope somebody can help me with that. It is bothering me quite a while...
Greets,
Basti
Well, I'm not using this plugin, but had to do the same thing. I figuered out that for my needs the following was the best solution:
In an XML file I define my strings (this example is xml/en/content.xml):
<translations>
<translation id="hello_world"><![CDATA[Hello ##username##!]]></translation>
<translation id="how_are_you"><![CDATA[How are you?]]></translation>
</translations>
In my Localizer class I initialize these translations and save them in an array. The translate function gets the ID string from smarty, searches for the id in its translations and for any ##string## text. These ##...## will be replaced with variables already assigned to smarty.
class Localizer {
private static $translations = array();
public static function init($language) {
$temp_content = simplexml_load_file('xml/' . $language . '/content.xml');
foreach ($temp_content as $key => $value){
self::$translations[(string)$value['id']] = (string)$value;
}
}
public static function translate($params, $name, $smarty) {
$translation = '';
if( ! is_null($name) && array_key_exists($name, self::$translations)) {
// get variables in translation text
$translation = self::$translations[$name];
preg_match_all('/##([^#]+)##/i', $translation, $vars, PREG_SET_ORDER);
// replace with assigned smarty values
foreach($vars as $var) {
$translation = str_replace($var[0], $smarty->getTemplateVars($var[1]), $translation);
}
}
return $translation;
}
}
Now you have to tell smarty which function it should use. This could be your index.php:
include('Localizer.class.php');
Localizer::init('en');
$smarty->registerPlugin('block', 'translate', array('Localizer', 'translate'), true);
To use the translations, first, I assign the username:
$smarty->assign('username', $username);
In the template file:
{translate}hello_world{/translate}
Hope this helps, greetings from austria :)

Categories