What is the best way to obtain the class name for a requested PHP file if my unknown class follows the following pattern?
index.php
<?php
require_once("startup.php");
class NotIndex extends View
{
public function getView()
{
echo "<pre>View Data</pre>";
}
}
?>
For testing I want to create an instance of the NotIndex class within the startup.php script.
startup.php
<?php
require_once("view.php");
//Include the script again to bring in the class definition
include($_SERVER['SCRIPT_FILENAME']);
//Make sure I'm seeing any errors
error_reporting(E_ALL);
ini_set('display_errors','On');
//If I knew the class I could just do this
$Page = new NotIndex();
$Page->getView();
//Todo: Find the class from $_SERVER['SCRIPT_FILENAME']
//Todo: Once we find the class create an instance and call getView()
exit;
?>
view.php
<?php
abstract class View
{
abstract function getView();
}
?>
I'm thinking a regular expression solution might be simple since the desired data will always be between 'class' and 'extends'. I don't have much experience formulating the desired expression and would appreciate some insight.
The following solution may or may not be a good way of doing this, but it did lead me to a non-regular expression solution which is always a plus. If anyone can improve on this, then I will gladly give them credit for both my questions above. Credit actually goes to #Nik answering Stack Overflow question Extracting function names from a file (with or without regular expressions). If I do this in my startup script then I can probably count on the unknown class being the last one in the array. However, I plan on integrating this into my framework and would like a more complete solution.
Since the class name is unknown, you may ask how can we be sure? Well, the abstract class View comes right before the unknown class in the array. Any solution would surely have to check for this. So without guarantee the last class is the needed class, I probably need to traverse backwards in the array until I have identified the abstract View class and the previous value in the array would be the unknown subclass.
startup.php
<?php
require_once("view.php");
//Include the script again to bring in the class definition
include($_SERVER['SCRIPT_FILENAME']);
//Make sure I'm seeing any errors
error_reporting(E_ALL);
ini_set('display_errors','On');
//If I knew the class I could just do this
$Page = new NotIndex();
$Page->getView();
//Todo: Find the class from $_SERVER['SCRIPT_FILENAME']
//Todo: Once we find the class create an instance and call getView()
$Classes = get_declared_classes();
$ViewObject = array_pop($Classes);
$NewPage = new $ViewObject();
$NewPage->getView();
exit;
?>
View Data
Please expand any opinions on this usage.
So this solution only works if I include my abstract View class immediately before I include the unknown subclass script again via include($_SERVER['SCRIPT_FILENAME']);. So this implies the get_declared_classes() function adds the classes in the order they were defined or included. This could be a problem.
<?php
require_once("view.php");
//Include the script again to bring in the class definition
include($_SERVER['SCRIPT_FILENAME']);
include("killsolution.php");
//Make sure I'm seeing any errors
error_reporting(E_ALL);
ini_set('display_errors','On');
//If I knew the class I could just do this
$Page = new NotIndex();
$Page->getView();
//Todo: Find the class from $_SERVER['SCRIPT_FILENAME']
//Todo: Once we find the class create an instance and call getView()
$Classes = get_declared_classes();
$ViewObject = array_pop($Classes);
$NewPage = new $ViewObject();
$NewPage->getView();
exit;
?>
Fatal error: Call to undefined method
killsolution::getView()
(This question is a subset of another open Stack Overflow question, What are some PHP object-oriented framework initialization techniques?)
Use __autoload().
If you must continue down this path then you can use PHPs Tokenizer to obtain the class name from a file path. Regex is not really the answer to this problem.
A friend of mine has written Überloader (A brute-force autoloader for PHP5.) which uses this very technique when it indexes class files. The _check_file() method from it will be of particular interest to you.
Using Überloader itself may even be a far better solution to your problem. It maintains a cache of class names and their relevant file paths so it is quicker than using the Tokenizer on each page load. Überloader is designed for legacy projects that have not planned or thought about their class naming conventions or file structures.
The following solution allows you to iterate over classes defined at runtime and in my case search for a user defined subclass of View. The get_declared_classes() function will return both system and user defined classes in the order they were included. My system contained over 120 system classes before the requested script class was included. For this reason I iterate backwards in the returned array.
startup.php
//Include the script again to bring in the class definition
include($_SERVER['SCRIPT_FILENAME']);
//Make sure I'm seeing any errors for testing
ini_set('display_errors','On');
//Don't assume we will find a subclass of View
$RequestClass = false;
//Iterate over declared classes starting from the last defined class
for($i = count($classes = get_declared_classes()) - 1; $i >= 0; $i--)
{
//Test if unknown class is a subclass of abstract View
if(get_parent_class($classes[$i]) == 'View')
{
//Store the name of the subclass of View
$RequestClass = $classes[$i];
//We found our unknown View
break;
}
}
if($RequestClass)
{
//We can now instantiate the class or do something useful
$Page = new $RequestClass();
$Page->getView();
}
exit;
Depending on when you call the get_declared_classes() function, the array could change. I know my unknown class will always be a subclass of View, therefore I can identify it no matter how many classes are defined afterwards. However, just to elaborate, if we have an unknown class that does not extend another class, then we can still obtain the last defined class using:
$LastClass = array_pop(get_declared_classes());
So we can always find the requested page's class if we search for it immediately after it is defined. Theres alot of ways this will break, but most people I suspect will never need this functionality anyways.
Related
Ok, so I am building a web application relying on Zend PHP....
Before having to read everything to describe my nested functions, what I need is to be able to call a function from one class to another, which neither are extended upon another, are already extending a db constructor, which are all independently separate files called by one master initializing script .... (?) ... Thanks in advance, and there is a better example below as to what I mean.
My HTML Page calls a "master" include list which initializes and creates all the instances of all my classes so that all pages have common access to the functions. i.e. require('app_init.php');
Here is the most important excerpt of app_init.php:
require_once('class-general.php');
require_once('class-users.php');
require_once('class-identities.php');
$general = new General();
$users = new Users($db);
$iden = new Iden($db);
---class-general.php
$general is my basis for stupid common functions I use, as well as the DB constructor that all classes can be extended from.
----class-users.php
<?php
class Users extends General{
public function getUserID(){....random block of auth code.... return $randomID#; }
}?>
-----class-identities.php
<?php
class Iden extends General{
public function do_random_change_to_db($with_me){
....random prepared function using $with_me....
$this->logger->log("Someone with UserID: ". /*((?$this?) HERE:)*/ FIXME->getUserID() . " did something : ".$with_me ."." , Zend_Log::INFO);
$success="gucci";
return $success;
}
}?>
Right now, I am being tossed a PHP error for
Fatal error: Call to undefined method Iden::getUserID() in ...`
What would be the best way to go about this? I've tried to include one class file with the other one, but i dont exactly want to create a $FIXME= new Users(); either to save on memory space.
I also honestly would prefer to not extend any more classes off another at this time.
Thank you in advance.
If the getUserID method does not depend on any instance state (and it doesn't look like it does, though you haven't made it entirely clear), making it static will allow you to call it like so:
Users::getUserID();
If it does depend on instance state, you will need to call it on an instance of the Users class.
It seems to me that General's methods should actually be static as well, or perhaps even be free functions outside of a class. Remember: classes are used to encapsulate state. If there's no state that needs to be encapsulated, use class (static) methods or simple functions. Do not needlessly complicate your code by introducing objects and inheritance in which those paradigms don't make sense.
I've been creating a script using PHP and seem to have hit a brick wall at the moment with it. I'm trying to call a variable from another file which is within a class, so I went about it by including the file within the original and then instantiating the class to call the variable. But the entire thing is within a recursive function and I get Fatal error: Cannot redeclare class JVersion. I've pasted part of the code below:
...
function functionname($path)
{
...
define('_JEXEC', true);
require_once ($path ."libraries/cms/version/version.php");
$test_class = new JVersion();
$jma_ver = $test_class->getShortVersion();
...
functionname($path);
...
}
This is how the part of the code within the version.php looks like
defined('_JEXEC') or die;
final class JVersion
{
public $RELEASE = '2.5';
public $DEV_LEVEL = '1';
public function getShortVersion()
{
return $this->RELEASE.'.'.$this->DEV_LEVEL;
}
Is there a way I can overcome the "Fatal error: Cannot redeclare class JVersion" error ?
If I understand the problem correctly (i.e. you have many classes named JVersion that you need to use), then I'm not sure of any way around this problem without namespacing your JVersion classes in some way. Either give them distinct names or put them in different PHP namespaces.
This sounds more like an architectural problem, really; the code you're using doesn't seem to provide a way of getting its version in a disposable manner (without setting yourself up for conflicts like this). Ideally, you should just have a global JVersion class that can be insantiated with the appropriate version numbers; this way there'd be no class name conflicts.
I'm searching for following issue i have.
The class file names of our project are named logon.class.php
But the interface file for that class is named logon.interface.php
My issue i have is that when the autoload method runs I should be able to detect if it is a class call or an interface call.
<?php
function __autoload($name){
if($name === is_class){
include_once($name.'class.php');
}elseif ($name === is_interface){
include_once($name.'interface.php');
}
}
?>
You can use ReflectionClass::isInterface to determine if the class is an interface.
$reflection = new ReflectionClass($name);
if ($reflection->isInterface()){
//Is an interface
}else{
//Not an interface
}
In your case, you would probably have to use file_exist first on $name.interface.php and $name.class.php to determine if they exist, require the one that exists, then check if it's an interface.
However, my opinion is that this might cause problems down the track. What if you have MyClass.class.php and MyClass.interface.php?
You should have some naming conventions for your classes and interfaces e.g. your class name is logon and interface name logon_interface, then you can easily differentiate between the two. For example, explode $name by underscore and check if last element is interface.
To avoid class name clashes you can use namespaces. Check The PSR-0 specifications.
Also check this post. If you read the contents of the file before including it, you can tokenize it and figure if the file contains an Interface or a class, without actually loading the class/interface. Keep in mind that interfaces and classes can't have the same name
Note: Using this method you can actually change the name of the interface at runtime (before loading the class, although that does seem very good practice)
I have a php file which has multiple classes in it. I noticed that __autoload is being called when I instantiate any of the classes, even after the 'package' file has been autoloaded. This is worrying me, because surely if the initial 'package' .php file has been loaded, it is unnecessary overhead for __autoload when the classes have already been loaded.
I probably made a hash of explaining that, so here is an example:
<?php
class Class1{};
class Class2{};
?>
Then in another file:
<?php
new Class1;
new Class2;
?>
__autoload will be used for both Class1 AND Class2's instantiation... even though they are housed in the same file.
Is there a way to get around this?
Sorry if my explanation isn't very good, I'd really appreciate any help or tips.
PHP's autoload should only be called if a class doesn't exist. In other words, for the most basic example, it uses the same logic as:
if( !class_exists("Class1") )
require "path\Class1.php";
If you are finding otherwise, I would check to be sure you are doing everything correctly and report a bug.
From PHP.net/autoload (important documentation highlighted):
In PHP 5, this is no longer necessary.
You may define an __autoload function
which is automatically called in case
you are trying to use a
class/interface which hasn't been
defined yet. By calling this
function the scripting engine is given
a last chance to load the class before
PHP fails with an error.
Bug in formatting, but the emphasis was on "which hasn't been defined yet". "Defined" occurs when a class is compiled (in most cases when a file containing said class is included).
__autoload is definitely NOT called the second time, when Class2 got defined as result of the first call.
First classes.php
<?php
class Class1 {};
class Class2 {};
Now test.php
<?php
function __autoload ($class)
{
print "Autoloading $class\n";
require 'classes.php';
}
$a = new Class1;
$b = new Class2;
print get_class($b);
When you run test.php, the result is:
Autoloading Class1
Class2
If you are getting different result, then there is something that you are not telling us.
I am using Kohana and just found this piece of code in their autoloading method
// Class extension to be evaluated
$extension = 'class '.$class.' extends '.$class.'_Core { }';
// Start class analysis
$core = new ReflectionClass($class.'_Core');
if ($core->isAbstract())
{
// Make the extension abstract
$extension = 'abstract '.$extension;
}
// Transparent class extensions are handled using eval. This is
// a disgusting hack, but it gets the job done.
eval($extension);
basically what it does is when I am referring to a class that doesn't exist (through object instantiation, calling class_exists() etc.), Kohana will create a class (eg. Foo) that extends a library class that follows certain naming convention (eg. Foo_Core). just being curious, is there any way to do something similar but without the use of eval?
If you are looking to create a dynamic class then eval() is the goto function (pun intended.) However kind of related, I've found that you can put a class declaration within an if-then statement. So you can do the following:
if(true)
{
class foo
{
// methods
}
}
I use this to check to see if dynamically created classes (from a configuration file) are current... if so then load the class, otherwise... regenerate the class, and load the new one. So if you're looking to create dynamic classes for similar reasons this might be a solution.
I think you're stuck with eval() for that.
It's marked as a "disgusting hack" so that makes it OK :)
I'd be interested to know what you do with such an empty class...
If you were wanting to be able to cache your dynamically created classes, you could write out to a file and require it instead. This could be considered equally hack-ish, but it is an option. For classes that are created once and used often, it might be a good solution. For classes that need to be dynamic every time, sticking with eval is probably the best solution.
$proxyClassOnDisk = '/path/to/proxyCodeCache/' . $clazz .'.cachedProxyClass';
if ( ! file_exists($proxyClassOnDisk) ) {
// Generate the proxy and put it into the proxy class on disk.
file_put_contents($proxyClassOnDisk, $this->generateProxy($object));
}
require_once($proxyClassOnDisk);
In this example, the idea is that you are creating dynamic proxies for the class of $object. $this->generateProxy($object) will return the string that looks more or less like what $extension looks like in the original question.
This is by no means a complete implementation, just some pseudo code to show what I'm describing.