I'm looping over all the files in a directory. Now I want to get all the functions and classes defined in each of them. From there, I can examine them further using the ReflectionClass. I can't figure out how to get all the functions and classes defined in a file though.
ReflectionExtension looks the closest to what I want, except my files aren't part of an extension. Is there some class or function I'm overlooking?
Great question. get_declared_classes and get_defined_functions could be a good starting point. You would have to take note of what classes / functions are already defined when trying to determine what's in a given file.
Also, not sure what your end goal is here, but tools such as PHP Depend or PHP Mess Detector may do something similar to what you want. I'd recommend checking them out as well.
This is the best I could come up with (courtesy):
function trimds($s) {
return rtrim($s,DIRECTORY_SEPARATOR);
}
function joinpaths() {
return implode(DIRECTORY_SEPARATOR, array_map('trimds', func_get_args()));
}
$project_dir = '/path/to/project/';
$ds = array($project_dir);
$classes = array();
while(!empty($ds)) {
$dir = array_pop($ds);
if(($dh=opendir($dir))!==false) {
while(($file=readdir($dh))!==false) {
if($file[0]==='.') continue;
$path = joinpaths($dir,$file);
if(is_dir($path)) {
$ds[] = $path;
} else {
$contents = file_get_contents($path);
$tokens = token_get_all($contents);
for($i=0; $i<count($tokens); ++$i) {
if(is_array($tokens[$i]) && $tokens[$i][0] === T_CLASS) {
$i += 2;
$classes[] = $tokens[$i][1];
}
}
}
}
} else {
echo "ERROR: Could not open directory '$dir'\n";
}
}
print_r($classes);
Wish I didn't have to parse out the files and loop over all the tokens like this.
Forgot the former solutions prevents me from using reflection as I wanted. New solution:
$project_dir = '/path/to/project/';
$ds = array($project_dir);
while(!empty($ds)) {
$dir = array_pop($ds);
if(($dh=opendir($dir))!==false) {
while(($file=readdir($dh))!==false) {
if($file[0]==='.') continue;
$path = joinpaths($dir,$file);
if(is_dir($path)) {
$ds[] = $path;
} else {
try{
include_once $path;
}catch(Exception $e) {
echo 'EXCEPTION: '.$e->getMessage().PHP_EOL;
}
}
}
} else {
echo "ERROR: Could not open directory '$dir'\n";
}
}
foreach(get_declared_classes() as $c) {
$class = new ReflectionClass($c);
$methods = $class->getMethods();
foreach($methods as $m) {
$dc = $m->getDocComment();
if($dc !== false) {
echo $class->getName().'::'.$m->getName().PHP_EOL;
echo $dc.PHP_EOL;
}
}
}
Related
I'm looking for a way to include or require the content of a variable, instead of a file.
Normally, one can require/include a php function file with either of these:
require_once('my1stphpfunctionfile.php')
include('my2ndphpfunctionfile.php');
Suppose I wanted to do something like this:
$contentOf1stFFile = file_get_contents('/tmp/my1stphpfunctionfile.php');
$contentOf2ndFFile = file_get_contents('/tmp/my2ndphpfunctionfile.php');
require_once($contentOf1stFFile);
require_once($contentOf2ndFFile);
Now, in the above example, I have the actual function files which I am loading into variables. In the real world scenario I'm actually dealing with, the php code in the function files are not stored in files. They're in variables. So I'm looking for a way to treat those variables as include/require treats the function files.
I'm new to php so please forgive these questions if you find them foolish. What I'm attempting to do here does not appear to be possible. What I ended up doing was using eval which I'm told is very dangerous and should be avoided:
eval("?>$contentOf1stFFile");
eval("?>$contentOf2ndFFile");
Content of $contentOf1stFFile:
# class_lookup.php
<?php
class Lookup_whois {
// Domain name which we want to lookup
var $domain;
// TLD for above domain, eg. 'com', 'net', etc...
var $tld;
// Array which contains information needed to parse the whois server response
var $tld_params;
// Sets to error code if something fails
var $error_code;
// Sets user-friendly error message if something goes wrong
var $error_message;
// For internal use mainly - raw response from the whois server
var $whois_raw_output;
function Lookup_whois($domain, $tld, $tld_params) {
$this->domain = $domain;
$this->tld = $tld;
$this->tld_params = $tld_params;
}
function check_domain_spelling() {
if (preg_match("/^([A-Za-z0-9]+(\-?[A-za-z0-9]*)){2,63}$/", $this->domain)) {
return true;
} else {
return false;
}
}
function get_whois_output() {
if (isset($this->tld_params[$this->tld]['parameter'])) {
$query = $this->tld_params[$this->tld]['parameter'].$this->domain.'.'.$this->tld;
} else {
$query = $this->domain.'.'.$this->tld;
}
$server = $this->tld_params[$this->tld]['whois'];
if (!$this->check_domain_spelling()) {
$this->error_message = 'Domain name is not correct, check spelling. Only numbers, letters and hyphens are allowed';
return false;
}
if (!$server) {
$this->error_message = 'Whois server name is empty, please check the config file';
return false;
}
$output = array();
$fp = fsockopen($server, 43, $errno, $errstr, 30);
if(!$fp) {
$this->error_code = $errno;
$this->error_message = $errstr;
fclose($fp);
return false;
} else {
sleep(2);
fputs($fp, $query . "\n");
while(!feof($fp)) {
$output[] = fgets($fp, 128);
}
fclose($fp);
$this->whois_raw_output = $output;
return true;
}
}
function parse_whois_data() {
if (!is_array($this->whois_raw_output) && Count($this->whois_raw_output) < 1) {
$this->error_message = 'No output to parse... Get data first';
return false;
}
$wait_for = 0;
$result = array();
$result['domain'] = $this->domain.'.'.$this->tld;
foreach ($this->whois_raw_output as $line) {
#if (ereg($this->tld_params[$this->tld]['wait_for'], $line)) {
if (preg_match($this->tld_params[$this->tld]['wait_for'],$line)) {
$wait_for = 1;
}
if ($wait_for == 1) {
foreach ($this->tld_params[$this->tld]['info'] as $key => $value) {
$regs = '';
if (ereg($value.'(.*)', $line, $regs)) {
if (key_exists($key, $result)) {
if (!is_array($result[$key])) {
$result[$key] = array($result[$key]);
}
$result[$key][] = trim($regs[1]);
} else {
$result[$key] = trim($regs[1]);
$i = 1;
}
}
}
}
}
return $result;
}
}
?>
Are there any other alternatives?
No there are no other alternatives.
In terms of security there is no difference if you include() a file or eval() the content. It depends on the context. As long as you only run your own code there is nothing "dangerous".
I want to return an error message when two classes provided by the user/ developer don't exist.
core/model/Conan.php,
namespace core\model;
class Conan
{
var $bodyBuild = 'extremely muscular';
var $birthDate = 'before history';
var $skill = 'fighting';
public function methodConan()
{
return 'The methodConan from namespace core\model';
}
}
local/model/Conan.php,
namespace local\model;
class Conan
{
var $bodyBuild = 'very skinny';
var $birthDate = '1963';
var $skill = 'comedy';
public function methodConan()
{
return 'The methodConan from namespace local\model';
}
}
index.php,
define ('WEBSITE_DOCROOT', str_replace('\\', '/', dirname(__FILE__)).'/');
include 'core/helper/BaseClassAutoloader.php';
// Autoload the core & local classes.
$autoloader = new BaseClassAutoloader([
'local/model/',
'core/model/'
]);
if (class_exists('\foo\model\Conan'))
{
echo 'from local';
}
else
{
if(class_exists('\boo\model\Conan'))
{
echo 'from core';
}
else
{
echo 'both don\'t exist';
}
}
I suppose to get 'both don\'t exist' as the result, I get this error instead,
Fatal error: Cannot redeclare class local\model\Conan in
C:\wamp\www...\local\model\Conan.php
on line 8
It doesn't really make sense!
Is it something wrong with my autoload below??
autoload class,
class BaseClassAutoloader
{
public function __construct($directory)
{
$this->directory = $directory;
spl_autoload_register(array($this,'getClass'));
}
private function getClass($class_name)
{
if(is_array($this->directory)): $mainDirectories = $this->directory;
else: $mainDirectories = array($this->directory); endif;
$subDirectories = [];
$namespace = "\\";
$isNamespace = false;
$parts = explode($namespace, $class_name);
if(strpos($class_name, $namespace) !== false)
{
$isNamespace = true;
}
$fileNameName = end($parts).'.php';
foreach($mainDirectories as $pathDirectory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator(WEBSITE_DOCROOT.$pathDirectory), // Must use absolute path to get the files when ajax is used.
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileObject)
{
if ($fileObject->isDir())
{
$pathnameReplace = str_replace('\\', '/', $fileObject->getPathname());
$array = explode("/",$pathnameReplace);
$folder = end($array);
if($folder === '.' || $folder === '..') {continue;}
$subDirectories[] = preg_replace('~.*?(?=core|local)~i', '', str_replace('\\', '/', $fileObject->getPathname())) .'/';
}
}
}
$merged_directories = array_merge($mainDirectories,$subDirectories);
foreach($merged_directories as $pathDirectory)
{
if(file_exists(WEBSITE_DOCROOT.$pathDirectory.$fileNameName))
{
include WEBSITE_DOCROOT.$pathDirectory.$fileNameName;
if($isNamespace === false) if (class_exists($class_name)) break;
}
}
}
}
try using require_once instead of include
try using include_once instead of include at line 62 oh BaseClassAutoLoader.php.
For future googlers, If you don't control the files inclusion process like say inside a framework, and you need an urget fix you can just wrap the class definition inside a check, and it will prevent php from throwing the above error.
if (!class_exists('AwesomCLass')) {
class AwesomCLass extends ParentClass {
// Do awesome stuff here all day long
}
}
For those who are getting fatal error while using class_exists can use a alternative , i found it useful.
$classname = 'Myclass';
if( class_exists( $classname ) ) {
echo "class exists";
} else {
echo "class not exists";
}
instead, we can use as given below
$classname = 'Myclass';
if( in_array($classname, get_declared_classes() ) ) {
echo "class exists";
} else {
echo "class not exists";
}
i create this function for search resume file from directory, if resume is available then function return full path, problem is function return nothing if i use "return", if i use "echo" then it will print right path
function search_resume($resume,$dir="uploads/resumes")
{
$root = scandir($dir);
foreach($root as $value)
{
/* echo $value."<br/>"; */
if($value === '.' || $value === '..') {continue;}
if(is_file("$dir/$value"))
{
if($value==$resume)
{
$path="$dir/$value";
return $path;
}
}
else
{
search_resume($resume,"$dir/$value");
}
}
}
A very typical, basic problem with recursive functions: you need to return recursive calls as well, they're not going to return themselves.
...
else {
$path = search_resume($resume,"$dir/$value");
if ($path) {
return $path;
}
}
The following code gives me the following error, even thought the variable 'cache_path' has been defined at the top.
<b>Notice</b>: Undefined variable: cache_path in <b>C:\Users\Jan Gieseler\Desktop\janBSite\Scripts\Index.php</b> on line <b>20</b><br />
Here is the code;
header('Content-type: application/x-javascript');
$cache_path = 'cache.txt';
function getScriptsInDirectory(){
$array = Array();
$scripts_in_directory = scandir('.');
foreach ($scripts_in_directory as $script_name) {
if (preg_match('/(.+)\.js/', $script_name))
{
array_push($array, $script_name);
}
}
return $array;
}
function compilingRequired(){
if (file_exists($cache_path))
{
$cache_time = filemtime($cache_path);
$files = getScriptsInDirectory();
foreach ($files as $script_name) {
if(filemtime($script_name) > $cache_time)
{
return true;
}
}
return false;
}
return true;
}
if (compilingRequired())
{
}
else
{
}
?>
What could I do to fix this?
EDIT: I've thought that PHP makes variables which are in the 'main' scope available for functions, too. I guess, I was wrong. Thanks for the help.
I've fixed it by using the 'global' statement.
In order to fully understand this you will have to read up on Variable Scope, good luck!
header('Content-type: application/x-javascript');
$cache_path = 'cache.txt';
function getScriptsInDirectory(){
$array = Array();
$scripts_in_directory = scandir('.');
foreach ($scripts_in_directory as $script_name) {
if (preg_match('/(.+)\.js/', $script_name))
{
array_push($array, $script_name);
}
}
return $array;
}
function compilingRequired($cache_path){ //<-- secret sauce
if (file_exists($cache_path))
{
$cache_time = filemtime($cache_path);
$files = getScriptsInDirectory();
foreach ($files as $script_name) {
if(filemtime($script_name) > $cache_time)
{
return true;
}
}
return false;
}
return true;
}
if (compilingRequired($cache_path)) //<-- additional secret sauce
{
}
else
{
}
?>
Your $cache_path is not known inside functions. Either give it as a parameter like MonkeyZeus suggests or use a global $cache_path inside your function.
function compilingRequired(){
global $cache_path; // <------- like this
if (file_exists($cache_path))
{
$cache_time = filemtime($cache_path);
$files = getScriptsInDirectory();
foreach ($files as $script_name) {
if(filemtime($script_name) > $cache_time)
{
return true;
}
}
return false;
}
return true;
}
there is a project which I need to extend. All classes are in seperate files, I need to extend some of the classes without rewriting existing code in other files. My idea was to use namespaces but I fail. Here is an example:
I've renamed the original A.php class file to A_Original.php:
class A
{
public function hello()
{
echo "hello world from Class A\n";
}
}
Then created a new A.php:
namespace AOriginal {
include 'A_Original.php';
}
namespace {
class A
{
public function hello()
{
echo "hello world from Class A Extended\n";
}
}
}
This fails because on including the original A_Original.php file the class is dumped to the global scope (thus ignoring the namespace command).
I can not modify the existing code inthe A_Original.php file, but renaming is ok.
The other project files (whic I cannot modify) use a require "A.php".
How to accomplish this?
You can extend a class without modifying its existing behaviour:
class A {
public function foo(){
}
}
class MySubClassOfA extends A {
public function bar(){
}
}
You can add your own methods to MySubClassOfA, i.e. bar(). You can call the foo method on MySubClassOfA and it's behaviour is the same, unless you define a method called foo in MySubClassOfA.
I guess that you have no choice but add the single line of "namespace xxx;" code on top of all your files. The following PHP CLI script may be useful.
<?php
function convert($namespace, $srcdir, $dstdir)
{
try
{
$files = glob("$srcdir/{*,.*}", GLOB_BRACE);
if ( ! file_exists($dstdir) && ! mkdir($dstdir) )
{
throw new Exception("Cannot create directory {$dstdir}");
}
if ( ! is_dir($dstdir) )
{
throw new Exception("{$dstdir} is not a directory");
}
foreach ( $files as $f )
{
extract(pathinfo($f)); // then we got $dirname, $basename, $filename, $extension
if ( $basename == '.' || $basename == '..' )
{
continue;
}
if ( is_dir($f) )
{
$d = $dstdir. substr($f, strlen($srcdir));
convert($namespace, $f, $d);
continue;
}
print "processing {$f} ... ";
if ( ($s = file_get_contents($f)) === FALSE )
{
throw new Exception("Error reading $f");
}
if ( preg_match("/^\s*namespace\s+\S+;/m", $s) )
{
print "already has namespace, skip";
}
else
{
$lines = preg_split("/(\n|\r\n)/", $s);
$output = array();
$matched = FALSE;
foreach ( $lines as $s )
{
$output[] = $s;
// check if this is a PHP code?
if ( ! $matched && preg_match('/<(\?(php )*|%)/', $s) )
{
$matched = TRUE;
print "insert namespace ... ";
$output[] = "namespace {$namespace};";
}
}
if ( file_put_contents("{$dstdir}/{$basename}" , implode("\n", $output)) === FALSE )
{
throw new Exception("Cannot save file {$dstdir}/{$basename}");
}
if ( ! $matched )
{
print ("not a PHP file, skip.");
}
else
{
print "done!";
}
}
print "\n";
}
}
catch (Exception $e)
{
print 'Error: '. $e->getMessage() .' ('. $e->getCode() .')' ."\n";
}
}
extract($_SERVER);
if ( $argc < 4 )
{
?>
Usage: php -F <?=$argv[0]?> <namespace> <source_dir(s)> <dst_dir>
Convert PHP code to be namespace-aware
<?
return;
}
else
{
for ( $i = 2; $i < $argc - 1; $i++ )
{
convert($argv[1], $argv[$i], $argv[$argc-1]);
}
}
?>
How about eval()?
New A.php
$lines = file('a_original.php');
array_unshift($lines, 'namespace AO;?>');
$string = implode(chr(13).chr(10), $lines);
eval($string);
class A extends AO\A
{
public function hello()
{
parent::hello();
echo "hello world from Class A Extended\n";
}
}