Skip to content
This repository

Move is_subclass_of code to Zend\Stdlib #1807

Merged
merged 15 commits into from almost 2 years ago

7 participants

prolic Don't Add Me To Your Organization a.k.a The Travis Bot Marc Bennewitz Maks Ralph Schindler Matthew Weier O'Phinney Evan Coury
prolic
prolic commented July 09, 2012

@see https://bugs.php.net/bug.php?id=53727

This code is used to determine the subclass of a given class. Used in Zend\Di, Zend\Form,
Zend\InfoCard, Zend\Loader, Zend\Mvc, Zend\ServiceManager and Zend\XmlRpc

prolic Move is_subclass_of code to Zend\Stdlib
@see https://bugs.php.net/bug.php?id=53727

This code is used to determine the subclass of a given class. Used in Zend\Di, Zend\Form,
Zend\InfoCard, Zend\Loader, Zend\Mvc, Zend\ServiceManager and Zend\XmlRpc
9ebee1f
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 9ebee1f into 402074c).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged f374a41 into 402074c).

library/Zend/Stdlib/SubClass.php
((14 lines not shown))
  14
+     * Checks if the object has this class as one of its parents
  15
+     *
  16
+     * @see https://bugs.php.net/bug.php?id=53727
  17
+     *
  18
+     * @param object|string $object
  19
+     * @param string $type
  20
+     */
  21
+    public static function isSubclassOf($object, $type)
  22
+    {
  23
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  24
+            return is_subclass_of($object, $type);
  25
+        }
  26
+        if (is_object($object)) {
  27
+            $object = get_class($object);
  28
+        }
  29
+        if (!array_key_exists($object, self::$cache)) {
1
Marc Bennewitz
marc-mabe added a note July 09, 2012

Should that not be case-insensitive ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
prolic
prolic commented July 09, 2012

Fixed, thanks @marc-mabe

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 1db1a1f into 402074c).

Marc Bennewitz

Why $object contains a name of a class-> IMHO $classname ?

Marc Bennewitz

Mh you should call class_parents & class_implements with the given classname because of autoloading will be done and could be non case-insensitive.

prolic
prolic commented July 09, 2012

Fixed, thanks @marc-mabe

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 789d837 into 402074c).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged fc00d87 into 402074c).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 9dd39a5 into 402074c).

library/Zend/Stdlib/SubClass.php
((16 lines not shown))
  16
+ * @category   Zend
  17
+ * @package    Zend_Stdlib
  18
+ * @subpackage SubClass
  19
+ */
  20
+abstract class SubClass
  21
+{
  22
+
  23
+    /**
  24
+     * Checks if the object has this class as one of its parents
  25
+     *
  26
+     * @see https://bugs.php.net/bug.php?id=53727
  27
+     *
  28
+     * @param object|string $object
  29
+     * @param string $type
  30
+     */
  31
+    public static function isSubclassOf($object, $type)
4
Ralph Schindler Owner

This implementation looks really expensive. I'd not accept objects here since PHP already handles objects just fine ($obj instanceof Type).

This abstraction should really only be for string class comparisons. Also, the body below can also be simplified for performance.

prolic
prolic added a note July 10, 2012

Hi @ralphschindler
PHP handles objects just fine with its instanceof operator. However is_subclass_of accepts also objects and the is_subclass_of function is also broken for objects.

see:

php5 -v

PHP 5.3.6-13ubuntu3.8 with Suhosin-Patch (cli) (built: Jun 13 2012 18:02:19)
Copyright (c) 1997-2011 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies
with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans

php5 -a

interface A {}
class B implements A {}
class C extends B {}
var_dump(is_subclass_of('B', 'A'));
bool(false)
var_dump(is_subclass_of('C', 'A'));
bool(true)
var_dump(is_subclass_of(new B, 'A'));
bool(false)
var_dump(is_subclass_of(new C, 'A'));
bool(true)

About performance: The method is basically yours (taken from Zend\Di\Di), i just added the case-insensitive behaviour. I will look into this and see how this can be improved. Thanks for your review.

Ralph Schindler Owner

Right, what I'm saying is that consuming code that wants to check if a particular object is of a particular type, it should just use instanceof, not is_subclass_of. is_subclass_of (php4) predates instanceof (php5) and its only usefulness is that it can be used with class name to class name comparisons.

As for the implementation, I'd simply remove the foreach. I'd create a multi dim array of $classes[$parent][$implements] = true; For the lower case stuff, I'd use array_change_case over ($classes[$parent]). What this does is remove the loop (expensive), and also keeps the lookup to an isset().

prolic
prolic added a note July 10, 2012

@ralphschindler I changed the implementation. It's now much faster. Tested various combinations, and this seems to be the fastet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
library/Zend/Stdlib/SubClass.php
((28 lines not shown))
  28
+     * @param object|string $object
  29
+     * @param string $type
  30
+     */
  31
+    public static function isSubclassOf($object, $type)
  32
+    {
  33
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  34
+            return is_subclass_of($object, $type);
  35
+        }
  36
+        if (is_object($object)) {
  37
+            $className = get_class($object);
  38
+        } else {
  39
+            $className = $object;
  40
+        }
  41
+        $className = ltrim($className, '\\');
  42
+        $type = ltrim($type, '\\');
  43
+        static $isSubclassFuncCache = null; // null as unset, array when set
4
Marc Bennewitz
marc-mabe added a note July 10, 2012

Why not static $cache = array(); ?

Matthew Weier O'Phinney Owner

Agree with @marc-mabe here. In fact, why not have the cache as a static protected member instead?

Ralph Schindler Owner

The only reason it was a static variable before was to keep it out of the DI API, since we've created an API for this function, it should be fine to make it a static member of this class.

prolic
prolic added a note July 10, 2012

Implementation detail changed. No need for a cache now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
library/Zend/Stdlib/SubClass.php
((18 lines not shown))
  18
+ * @subpackage SubClass
  19
+ */
  20
+abstract class SubClass
  21
+{
  22
+
  23
+    /**
  24
+     * Checks if the object has this class as one of its parents
  25
+     *
  26
+     * @see https://bugs.php.net/bug.php?id=53727
  27
+     *
  28
+     * @param object|string $object
  29
+     * @param string $type
  30
+     */
  31
+    public static function isSubclassOf($object, $type)
  32
+    {
  33
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
2
Marc Bennewitz
marc-mabe added a note July 10, 2012

Not tested but this one should be much more performance:

if (is_subclass_of($object, $type)) {
return true;
}

if (version_compare(\PHP_VERSION, '5.3.7', '<')) {
return false;
}

// Workaround for PHP < 5.3.7
$class = $object;
if (is_object($object)) {
$class = get_class($object);
}

if (!class_exists($class, true)) {
return false;
}

$classNormalized = ltrim(strtolower($class), '\');
$typeNormalized = ltrim(strtolower($type), '\');

static $cache = array();
if (!isset($cache[$classNormalized])) {
$parents = class_parents($class) + class_implements($class);
$parents = array_map('strtolower', $parents);
$cache[$classNormalized] = $parents;
}

return in_array($typeNormalized, $cache[$classNormalized]);

Marc Bennewitz
marc-mabe added a note July 10, 2012

Sry it should be ... if (version_compare(\PHP_VERSION, '5.3.7', '>=')) { ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
library/Zend/Stdlib/SubClass.php
((22 lines not shown))
  22
+
  23
+    /**
  24
+     * Checks if the object has this class as one of its parents
  25
+     *
  26
+     * @see https://bugs.php.net/bug.php?id=53727
  27
+     *
  28
+     * @param object|string $object
  29
+     * @param string $type
  30
+     */
  31
+    public static function isSubclassOf($object, $type)
  32
+    {
  33
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  34
+            return is_subclass_of($object, $type);
  35
+        }
  36
+        if (is_object($object)) {
  37
+            $className = get_class($object);
3
Maks Collaborator
Maks3w added a note July 10, 2012

This can be replaced by: return ($object instanceof $type)

prolic
prolic added a note July 10, 2012

fixed, thanks

Ralph Schindler Owner

As I said, we don't need to incur the cost of a function call where instanceof would have sufficed. This method should only deal with $class, $type comparisons, not $object $type comparisons.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Marc Bennewitz marc-mabe commented on the diff July 10, 2012
library/Zend/Stdlib/SubClass.php
((5 lines not shown))
  5
+ * @link      http://github.com/zendframework/zf2 for the canonical source repository
  6
+ * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7
+ * @license   http://framework.zend.com/license/new-bsd New BSD License
  8
+ * @package   Zend_Stdlib
  9
+ */
  10
+
  11
+namespace Zend\Stdlib;
  12
+
  13
+/**
  14
+ * @see https://bugs.php.net/bug.php?id=53727
  15
+ *
  16
+ * @category   Zend
  17
+ * @package    Zend_Stdlib
  18
+ * @subpackage SubClass
  19
+ */
  20
+abstract class SubClass
1
Marc Bennewitz
marc-mabe added a note July 10, 2012

Name it ClassUtils or OopUtils ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
library/Zend/Stdlib/SubClass.php
((32 lines not shown))
  32
+    {
  33
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  34
+            return is_subclass_of($object, $type);
  35
+        }
  36
+        if (is_object($object)) {
  37
+            $className = get_class($object);
  38
+        } else {
  39
+            $className = $object;
  40
+        }
  41
+        $className = ltrim($className, '\\');
  42
+        $type = ltrim($type, '\\');
  43
+        static $isSubclassFuncCache = null; // null as unset, array when set
  44
+        if ($isSubclassFuncCache === null) {
  45
+            $isSubclassFuncCache = array();
  46
+        }
  47
+        if (!array_key_exists($className, $isSubclassFuncCache)) {
1
Matthew Weier O'Phinney Owner

First, you store the value of strtolower($className) -- as such, this condition will always fail.

Instead of doing the work inside the conditional, have a conditional test if the array key exists, and return the value. Then do the work in the main body.

$normalizedClassName = strtolower($className);
$normalizedType             = strtolower($type);
if (array_key_exists($normalizedClassName, $isSubclassFuncCache)) {
    return isset($isSubclassFuncCache[$normalizedClassName][$normalizedType];
}
$parents = class_parents($className, true) + class_implements($className, true);
/* ... */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/Zend/Stdlib/SubClassTest.php
((4 lines not shown))
  4
+ *
  5
+ * @link      http://github.com/zendframework/zf2 for the canonical source repository
  6
+ * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7
+ * @license   http://framework.zend.com/license/new-bsd New BSD License
  8
+ * @package   Zend_Stdlib
  9
+ */
  10
+
  11
+namespace ZendTest\Stdlib;
  12
+
  13
+use Zend\Stdlib\SubClass;
  14
+
  15
+class SubClassTest extends \PHPUnit_Framework_TestCase
  16
+{
  17
+    protected function setUp()
  18
+    {
  19
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
1
Maks Collaborator
Maks3w added a note July 10, 2012

I think that is best test it with all versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
prolic
prolic commented July 10, 2012

I changed the implementation. It's now much faster. About the class name: Why not Zend\Stdlib\PhpBugs or something like that? I guess we can reimplement some php-functions, that are broken.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 99dca8e into 402074c).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 7b2f30e into 402074c).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged a14f2f7 into 402074c).

prolic
prolic commented July 10, 2012

If someone finds a faster solution, please send it to me! :-) Perhabs I should rebase, so we merge not so many commits, just a single one.

Ralph Schindler
Owner

For the record, I am generally against factoring this out of DI. Every line of code in DI extremely deliberate and designed to be as fast as possible, especially given the problem it is solving. The implementation of that particular function is suited for that particular hack (you'll notice I don't do any case switching since the likelihood of case being an issue is next to nil, so there is no need to incur that cost).

prolic
prolic commented July 10, 2012

@ralphschindler can you please test your implementation against my current on your machine? On my machine my current implementation using Reflection is faster.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 1661628 into 402074c).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged e89cdcf into 402074c).

Ralph Schindler ralphschindler commented on the diff July 10, 2012
library/Zend/Stdlib/SubClass.php
((30 lines not shown))
  30
+     * @param string $className
  31
+     * @param string $type
  32
+     */
  33
+    public static function isSubclassOf($className, $type)
  34
+    {
  35
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  36
+            return is_subclass_of($className, $type);
  37
+        }
  38
+        if (is_subclass_of($className, $type)) {
  39
+            return true;
  40
+        }
  41
+        if (!interface_exists($type)) {
  42
+            return false;
  43
+        }
  44
+        $r = new ReflectionClass($className);
  45
+        return $r->implementsInterface($type);
4
Ralph Schindler Owner

Is using Reflection here not jumping out as a bad idea? Would you expect that by calling this seemingly innocuous method (SubClass::isSubclassOf()) would go and instantiate a reflection object to answer the question?
Also, instead of implementsInterface, I think you were looking for $r->isSubclassOf().

prolic
prolic added a note July 10, 2012

Well it's faster, so why not use it.
I am using implementsInterface, because I can be sure, that $type is an interface. Otherwise the method would return earlier.

Matthew Weier O'Phinney Owner

Did you test this against a version that caches? Otherwise, every time you test a given class, it will have to do the reflection, which is going to be slow.

Honestly, I'm not 100% convinced we should abstract this into a static method call; the handful of places it was used before were each optimized for the specific use cases, and you'll notice they differ in approach based on context.

prolic
prolic added a note July 11, 2012

I will put performance tests online, so everybody can compare the speed difference.
Generally I like that we have a central place, where we care about this php bug. Also we provide a zf-way for end-users, so they know how to handle the situation with this specific php bug. They can just use a static method call until 5.4 is the minimum php version and the class is removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
prolic
prolic commented July 10, 2012

I can remove the dependency in Zend\Di\Di again, if that is wished. Any other thoughts on this except @ralphschindler who brought this up?

prolic
prolic commented July 11, 2012

performance comparisions online:
https://gist.github.com/3089749

Matthew Weier O'Phinney
Owner

Performance results look good. However, they are not testing the same implementations. Ideally, we should be testing the difference between calling a local method and calling a static method on another class. If I have time today, I'll fork your gist and see what results that presents.

prolic
prolic commented July 11, 2012

Great, but I don't expect that it will be then faster then with reflection :-)

Matthew Weier O'Phinney
Owner

My experiments bear out your results: https://gist.github.com/3092671

Basically, the approach you propose is 3-4x faster than the original code in Di, and caching actually halves the gains. I'm convinced.

Matthew Weier O'Phinney weierophinney merged commit f25e711 into from July 11, 2012
Matthew Weier O'Phinney weierophinney closed this July 11, 2012
Ralph Schindler
Owner

So, I generally agree caching is no good in any instance. But your tests are flawed, and so are your numbers. In general, for DI's purposes, I don't care about case since all lookups contain valid classes already (the source of all types is Reflection in the first place). Beyond that, and this is as much a guess as your numbers, 3-5 times the amount of lookups will be for completely unrelated code in the first place - so that needs to be factored in.

https://gist.github.com/3092974

Matthew Weier O'Phinney
Owner

Revised benchmarks, combining those of @prolic, myself, and @ralphschindler: https://gist.github.com/3092671

Basically, the uncached reflection implementation is on par with the fastest version @ralphschindler used (which uses class_parents() + class_implements() to do its work), but has the benefit of being case sensitive -- which is useful for DI use cases.

However, I'm unconvinced that we should have this as a centralized solution. In particular, 4 of the 8 components did not previously have a dep on Stdlib, and in the case of the AutoloaderFactory, there should be no dependencies outside of the autoloaders themselves.

Matthew Weier O'Phinney weierophinney referenced this pull request from a commit in weierophinney/zf2 July 11, 2012
Matthew Weier O'Phinney Remove Zend\Stdlib\SubClass
The main reason for this class was to work around changes in PHP between
versions 5.3.3 and 5.3.7 regarding how is_subclass_of() works. In the earlier
versions, it was buggy. In 5.3.7 and up, the bugs are fixed, and the solution is
very fast.

There are very few places in the framework where an is_subclass_of() test makes
sense, and those are typically only with classes responsible for creating other
classes -- factories, primarily. As such, the number of places where this will
be of use is minimal, and can be maintained.

The main goal is to have a consistent, performant way to do the check when
required, and one which will work with case sensitive contexts. The solution by
@prolic in #1807 is good; we just don't need to abstract this, particularly as
we'll be able to remove this entirely in ZF3. Additionally, abstracting it gives
additional dependencies for those components needing it, and in cases like
autoloaders, DI, and the service manager, the extra dependency is undesirable as
these should stand alone.

The code has been duplicated into each class requiring the lookup (I put the
check into AbstractPluginManager, as it's not unlikely other plugin managers may
need this).
387c529
Matthew Weier O'Phinney
Owner

New PR about this: #1852

Evan Coury
Collaborator

While I generally advocate that cross-component dependencies are not evil, I have to say I agree at least in the case of the AutoloaderFactory -- this class should absolutely not have dependencies outside of Zend\Loader.

Evan Coury EvanDotPro referenced this pull request from a commit July 11, 2012
Evan Coury Remove require_once() in AutoloaderFactory
This dependency was removed by @weierophinney in PR #1852, but the
require_once was not removed. Originally the dependency was introduced
by @prolic in PR #1807.
3eb61e3
Deleted user Unknown referenced this pull request from a commit July 11, 2012
Matthew Weier O'Phinney Remove Zend\Stdlib\SubClass
The main reason for this class was to work around changes in PHP between
versions 5.3.3 and 5.3.7 regarding how is_subclass_of() works. In the earlier
versions, it was buggy. In 5.3.7 and up, the bugs are fixed, and the solution is
very fast.

There are very few places in the framework where an is_subclass_of() test makes
sense, and those are typically only with classes responsible for creating other
classes -- factories, primarily. As such, the number of places where this will
be of use is minimal, and can be maintained.

The main goal is to have a consistent, performant way to do the check when
required, and one which will work with case sensitive contexts. The solution by
@prolic in #1807 is good; we just don't need to abstract this, particularly as
we'll be able to remove this entirely in ZF3. Additionally, abstracting it gives
additional dependencies for those components needing it, and in cases like
autoloaders, DI, and the service manager, the extra dependency is undesirable as
these should stand alone.

The code has been duplicated into each class requiring the lookup (I put the
check into AbstractPluginManager, as it's not unlikely other plugin managers may
need this).
3894420
Deleted user Unknown referenced this pull request from a commit July 11, 2012
Evan Coury Remove require_once() in AutoloaderFactory
This dependency was removed by @weierophinney in PR #1852, but the
require_once was not removed. Originally the dependency was introduced
by @prolic in PR #1807.
f0ff1de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
29  library/Zend/Di/Di.php
@@ -3,6 +3,7 @@
3 3
 namespace Zend\Di;
4 4
 
5 5
 use Closure;
  6
+use Zend\Stdlib\SubClass;
6 7
 
7 8
 class Di implements DependencyInjectionInterface
8 9
 {
@@ -301,7 +302,7 @@ protected function handleInjectDependencies($instance, $injectionMethods, $param
301 302
                                         if ($methodParams) {
302 303
                                             foreach ($methodParams as $methodParam) {
303 304
                                                 $objectToInjectClass = $this->getClass($objectToInject);
304  
-                                                if ($objectToInjectClass == $methodParam[1] || $this->isSubclassOf($objectToInjectClass, $methodParam[1])) {
  305
+                                                if ($objectToInjectClass == $methodParam[1] || SubClass::isSubclassOf($objectToInjectClass, $methodParam[1])) {
305 306
                                                     if ($this->resolveAndCallInjectionMethodForInstance($instance, $typeInjectionMethod, array($methodParam[0] => $objectToInject), $instanceAlias, true, $type)) {
306 307
                                                         $calledMethods[$typeInjectionMethod] = true;
307 308
                                                     }
@@ -574,7 +575,7 @@ protected function resolveMethodParameters($class, $method, array $callTimeUserP
574 575
                     }
575 576
                     $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ?
576 577
                          $this->instanceManager->getClassFromAlias($pInstance) : $pInstance;
577  
-                    if ($pInstanceClass === $type || $this->isSubclassOf($pInstanceClass, $type)) {
  578
+                    if ($pInstanceClass === $type || SubClass::isSubclassOf($pInstanceClass, $type)) {
578 579
                         $computedParams['required'][$fqParamPos] = array($pInstance, $pInstanceClass);
579 580
                         continue 2;
580 581
                     }
@@ -591,7 +592,7 @@ protected function resolveMethodParameters($class, $method, array $callTimeUserP
591 592
                     }
592 593
                     $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ?
593 594
                          $this->instanceManager->getClassFromAlias($pInstance) : $pInstance;
594  
-                    if ($pInstanceClass === $type || $this->isSubclassOf($pInstanceClass, $type)) {
  595
+                    if ($pInstanceClass === $type || SubClass::isSubclassOf($pInstanceClass, $type)) {
595 596
                         $computedParams['required'][$fqParamPos] = array($pInstance, $pInstanceClass);
596 597
                         continue 2;
597 598
                     }
@@ -673,26 +674,4 @@ protected function getClass($instance)
673 674
         return get_class($instance);
674 675
     }
675 676
 
676  
-    /**
677  
-     * @see https://bugs.php.net/bug.php?id=53727
678  
-     *
679  
-     * @param $class
680  
-     * @param $type
681  
-     * @return bool
682  
-     */
683  
-    protected function isSubclassOf($class, $type)
684  
-    {
685  
-        /* @var $isSubclassFunc Closure */
686  
-        static $isSubclassFuncCache = null; // null as unset, array when set
687  
-
688  
-        if ($isSubclassFuncCache === null) {
689  
-            $isSubclassFuncCache = array();
690  
-        }
691  
-
692  
-        if (!array_key_exists($class, $isSubclassFuncCache)) {
693  
-            $isSubclassFuncCache[$class] = class_parents($class, true) + class_implements($class, true);
694  
-        }
695  
-        return (isset($isSubclassFuncCache[$class][$type]));
696  
-    }
697  
-
698 677
 }
22  library/Zend/Form/Annotation/AnnotationBuilder.php
@@ -33,6 +33,7 @@
33 33
 use Zend\Form\Exception;
34 34
 use Zend\Form\Factory;
35 35
 use Zend\Stdlib\ArrayUtils;
  36
+use Zend\Stdlib\SubClass;
36 37
 
37 38
 /**
38 39
  * Parses a class' properties for annotations in order to create a form and
@@ -339,7 +340,7 @@ protected function configureElement($annotations, $reflection, $formSpec, $filte
339 340
             : 'Zend\Form\Element';
340 341
 
341 342
         // Compose as a fieldset or an element, based on specification type
342  
-        if ($this->isFieldset($type)) {
  343
+        if (SubClass::isSubclassOf($type, 'Zend\Form\FieldsetInterface')) {
343 344
             if (!isset($formSpec['fieldsets'])) {
344 345
                 $formSpec['fieldsets'] = array();
345 346
             }
@@ -386,23 +387,4 @@ protected function checkForExclude($annotations)
386 387
         return (bool) $results->last();
387 388
     }
388 389
 
389  
-    /**
390  
-     * Determine if the type represents a fieldset
391  
-     *
392  
-     * For PHP versions >= 5.3.7, uses is_subclass_of; otherwise, uses
393  
-     * reflection to determine the interfaces implemented.
394  
-     *
395  
-     * @param  string $type
396  
-     * @return bool
397  
-     */
398  
-    protected function isFieldset($type)
399  
-    {
400  
-        if (version_compare(PHP_VERSION, '5.3.7', 'gte')) {
401  
-            return is_subclass_of($type, 'Zend\Form\FieldsetInterface');
402  
-        }
403  
-
404  
-        $r = new ClassReflection($type);
405  
-        $interfaces = $r->getInterfaceNames();
406  
-        return (in_array('Zend\Form\FieldsetInterface', $interfaces));
407  
-    }
408 390
 }
22  library/Zend/Form/Factory.php
@@ -27,6 +27,7 @@
27 27
 use Zend\InputFilter\InputFilterInterface;
28 28
 use Zend\Stdlib\ArrayUtils;
29 29
 use Zend\Stdlib\Hydrator;
  30
+use Zend\Stdlib\SubClass;
30 31
 
31 32
 /**
32 33
  * @category   Zend
@@ -84,33 +85,20 @@ public function create($spec)
84 85
         $spec = $this->validateSpecification($spec, __METHOD__);
85 86
         $type = isset($spec['type']) ? $spec['type'] : 'Zend\Form\Element';
86 87
 
87  
-        if ($type instanceof FormInterface) {
  88
+        if (SubClass::isSubclassOf($type, 'Zend\Form\FormInterface')) {
88 89
             return $this->createForm($spec);
89 90
         }
90 91
 
91  
-        if ($type instanceof FieldsetInterface) {
  92
+        if (SubClass::isSubclassOf($type, 'Zend\Form\FieldsetInterface')) {
92 93
             return $this->createFieldset($spec);
93 94
         }
94 95
 
95  
-        if ($type instanceof ElementInterface) {
  96
+        if (SubClass::isSubclassOf($type, 'Zend\Form\ElementInterface')) {
96 97
             return $this->createElement($spec);
97 98
         }
98 99
 
99  
-        if (is_string($type) && class_exists($type)) {
100  
-            $reflection = new ReflectionClass($type);
101  
-            if ($reflection->implementsInterface('Zend\Form\FormInterface')) {
102  
-                return $this->createForm($spec);
103  
-            }
104  
-            if ($reflection->implementsInterface('Zend\Form\FieldsetInterface')) {
105  
-                return $this->createFieldset($spec);
106  
-            }
107  
-            if ($reflection->implementsInterface('Zend\Form\ElementInterface')) {
108  
-                return $this->createElement($spec);
109  
-            }
110  
-        }
111  
-
112 100
         throw new Exception\DomainException(sprintf(
113  
-            '%s expects the $spec["type"] to implement one of %s, %s, %s, or a valid full qualified class name; received %s',
  101
+            '%s expects the $spec["type"] to implement one of %s, %s, or %s; received %s',
114 102
             __METHOD__,
115 103
             'Zend\Form\ElementInterface',
116 104
             'Zend\Form\FieldsetInterface',
3  library/Zend/InfoCard/XML/AbstractElement.php
@@ -23,6 +23,7 @@
23 23
 
24 24
 use DOMElement;
25 25
 use SimpleXMLElement;
  26
+use Zend\Stdlib\SubClass;
26 27
 
27 28
 /**
28 29
  * An abstract class representing a an XML data block
@@ -80,7 +81,7 @@ static public function convertToObject(\DOMElement $e, $classname)
80 81
             throw new Exception\InvalidArgumentException('Class provided for converting does not exist');
81 82
         }
82 83
 
83  
-        if(!is_subclass_of($classname, 'Zend\InfoCard\XML\ElementInterface')) {
  84
+        if (!SubClass::isSubclassOf($classname, 'Zend\InfoCard\XML\ElementInterface')) {
84 85
             throw new Exception\InvalidArgumentException("DOM element must be converted to an instance of Zend_InfoCard_Xml_Element");
85 86
         }
86 87
 
15  library/Zend/Loader/AutoloaderFactory.php
@@ -102,15 +102,12 @@ public static function factory($options = null)
102 102
                     );
103 103
                 }
104 104
 
105  
-                // unfortunately is_subclass_of is broken on some 5.3 versions
106  
-                // additionally instanceof is also broken for this use case
107  
-                if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
108  
-                    if (!is_subclass_of($class, 'Zend\Loader\SplAutoloader')) {
109  
-                        require_once 'Exception/InvalidArgumentException.php';
110  
-                        throw new Exception\InvalidArgumentException(
111  
-                            sprintf('Autoloader class %s must implement Zend\\Loader\\SplAutoloader', $class)
112  
-                        );
113  
-                    }
  105
+                require_once __DIR__ . '/../Stdlib/SubClass.php';
  106
+                if (!\Zend\Stdlib\SubClass::isSubclassOf($class, 'Zend\Loader\SplAutoloader')) {
  107
+                    require_once 'Exception/InvalidArgumentException.php';
  108
+                    throw new Exception\InvalidArgumentException(
  109
+                        sprintf('Autoloader class %s must implement Zend\\Loader\\SplAutoloader', $class)
  110
+                    );
114 111
                 }
115 112
 
116 113
                 if ($class === static::STANDARD_AUTOLOADER) {
3  library/Zend/Mvc/Router/RoutePluginManager.php
@@ -21,6 +21,7 @@
21 21
 
22 22
 namespace Zend\Mvc\Router;
23 23
 
  24
+use Zend\Stdlib\SubClass;
24 25
 use Zend\ServiceManager\AbstractPluginManager;
25 26
 
26 27
 /**
@@ -92,7 +93,7 @@ protected function createFromInvokable($canonicalName, $requestedName)
92 93
             ));
93 94
         }
94 95
 
95  
-        if (!$this->isSubclassOf($invokable, __NAMESPACE__ . '\RouteInterface')) {
  96
+        if (!SubClass::isSubclassOf($invokable, __NAMESPACE__ . '\RouteInterface')) {
96 97
             throw new Exception\RuntimeException(sprintf(
97 98
                 '%s: failed retrieving "%s%s" via invokable class "%s"; class does not implement %s\RouteInterface',
98 99
                 __METHOD__,
22  library/Zend/ServiceManager/AbstractPluginManager.php
@@ -20,8 +20,6 @@
20 20
 
21 21
 namespace Zend\ServiceManager;
22 22
 
23  
-use Zend\Code\Reflection\ClassReflection;
24  
-
25 23
 /**
26 24
  * ServiceManager implementation for managing plugins
27 25
  *
@@ -194,24 +192,4 @@ protected function createFromInvokable($canonicalName, $requestedName)
194 192
         return $instance;
195 193
     }
196 194
 
197  
-    /**
198  
-     * Determine if a class implements a given interface
199  
-     *
200  
-     * For PHP versions >= 5.3.7, uses is_subclass_of; otherwise, uses
201  
-     * reflection to determine the interfaces implemented.
202  
-     *
203  
-     * @param  string $class
204  
-     * @param  string $type
205  
-     * @return bool
206  
-     */
207  
-    protected function isSubclassOf($class, $type)
208  
-    {
209  
-        if (version_compare(PHP_VERSION, '5.3.7', 'gte')) {
210  
-            return is_subclass_of($class, $type);
211  
-        }
212  
-
213  
-        $r = new ClassReflection($class);
214  
-        $interfaces = $r->getInterfaceNames();
215  
-        return (in_array($type, $interfaces));
216  
-    }
217 195
 }
47  library/Zend/Stdlib/SubClass.php
... ...
@@ -0,0 +1,47 @@
  1
+<?php
  2
+/**
  3
+ * Zend Framework (http://framework.zend.com/)
  4
+ *
  5
+ * @link      http://github.com/zendframework/zf2 for the canonical source repository
  6
+ * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7
+ * @license   http://framework.zend.com/license/new-bsd New BSD License
  8
+ * @package   Zend_Stdlib
  9
+ */
  10
+
  11
+namespace Zend\Stdlib;
  12
+
  13
+use ReflectionClass;
  14
+
  15
+/**
  16
+ * @see https://bugs.php.net/bug.php?id=53727
  17
+ *
  18
+ * @category   Zend
  19
+ * @package    Zend_Stdlib
  20
+ * @subpackage SubClass
  21
+ */
  22
+abstract class SubClass
  23
+{
  24
+
  25
+    /**
  26
+     * Checks if the object has this class as one of its parents
  27
+     *
  28
+     * @see https://bugs.php.net/bug.php?id=53727
  29
+     *
  30
+     * @param string $className
  31
+     * @param string $type
  32
+     */
  33
+    public static function isSubclassOf($className, $type)
  34
+    {
  35
+        if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  36
+            return is_subclass_of($className, $type);
  37
+        }
  38
+        if (is_subclass_of($className, $type)) {
  39
+            return true;
  40
+        }
  41
+        if (!interface_exists($type)) {
  42
+            return false;
  43
+        }
  44
+        $r = new ReflectionClass($className);
  45
+        return $r->implementsInterface($type);
  46
+    }
  47
+}
7  library/Zend/XmlRpc/Server.php
@@ -21,10 +21,10 @@
21 21
 
22 22
 namespace Zend\XmlRpc;
23 23
 
24  
-use ReflectionClass;
25 24
 use Zend\Server\AbstractServer;
26 25
 use Zend\Server\Definition;
27 26
 use Zend\Server\Reflection;
  27
+use Zend\Stdlib\SubClass;
28 28
 
29 29
 /**
30 30
  * An XML-RPC server implementation
@@ -452,10 +452,9 @@ public function getResponse()
452 452
      */
453 453
     public function setResponseClass($class)
454 454
     {
455  
-        if (!class_exists($class) or
456  
-            ($c = new ReflectionClass($class) and !$c->isSubclassOf('Zend\\XmlRpc\\Response'))) {
457  
-
  455
+        if (!class_exists($class) || !SubClass::isSubclassOf($class, 'Zend\XmlRpc\Response')) {
458 456
             throw new Server\Exception\InvalidArgumentException('Invalid response class');
  457
+
459 458
         }
460 459
         $this->responseClass = $class;
461 460
         return true;
30  tests/Zend/Stdlib/SubClassTest.php
... ...
@@ -0,0 +1,30 @@
  1
+<?php
  2
+/**
  3
+ * Zend Framework (http://framework.zend.com/)
  4
+ *
  5
+ * @link      http://github.com/zendframework/zf2 for the canonical source repository
  6
+ * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7
+ * @license   http://framework.zend.com/license/new-bsd New BSD License
  8
+ * @package   Zend_Stdlib
  9
+ */
  10
+
  11
+namespace ZendTest\Stdlib;
  12
+
  13
+use Zend\Stdlib\SubClass;
  14
+
  15
+class SubClassTest extends \PHPUnit_Framework_TestCase
  16
+{
  17
+    protected function setUp()
  18
+    {
  19
+        require_once 'TestAsset/DummySubclasses.php';
  20
+    }
  21
+
  22
+    public function testIsSubclassOf()
  23
+    {
  24
+        $test1 = SubClass::isSubclassOf('ZendTest\Stdlib\TestAsset\ChildClass', 'ZendTest\Stdlib\TestAsset\TestInterface');
  25
+        $test2 = SubClass::isSubclassOf('ZendTest\Stdlib\TestAsset\ParentClass', 'ZendTest\Stdlib\TestAsset\TestInterface');
  26
+        $this->assertTrue($test1);
  27
+        $this->assertTrue($test2);
  28
+    }
  29
+
  30
+}
16  tests/Zend/Stdlib/TestAsset/DummySubclasses.php
... ...
@@ -0,0 +1,16 @@
  1
+<?php
  2
+
  3
+namespace ZendTest\Stdlib\TestAsset
  4
+{
  5
+    interface TestInterface
  6
+    {
  7
+    }
  8
+
  9
+    class ParentClass implements TestInterface
  10
+    {
  11
+    }
  12
+
  13
+    class ChildClass extends ParentClass
  14
+    {
  15
+    }
  16
+}
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.