I've been trawling through the PHP docs trying to identify if there is any method that will allow me to differentiate between instances of a built-in class (such as DateTime or PDO) and User-defined Classes, but without any success.
The only approach that I have found so far is to try and bind a closure to the instance. When doing this with a built-in class, it displays a warning (yuk) and returns a null.
$targetObject = new DateTime();
$closure = function();
$test = #$closure->bindTo($targetObject, get_class($targetObject));
if ($test === false) {
throw new Exception('General failure');
} elseif ($test === null) {
throw new Exception('Unable to bind to internal class');
}
Is anybody aware of a cleaner approach to this problem?
Take a look at the reflection API. Manual
<?php
$d = new DateTime();
$r = new ReflectionClass($d);
echo ($r->isInternal());
Related
The ReflectionMethod instance from PHP (http://php.net/manual/en/class.reflectionmethod.php) has the getDocComment method that returns the annotation of a method. This works ok, unless you use unserialized object.
$ref = new ReflectionClass('a');
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //bool(false)
$ref = unserialize(serialize($ref));
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //PHP Warning: Uncaught Error: Internal error: Failed to retrieve the reflection object
Is there any way of testing if the ReflectionMethod object has correctly defined doc comment? I mean, I do not care about getting the annotation after serialize/unserialize, but I want to check if calling getDocComment is safe.
Edit: According to responses that advice error handling + fallback, I rephrase the Q.
I have some simple cache of reflections (array of ReflectionMethod objects). Until I use item from that cache, I wold like to chech its correctness. I do NOT want to handle error, I want to "predict error". Awesome would be something like hasDocComment method that does not generate any error, but returns only true/false within any ReflectionMethod object state.
The general approach of serializing reflection objects is wrong. There exists a PHP Bug report for it, but it has been set to "irrelevant":
https://bugs.php.net/bug.php?id=70719
The reason is, that you cannot connect a reflection object back to its class again, because you would have to deal with source code changes and all kinds of stuff. What you should do instead is, to serialize the name of the class and generate a NEW reflection object from that class, when you unserialize.
Code Example:
class A { }
$ref = new ReflectionClass('A');
var_dump(method_exists($ref, 'getDocComment'));
// serialize only the class name
$refClass = unserialize(serialize($ref->getName()));
// get a new reflection object from that class ...
$ref = new ReflectionClass($refClass);
var_dump(method_exists($ref, 'getDocComment'));
// If you want to serialize an object
$a = new A();
$a2 = unserialize(serialize($a));
$ref = new ReflectionClass(get_class($a2));
var_dump(method_exists($ref, 'getDocComment'));
If you need to be able to handle errors, you can try/catch the execution block. Since alt-php71-7.1.0-1 (which you seem to be using), this will throw an instance of Error instead of simply a Fatal Error, which allows you to do error handling.
<?php
class A { }
$ref = new ReflectionClass('A');
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //bool(false)
// serialize only the class name
$refClass = unserialize(serialize($ref));
try {
$refClass->getDocComment();
// do your work
}
catch (Error $e) {
echo "Malformed Reflection object: ".$e->getMessage();
}
Demo
And since you can still get the class name from the malformed Reflection instance, you can instantiate a new one right in your catch block:
<?php
class A { }
$ref = new ReflectionClass('A');
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //bool(false)
// serialize only the class name
$refClass = unserialize(serialize($ref));
try {
$refClass->getDocComment();
}
catch (Error $e) {
$recoveredRef = new ReflectionClass($refClass->getName());
var_dump($recoveredRef);
var_dump($recoveredRef->getDocComment()); // works correctly
echo "Malformed Reflection object, but recovered: ".$e->getMessage();
}
Demo
I want to check that the $duration is object of type DateInterval otherwise throw an exception. get_class function fails if $duration is not an object... and this code looks to long
if (is_object($duration)) {
if (get_class($duration) != "DateInterval") {
throw new \Exception('The provided duration is not DateInterval type.');
}
} else {
throw new \Exception('The provided duration is not DateInterval type.');
}
Is there a better way ?
I would use the instanceof method. See the example:
$obj = new A();
if ($obj instanceof A) {
echo 'A';
}
https://secure.php.net/manual/en/internals2.opcodes.instanceof.php
Use instanceof:
if (!$duration instanceof DateInterval) {
// throw Exception
}
This seems like a fairly simple reflection problem, yet I can't figure it our. I use Laravel 4.2 on Debian with PHP 5.6.6-1.
Basicly what happens is that I want to spawn a new object from a class in a Laravel QueueHandler like so:
$className = 'MyClass';
$myobject = new $className ();
and this doesn't work. I tried everything I can possibly think of and have no clue where to look. This code doesn;t work while it should:
<?php
use Pronamic\Twinfield\Secure\Config;
use Pronamic\Twinfield\Customer\CustomerFactory;
class TwinfieldQueueHandler {
private $twinfieldConfig = null;
...
try {
$twinfieldFactoryClass = 'CustomerFactory';
//returns 0
echo strcmp('CustomerFactory', $twinfieldFactoryClass);
//works
$test0 = new CustomerFactory ($this->twinfieldConfig);
//throws an exeption with message: "Class CustomerFactory does not exist"
$r = new ReflectionClass($twinfieldFactoryClass);
$test1 = $r->newInstanceArgs($this->twinfieldConfig);
//gives error PHP Fatal error: Class 'CustomerFactory' not found in {file} on line {line}
$test2 = new $twinfieldFactoryClass ($this->twinfieldConfig);
} catch (Exception $e) {
Log::error($e->getMessage());
}
Has anyone got any pointers on where to look and how to debug this?
ReflectionClass will ignore your current namespace and use statements completely. You have to specify the fully qualified name of the class:
$r = new ReflectionClass('Pronamic\Twinfield\Customer\CustomerFactory');
As a user points out on php.net:
To reflect on a namespaced class in PHP 5.3, you must always specify the fully qualified name of the class - even if you've aliased the containing namespace using a "use" statement.
Note that you could work around this by passing an object:
$test0 = new CustomerFactory ($this->twinfieldConfig);
$r = new ReflectionClass($test0);
I can't do this but wondering what would work:
is_object(new Memcache){
//assign memcache object
$memcache = new Memcache;
$memcache->connect('localhost', 11211);
$memcache->get('myVar');
}
else{
//do database query to generate myVar variable
}
You can use class_exists() to check if a class exists, but it will not return if you can instantiate that class!
One of the reasons you can't, might be that it is an abstract class. To check for that you should do something like this after you check for class_exists().
This might be impossible (having an abstract class, not checking for it) for above example, but in other situations might be giving you headaches :)
//first check if exists,
if (class_exists('Memcache')){
//there is a class. but can we instantiate it?
$class = new ReflectionClass('Memcache')
if( ! $class->isAbstract()){
//dingdingding, we have a winner!
}
}
See class_exists
if (class_exists('Memcache')){
//assign memcache object
$memcache = new Memcache;
$memcache->connect('localhost', 11211);
$memcache->get('myVar');
}
else{
//do database query to generate myVar variable
}
Check out class_exists().
http://php.net/manual/en/function.class-exists.php
You can use the class_exists function to see if a class exists or not.
See more in the manual: class_exists
The ReflectionClass::isInstantiable method checks if the class is instantiable.
$reflector = new ReflectionClass($concrete);
if ($reflector->isInstantiable()) {
// do something
}
class MyDestructableClass {
function __construct() {
print "\nIn constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "\nDestroying " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
When the above script is in a complex environment,the __destruct won't get called when exit,but I can't reproduce it easily.Have someone ever noticed this ?
EDIT
I'll post the whole stuff here,it's the testing environment of symfony,which means you can easily reproduce it if you are familar with the framework:
require_once dirname(__FILE__).'/../bootstrap/Doctrine.php';
$profiler = new Doctrine_Connection_Profiler();
$conn = Doctrine_Manager::connection();
$conn->setListener($profiler);
$t = new lime_test(0, new lime_output_color());
class MyDestructableClass {
function __construct() {
print "\nIn constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "\nDestroying " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
$news = new News();
$news->setUrl('http://test');
$news->setHash('http://test');
$news->setTitle('http://test');
$news->setSummarize('http://test');
$news->setAccountId(1);
$news->setCategoryId(1);
$news->setThumbnail('http://test');
$news->setCreatedAt(date('Y-m-d H:i:s',time()));
$news->setUpdatedAt(date('Y-m-d H:i:s',time()));
$news->save();
exit();
The __destruct will not be called:
If exit is called in another destructor
Depending on the PHP Version: if exit is called in a shutdown function registered with register_shutdown_function
If there is a fatal error somewhere in the code
If another destructor throws an exception
If you try to handle an exception in a destructor (PHP >= 5.3.0)
Guess that's all I can think of right now
& What Pascal MARTIN said. That's the first step of debugging that.
The __destruct method will also not be called if script is running on CLI and receives a SIGTERM (Ctrl+C)
Not having an output on the screen doesn't mean the destructor is not called : the ouptut could be captured using output_buffering (maybe lime does that, to be able to work on it ? ), and not echoed when the script ends, for instance.
For testing purposes, you could try writing to a file, in your __destruct method, instead of just echoing some text.
(Just make sure your application / PHP has the required privileges to write to your destination file)
(I've already run into situations where I would not see the output made in a destructor -- but it was actually called)
As the PHP documentation says:
The destructor will be called even if script execution is stopped using exit(). Calling exit() in a destructor will prevent the remaining shutdown routines from executing.
I know I'am a little late to the party but for people who are also looking to get __destruct to be executed when CTRL+C and/or Fatal errors occur, you can try this (below is a test case):
Index.php
<?php
// Setup CTRL+C and System kill message handler
// The only signal that cannot be caught is the SIGKILL (very hard kill)
declare(ticks = 1); // Required else it won't work.
pcntl_signal(SIGTERM, 'close'); // System kill (Unhappy Termination)
pcntl_signal(SIGINT, 'close'); // CTRL+C (Happy Termination)
// Shutdown functions will be executed even on fatal errors
register_shutdown_function('close');
function close($signal = null) // only pcntl_signal fills $signal so null is required
{
// Check if there was an fatal error (else code below isn't needed)
$err = error_get_last();
if(is_array($err))
{
foreach(array_keys($GLOBALS) as $key)
{
if(in_array($key, ['_GET', '_POST', '_COOKIE', '_FILES', '_SERVER', '_REQUEST', '_ENV', 'GLOBALS']))
continue;
// This will automatically call __destruct
unset($GLOBALS[$key]);
}
}
}
// Example
class blah
{
private $id = '';
public function __construct()
{
$this->id = uniqid();
// note this piece of code, doesn't work on windows!
exec('mkdir /tmp/test_'.$this->id);
}
public function __destruct()
{
// note this piece of code, doesn't work on windows!
exec('rm /tmp/test_'.$this->id.' -R');
}
}
// Test
$a = new blah();
$b = new blah();
$c = new blah();
$d = new blah();
$e = new blah();
$f = new blah();
$g = new blah();
$h = new blah();
$i = new blah();
$j = new blah();
$k = new blah();
$l = new blah();
$m = new blah();
$n = new blah();
$o = new blah();
$p = new blah();
$q = new blah();
$r = new blah();
$s = new blah();
$t = new blah();
$u = new blah();
$v = new blah();
$w = new blah();
$x = new blah();
$y = new blah();
$z = new blah();
// The script that causes an fatal error
require_once(__DIR__.'/test.php');
Test.php
<?php
// this will create a parse (E_PARSE) error.
asdsaddsaadsasd
Note: calling exit or throwing exceptions in destructors or shutdown functions will cause the script to terminate immediately.
If you pass a reference of an instance of a class to another class and call exit outside of that class, the __destruct of the class will not be called. It looks like a bug to me.
$a = new classa();
$b = new classb($a); // where classb constructor is __construct(&a) {}
exit;
neither destructors will be called, and there is no guarantee of order of destruction. I have tried many ways to just echo a message in the destructor and it never prints unless I explicitly say:
unset($b); // properly calls classb::__destruct
unset($a); // properly calls classa::__destruct
exit;
Since I cannot get any results, I cannot tell if it is a race condition of destructors or just the expected results; Anyway, unset() always calls the destructor properly. I know it is a pain, but better than living with this bug. They need to handle reference counts to classes and dependency order properly when exit is called and call the destructors in the proper order.
Don't familiar with the Doctrine, but check one point: check for possible exceptions in __construct()/__destruct() they can produce fatal errors.