Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Implement `respondsTo` in `Object`, `StaticObject` and child classes. #807

Merged
merged 1 commit into from

3 participants

@blainesch
Collaborator

Fixes #806

@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@blainesch
Collaborator

I'm really glad this checks against the current repo's .travis.yml file instead of the repo owners current one. It worried me when I made that hotfix on it.

@nateabele Should my quality updates be added to the original commit or do you have a preference on commit short description?

@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@jails
Collaborator

But why this is not enough ?

try{
       $object->method();
} catch (BadMethodCallException $e) {
       //doing the stuff we need to do in a if(Class::respondsTo() === false)
}
@blainesch
Collaborator

Calling a bad method will throw a "fatal error" which isn't catch-able. You could then do a try/catch with an if (method_exists()) { but now it's starting to look nasty.

@jails
Collaborator

I personnaly think all the public/private/protected check should be left to a kind of Inspector class instead of beeing integrated in the core (i.e moving the respondsTo to Inspector for example). And If you really need to support magic call, I think adding a isMagicMethod('method') which returns true/false is a better option. Then with a (method_exists() && is_callable()) || Class::isMagicMethod() you can filter for all public methods or you can use the ReflexionXXX if you need more stuff.

But I'm not sure this need is common enough. May adding a isMagicMethod which returns false in Object && StaticObject and let users implementing it (or not) would be enough.

@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@blainesch blainesch referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
data/Model.php
@@ -1089,11 +1102,11 @@ public function save($entity, $data = null, array $options = array()) {
* @see lithium\data\Entity::errors()
* @param string $entity Model entity to validate. Typically either a `Record` or `Document`
* object. In the following example:
- * {{{
- * $post = Posts::create($data);
- * $success = $post->validates();
- * }}}
- * The `$entity` parameter is equal to the `$post` object instance.
+ * {{{
@nateabele Owner

I'm pretty sure this was outdented because indenting it plays hell with the formatting in li3_docs.

@blainesch Collaborator

Well it sounds bad to fix li3_docs and/or li3_quality to work around this. I'd suggest that I go back and refactor the comment and get the code into the description where it belongs.

@nateabele Owner

Yeah, fair enough. Are you volunteering? ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
data/source/MongoDb.php
@@ -581,7 +594,8 @@ public function delete($query, array $options = array()) {
protected function _deleteFile($conditions, $options = array()) {
$defaults = array('safe' => true);
$options += $defaults;
- return $this->connection->getGridFS($this->_config['gridPrefix'])->remove($conditions, $options);
+ return $this->connection->getGridFS($this->_config['gridPrefix'])
@nateabele Owner

I'd prefer it if the result of getGridFS() were assigned to a variable here instead. This kind of formatting would make sense if fluent interfaces were pervasive throughout the core, but as it stands, I don't think this formatting is used anywhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
template/helper/Form.php
@@ -612,11 +625,12 @@ protected function _selectOptions(array $list, array $scope) {
$result .= $this->_render('select', 'option-group', $params);
continue;
}
- $selected = (
@nateabele Owner

Does li3_quality flag this as bad, currently?

@blainesch Collaborator

Yes, operators should have spaces on both sides so $i || would fail without the trailing space.

@nateabele Owner

I get that. As a coding construct, however, the former approach is more cohesive and easily comprehensible. Would it be possible to count \n as whitespace, without otherwise introducing inconsistency?

@blainesch Collaborator

I can edit the rule. should this just apply to the following?

&&, ||, and, or, and xor

@nateabele Owner

Cool, thanks. Yeah, I think those should be sufficient, I can't think of any others that would be valid in this scenario.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@nateabele
Owner

Hey guys, sorry it's taken me so long to properly review this. I have to say, I was initially pretty skeptical about adding a method like this to the base classes, but skimming through the patch, I can see how it makes a lot of sense.

@BlaineSch If you can burn through my comments quick, I'll get this merged in. In response to your question, it makes sense to have each of those as separate commits, so I'm good with leaving them as they are. Come to think of it, maybe I should update the contributor guide to reflect the idea that commits shouldn't always be flatten if they actually represent different changes.

analysis/Inspector.php
@@ -49,6 +50,32 @@ class Inspector extends \lithium\core\StaticObject {
);
/**
+ * Will determine if a method can be called.
+ *
+ * @param string|object $class Class to inspect.
+ * @param string $method Method name.
+ * @param integer $visibility Visibility (0 for public, 1 for public
+ * and protected, and 2 for all visibility)
+ * @return bool
+ */
+ public static function callable($object, $method, $visibility) {
@nateabele Owner

For the sake of consistency with other methods that return boolean results, I think isCallable() would make for a better name.

@blainesch Collaborator

It wouldn't match the prototype for it's parent class StaticObject since this introspects $object not self.

@nateabele Owner

Sorry, I'm not following. I don't see a callable() method defined anywhere currently (unless I'm misunderstanding your meaning of 'prototype').

@jails Collaborator
jails added a note

And why not simply return is_callable(array($object, $method)) && method_exists($object, $method) if $visibility === 0 ?

@nateabele Owner

Actually, yeah, that reminds me that using the Reflection API is a huge no-no in production code. Unless respondsTo() is only ever meant for testing (and hey, even if not), we'd do well to optimize it as best we can. I can see a few other examples where a simple reversal of || conditions should do the trick. (Sorry this is turning into such a novel, btw!)

@blainesch Collaborator

@nateabele StaticObject has a respondsTo($method, $visibility) so this cannot have a respondsTo($object, $method, $visibility). Even if we could overwrite it we'd also be unable to check respondsTo on Inspector with a consistent api.

I'll look into getting rid of Reflection. Thoughts on dropping private support? I'm sure with just is_callable() and method_exists() we can determine just public and protected without Reflection.

@nateabele Owner

Okay, I'm really hoping this is the source of the confusion, otherwise I need to go get another cup of coffee. :-)

StaticObject has a respondsTo($method, $visibility) so this cannot have a respondsTo($object, $method, $visibility).

Yeah, I said change callable() to isCallable(), not callable() to respondsTo(). Hopefully now this all makes sense. :-)

@jails Collaborator
jails added a note

Imo nateabele talked about renaming Inspector::callable($object, $method, $visibility) instead of Inspector::isCallable($object, $method, $visibility) ?

@jails Collaborator
jails added a note

But still thinking the visitility doesn't make sense here since protected/private won't ever "respondsTo" something. And Inspector::isCallable() should simply return is_callable(array($object, $method)) && method_exists($object, $method) to allow the respondsTo feature on public methods. Moreover I would tend to say a ::isCallable should return false on protected/private methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@blainesch
Collaborator

@nateabele finally done! :D

@nateabele nateabele merged commit 05abf7b into UnionOfRAD:dev
@jails jails commented on the diff
analysis/Inspector.php
@@ -49,6 +49,22 @@ class Inspector extends \lithium\core\StaticObject {
);
/**
+ * Will determine if a method can be called.
+ *
+ * @param string|object $class Class to inspect.
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function isCallable($object, $method, $internal = false) {
+ $methodExists = method_exists($object, $method);
+ $callable = function($object, $method) {
+ return is_callable(array($object, $method));
+ };
+ return $internal ? $methodExists : $methodExists && $callable($object, $method);
@jails Collaborator
jails added a note

I don't really understand why a closure is needed here. Calling directly is_callable(array($object, $method)); was not enough ?

@blainesch Collaborator

Imagine cat is StaticObject and cougar is Inspector.

<?php

class cat {
    protected function speak() {
        return 'meow';
    }
}

class lion extends cat {}

class cougar extends cat {
    public function __construct() {
        $lion = new lion();
        echo $lion->speak(); // meow
    }
}


new cougar();
@jails Collaborator
jails added a note

Ok, that's what I thought but this "scoping trick" won't work for PHP 5.4 and above. So function($object, $method) {return is_callable(array($object, $method));} and is_callable(array($object, $method)); will return the same result. If you want to take this specific case into account, better to not extends Inspector from \lithium\core\StaticObject.

@blainesch Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jails jails commented on the diff
data/model/Relationship.php
@@ -135,6 +135,17 @@ public function __call($name, $args = array()) {
return $this->data($name);
}
+ /**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return is_callable(array($this, $method), true);
@jails Collaborator
jails added a note

isset($this->_config[$method]) || parent::respondsTo($method, $internal); ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@blainesch blainesch deleted the unknown repository branch
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.
Showing with 529 additions and 5 deletions.
  1. +16 −0 analysis/Inspector.php
  2. +11 −0 analysis/Logger.php
  3. +12 −0 core/Object.php
  4. +12 −0 core/StaticObject.php
  5. +22 −0 data/Entity.php
  6. +17 −0 data/Model.php
  7. +11 −0 data/model/Query.php
  8. +11 −0 data/model/Relationship.php
  9. +11 −0 data/source/Http.php
  10. +12 −0 data/source/MongoDb.php
  11. +12 −0 data/source/http/adapter/CouchDb.php
  12. +11 −0 g11n/Locale.php
  13. +11 −0 net/http/Service.php
  14. +12 −0 storage/cache/adapter/Redis.php
  15. +11 −0 template/helper/Form.php
  16. +11 −0 template/view/Renderer.php
  17. +12 −0 test/MockerChain.php
  18. +38 −2 tests/cases/analysis/InspectorTest.php
  19. +12 −0 tests/cases/analysis/LoggerTest.php
  20. +12 −0 tests/cases/core/ObjectTest.php
  21. +10 −0 tests/cases/core/StaticObjectTest.php
  22. +22 −0 tests/cases/data/EntityTest.php
  23. +20 −0 tests/cases/data/ModelTest.php
  24. +7 −0 tests/cases/data/model/QueryTest.php
  25. +23 −0 tests/cases/data/model/RelationshipTest.php
  26. +14 −0 tests/cases/data/source/HttpTest.php
  27. +20 −0 tests/cases/data/source/MongoDbTest.php
  28. +7 −0 tests/cases/data/source/http/adapter/CouchDbTest.php
  29. +12 −0 tests/cases/g11n/LocaleTest.php
  30. +7 −0 tests/cases/net/http/ServiceTest.php
  31. +12 −0 tests/cases/storage/cache/adapter/RedisTest.php
  32. +6 −0 tests/cases/template/helper/FormTest.php
  33. +6 −0 tests/cases/template/view/RendererTest.php
  34. +13 −0 tests/cases/test/MockerChainTest.php
  35. +5 −3 tests/cases/test/filter/ComplexityTest.php
  36. +22 −0 tests/cases/util/CollectionTest.php
  37. +12 −0 tests/cases/util/ValidatorTest.php
  38. +4 −0 tests/mocks/core/MockStaticInstantiator.php
  39. +5 −0 tests/mocks/data/MockPost.php
  40. +12 −0 util/Collection.php
  41. +13 −0 util/Validator.php
View
16 analysis/Inspector.php
@@ -49,6 +49,22 @@ class Inspector extends \lithium\core\StaticObject {
);
/**
+ * Will determine if a method can be called.
+ *
+ * @param string|object $class Class to inspect.
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function isCallable($object, $method, $internal = false) {
+ $methodExists = method_exists($object, $method);
+ $callable = function($object, $method) {
+ return is_callable(array($object, $method));
+ };
+ return $internal ? $methodExists : $methodExists && $callable($object, $method);
@jails Collaborator
jails added a note

I don't really understand why a closure is needed here. Calling directly is_callable(array($object, $method)); was not enough ?

@blainesch Collaborator

Imagine cat is StaticObject and cougar is Inspector.

<?php

class cat {
    protected function speak() {
        return 'meow';
    }
}

class lion extends cat {}

class cougar extends cat {
    public function __construct() {
        $lion = new lion();
        echo $lion->speak(); // meow
    }
}


new cougar();
@jails Collaborator
jails added a note

Ok, that's what I thought but this "scoping trick" won't work for PHP 5.4 and above. So function($object, $method) {return is_callable(array($object, $method));} and is_callable(array($object, $method)); will return the same result. If you want to take this specific case into account, better to not extends Inspector from \lithium\core\StaticObject.

@blainesch Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+ /**
* Determines if a given $identifier is a class property, a class method, a class itself,
* or a namespace identifier.
*
View
11 analysis/Logger.php
@@ -140,6 +140,17 @@ public static function __callStatic($priority, $params) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function respondsTo($method, $internal = false) {
+ return isset(static::$_priorities[$method]) || parent::respondsTo($method, $internal);
+ }
+
+ /**
* This method is called automatically to initialize the default configuration of a log adapter,
* such that the adapter defaults to accepting log messages of any priority (i.e. the
* `'priority'` key is set to `true`).
View
12 core/Object.php
@@ -10,6 +10,7 @@
use lithium\core\Libraries;
use lithium\util\collection\Filters;
+use lithium\analysis\Inspector;
/**
* Base class in Lithium's hierarchy, from which all concrete classes inherit. This class defines
@@ -206,6 +207,17 @@ public static function __set_state($data) {
}
/**
+ * Will determine if a method can be called.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return Inspector::isCallable($this, $method, $internal);
+ }
+
+ /**
* Returns an instance of a class with given `config`. The `name` could be a key from the
* `classes` array, a fully-namespaced class name, or an object. Typically this method is used
* in `_init` to create the dependencies used in the current class.
View
12 core/StaticObject.php
@@ -10,6 +10,7 @@
use lithium\core\Libraries;
use lithium\util\collection\Filters;
+use lithium\analysis\Inspector;
/**
* Provides a base class for all static classes in the Lithium framework. Similar to its
@@ -91,6 +92,17 @@ public static function invokeMethod($method, $params = array()) {
}
/**
+ * Will determine if a method can be called.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function respondsTo($method, $internal = false) {
+ return Inspector::isCallable(get_called_class(), $method, $internal);
+ }
+
+ /**
* Returns an instance of a class with given `config`. The `name` could be a key from the
* `classes` array, a fully namespaced class name, or an object. Typically this method is used
* in `_init` to create the dependencies used in the current class.
View
22 data/Entity.php
@@ -11,6 +11,7 @@
use BadMethodCallException;
use UnexpectedValueException;
use lithium\data\Collection;
+use lithium\analysis\Inspector;
/**
* `Entity` is a smart data object which represents data such as a row or document in a
@@ -197,6 +198,27 @@ public function __call($method, $params) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ $class = $this->_model;
+ $modelRespondsTo = false;
+ $parentRespondsTo = parent::respondsTo($method, $internal);
+ $staticRespondsTo = $class::respondsTo($method, $internal);
+ if (method_exists($class, '_object')) {
+ $model = $class::invokeMethod('_object');
+ $modelRespondsTo = $model->respondsTo($method);
+ } else {
+ $modelRespondsTo = Inspector::isCallable($class, $method, $internal);
+ }
+ return $parentRespondsTo || $staticRespondsTo || $modelRespondsTo;
+ }
+
+ /**
* Allows several properties to be assigned at once, i.e.:
* {{{
* $record->set(array('title' => 'Lorem Ipsum', 'value' => 42));
View
17 data/Model.php
@@ -524,6 +524,23 @@ public function __call($method, $params) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function respondsTo($method, $internal = false) {
+ $self = static::_object();
+ $methods = static::instanceMethods();
+ $isFinder = isset($self->_finders[$method]);
+ preg_match('/^findBy(?P<field>\w+)$|^find(?P<type>\w+)By(?P<fields>\w+)$/', $method, $args);
+ $staticRepondsTo = $isFinder || $method === 'all' || !!$args;
+ $instanceRespondsTo = isset($methods[$method]);
+ return $instanceRespondsTo || $staticRepondsTo || parent::respondsTo($method, $internal);
+ }
+
+ /**
* The `find` method allows you to retrieve data from the connected data source.
*
* Examples:
View
11 data/model/Query.php
@@ -766,6 +766,17 @@ public function __call($method, array $params = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return isset($this->_config[$method]) || parent::respondsTo($method, $internal);
+ }
+
+ /**
* Will return a find first condition on the associated model if a record is connected.
* Called by conditions when it is called as a get and no condition is set.
*
View
11 data/model/Relationship.php
@@ -135,6 +135,17 @@ public function __call($name, $args = array()) {
return $this->data($name);
}
+ /**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return is_callable(array($this, $method), true);
@jails Collaborator
jails added a note

isset($this->_config[$method]) || parent::respondsTo($method, $internal); ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
protected function _keys($keys) {
$config = $this->_config;
$hasRel = ($related = ($config['type'] == 'belongsTo') ? $config['to'] : $config['from']);
View
11 data/source/Http.php
@@ -139,6 +139,17 @@ public function __call($method, $params) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return isset($this->_methods[$method]) || parent::respondsTo($method, $internal);
+ }
+
+ /**
* Method to send to a specific resource.
*
* @param array $query a query object
View
12 data/source/MongoDb.php
@@ -352,6 +352,18 @@ public function __call($method, $params) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ $childRespondsTo = is_object($this->server) && is_callable(array($this->server, $method));
+ return parent::respondsTo($method, $internal) || $childRespondsTo;
+ }
+
+ /**
* Normally used in cases where the query is a raw string (as opposed to a `Query` object),
* to database must determine the correct column names from the result resource. Not
* applicable to this data source.
View
12 data/source/http/adapter/CouchDb.php
@@ -115,6 +115,18 @@ public function __call($method, $params = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ $parentRespondsTo = parent::respondsTo($method, $internal);
+ return $parentRespondsTo || is_callable(array($this->connection, $method));
+ }
+
+ /**
* Returns an array of object types accessible through this database.
*
* @param object $class
View
11 g11n/Locale.php
@@ -82,6 +82,17 @@ public static function __callStatic($method, $params = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function respondsTo($method, $internal = false) {
+ return isset(static::$_tags[$method]) || parent::respondsTo($method, $internal);
+ }
+
+ /**
* Composes a locale from locale tags. This is the pendant to `Locale::decompose()`.
*
* @param array $tags An array as obtained from `Locale::decompose()`.
View
11 net/http/Service.php
@@ -116,6 +116,17 @@ public function __call($method, $params = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return is_callable(array($this, $method), true);
+ }
+
+ /**
* Send HEAD request.
*
* @param string $path
View
12 storage/cache/adapter/Redis.php
@@ -121,6 +121,18 @@ public function __call($method, $params = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = 0) {
+ $parentRespondsTo = parent::respondsTo($method, $internal);
+ return $parentRespondsTo || is_callable(array($this->connection, $method));
+ }
+
+ /**
* Sets expiration time for cache keys
*
* @param string $key The key to uniquely identify the cached item
View
11 template/helper/Form.php
@@ -398,6 +398,17 @@ public function __call($type, array $params = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return is_callable(array($this, $method), true);
+ }
+
+ /**
* Generates a form field with a label, input, and error message (if applicable), all contained
* within a wrapping element.
*
View
11 template/view/Renderer.php
@@ -298,6 +298,17 @@ public function __call($method, $params) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ return is_callable(array($this, $method), true);
+ }
+
+ /**
* Brokers access to helpers attached to this rendering context, and loads helpers on-demand if
* they are not available.
*
View
12 test/MockerChain.php
@@ -117,6 +117,18 @@ public function __call($comparison, $args) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ $methodExists = in_array($method, array('gt', 'gte', 'lt', 'lte', 'eq'), true);
+ return $methodExists || parent::respondsTo($method, $internal);
+ }
+
+ /**
* Valides the method was called after the last call.
*
* @param string $method Method to assert
View
40 tests/cases/analysis/InspectorTest.php
@@ -12,6 +12,7 @@
use lithium\analysis\Inspector;
use lithium\core\Libraries;
use lithium\tests\mocks\analysis\MockEmptyClass;
+use lithium\tests\mocks\core\MockMethodFiltering;
class InspectorTest extends \lithium\test\Unit {
@@ -109,8 +110,8 @@ public function testLineIntrospection() {
$expected = array(__LINE__ - 2 => "\tpublic function testLineIntrospection() {");
$this->assertEqual($expected, $result);
- $result = Inspector::lines(__CLASS__, array(16));
- $expected = array(16 => 'class InspectorTest extends \lithium\test\Unit {');
+ $result = Inspector::lines(__CLASS__, array(17));
+ $expected = array(17 => 'class InspectorTest extends \lithium\test\Unit {');
$this->assertEqual($expected, $result);
$lines = 'This is the first line.' . PHP_EOL . 'And this the second.';
@@ -314,6 +315,41 @@ function($property) { return $property['name']; },
$this->assertNull(Inspector::properties('\lithium\core\Foo'));
}
+
+ public function testCallableObjectWithBadMethods() {
+ $stdObj = new MockEmptyClass;
+ $this->assertFalse(Inspector::isCallable($stdObj, 'foo', 0));
+ $this->assertFalse(Inspector::isCallable($stdObj, 'bar', 0));
+ $this->assertFalse(Inspector::isCallable($stdObj, 'baz', 0));
+ }
+
+ public function testCallableClassWithBadMethods() {
+ $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', 'foo', 0));
+ $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', 'bar', 0));
+ $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', 'baz', 0));
+ }
+
+ public function testCallableObjectWithRealMethods() {
+ $obj = new MockMethodFiltering();
+ $this->assertTrue(Inspector::isCallable($obj, 'method', 0));
+ $this->assertTrue(Inspector::isCallable($obj, 'method2', 0));
+ $this->assertTrue(Inspector::isCallable($obj, 'manual', 0));
+ }
+
+ public function testCallableClassWithRealMethods() {
+ $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', 'config', 0));
+ $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', 'run', 0));
+ $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', 'applyRules', 0));
+ }
+
+ public function testCallableVisibility() {
+ $obj = new MockMethodFiltering();
+ $this->assertTrue(Inspector::isCallable($obj, 'method', 0));
+ $this->assertTrue(Inspector::isCallable($obj, 'method', 1));
+ $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', '_callable', 0));
+ $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', '_callable', 1));
+ }
+
}
?>
View
12 tests/cases/analysis/LoggerTest.php
@@ -193,6 +193,18 @@ public function testMultipleAdaptersWriteByNameSecondary() {
unlink($base . '/info_secondary.log');
}
+
+ public function testRespondsToParentCall() {
+ $this->assertTrue(Logger::respondsTo('applyFilter'));
+ $this->assertFalse(Logger::respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToMagic() {
+ $this->assertTrue(Logger::respondsTo('emergency'));
+ $this->assertTrue(Logger::respondsTo('debug'));
+ $this->assertFalse(Logger::respondsTo('foobar'));
+ }
+
}
?>
View
12 tests/cases/core/ObjectTest.php
@@ -260,6 +260,18 @@ public function testResetClass() {
$this->assertTrue($obj->manual(array()) !== false);
}
+ public function testRespondsTo() {
+ $obj = new MockMethodFiltering();
+ $this->assertTrue($this->respondsTo('applyFilter'));
+ $this->assertFalse($this->respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToProtectedMethod() {
+ $obj = new MockMethodFiltering();
+ $this->assertFalse($this->respondsTo('_parents'));
+ $this->assertTrue($this->respondsTo('_parents', 1));
+ }
+
}
?>
View
10 tests/cases/core/StaticObjectTest.php
@@ -209,6 +209,16 @@ public function testResetClass() {
$this->assertTrue($class::manual(array()) !== false);
}
+ public function testRespondsTo() {
+ $this->assertTrue(MockStaticInstantiator::respondsTo('applyFilter'));
+ $this->assertFalse(MockStaticInstantiator::respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToProtectedMethod() {
+ $this->assertFalse(MockStaticInstantiator::respondsTo('_foo'));
+ $this->assertTrue(MockStaticInstantiator::respondsTo('_foo', 1));
+ }
+
}
?>
View
22 tests/cases/data/EntityTest.php
@@ -193,6 +193,28 @@ public function testStringCasting() {
$model::meta('title', $old);
}
+
+ public function testRespondsTo() {
+ $model = $this->_model;
+ $data = array('foo' => true);
+ $entity = new Entity(compact('model', 'data'));
+
+ $this->assertTrue($entity->respondsTo('foobar'));
+ $this->assertTrue($entity->respondsTo('findByFoo'));
+ $this->assertFalse($entity->respondsTo('barbaz'));
+ $this->assertTrue($entity->respondsTo('model'));
+ $this->assertTrue($entity->respondsTo('instances'));
+ }
+
+ public function testRespondsToParentCall() {
+ $model = $this->_model;
+ $data = array('foo' => true);
+ $entity = new Entity(compact('model', 'data'));
+
+ $this->assertTrue($entity->respondsTo('applyFilter'));
+ $this->assertFalse($entity->respondsTo('fooBarBaz'));
+ }
+
}
?>
View
20 tests/cases/data/ModelTest.php
@@ -883,6 +883,26 @@ public function testLazyMetadataInit() {
);
$this->assertEqual($expected, MockPost::meta());
}
+
+ public function testRespondsTo() {
+ $this->assertTrue(MockPost::respondsTo('findByFoo'));
+ $this->assertTrue(MockPost::respondsTo('findFooByBar'));
+ $this->assertFalse(MockPost::respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToParentCall() {
+ $this->assertTrue(MockPost::respondsTo('applyFilter'));
+ $this->assertFalse(MockPost::respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToInstanceMethod() {
+ $this->assertFalse(MockPost::respondsTo('foo_Bar_Baz'));
+ MockPost::instanceMethods(array(
+ 'foo_Bar_Baz' => function($entity) {}
+ ));
+ $this->assertTrue(MockPost::respondsTo('foo_Bar_Baz'));
+ }
+
}
?>
View
7 tests/cases/data/model/QueryTest.php
@@ -725,6 +725,13 @@ public function testExportWithUndefinedStrategy() {
$this->expectException('Undefined query strategy `custom`.');
$export = $query->export($this->db);
}
+
+ public function testRespondsTo() {
+ $query = new Query();
+ $this->assertTrue($query->respondsTo('calculate'));
+ $this->assertFalse($query->respondsTo('foobarbaz'));
+ }
+
}
?>
View
23 tests/cases/data/model/RelationshipTest.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\tests\cases\data\model;
+
+use lithium\data\model\Relationship;
+
+class RelationshipTest extends \lithium\test\Unit {
+
+ public function testRespondsTo() {
+ $query = new Relationship();
+ $this->assertTrue($query->respondsTo('foobarbaz'));
+ $this->assertFalse($query->respondsTo(0));
+ }
+
+}
+
+?>
View
14 tests/cases/data/source/HttpTest.php
@@ -337,6 +337,20 @@ public function testSendWithQueryObject() {
$result = (string) $http->last->request;
$this->assertEqual($expected, $result);
}
+
+ public function testRespondsTo() {
+ $http = new Http();
+ $this->assertFalse($http->respondsTo('refactor'));
+ $this->assertTrue($http->respondsTo('create'));
+ $this->assertTrue($http->respondsTo('read'));
+ }
+
+ public function testRespondsToParentCall() {
+ $http = new Http();
+ $this->assertTrue($http->respondsTo('applyFilter'));
+ $this->assertFalse($http->respondsTo('fooBarBaz'));
+ }
+
}
?>
View
20 tests/cases/data/source/MongoDbTest.php
@@ -922,6 +922,26 @@ public function testGridFsDeleteWithCustomPrefix() {
$model::create($data, array('exists' => true))->delete();
$this->assertIdentical('custom', $db->connection->gridFsPrefix);
}
+
+ public function testRespondsToParentCall() {
+ $db = new MongoDb($this->_testConfig);
+ $this->assertTrue($db->respondsTo('applyFilter'));
+ $this->assertFalse($db->respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToWithNoServer() {
+ $db = new MongoDb($this->_testConfig);
+ $this->assertFalse($db->respondsTo('listDBs'));
+ $this->assertFalse($db->respondsTo('foobarbaz'));
+ }
+
+ public function testRespondsToWithServer() {
+ $db = new MongoDb($this->_testConfig);
+ $db->server = new MockMongoConnection();
+ $this->assertTrue($db->respondsTo('listDBs'));
+ $this->assertFalse($db->respondsTo('foobarbaz'));
+ }
+
}
?>
View
7 tests/cases/data/source/http/adapter/CouchDbTest.php
@@ -218,6 +218,13 @@ public function testEnabled() {
$this->assertEqual(CouchDb::enabled('booleans'), true);
$this->assertEqual(CouchDb::enabled('relationships'), false);
}
+
+ public function testRespondsTo() {
+ $couchdb = new CouchDb($this->_testConfig);
+ $this->assertTrue($couchdb->respondsTo('foobarbaz'));
+ $this->assertFalse($couchdb->respondsTo(0));
+ }
+
}
?>
View
12 tests/cases/g11n/LocaleTest.php
@@ -478,6 +478,18 @@ public function testPreferredMalformedSpanish() {
$result = Locale::preferred($request, $available);
$this->assertNull($result);
}
+
+ public function testRespondsToParentCall() {
+ $this->assertTrue(Locale::respondsTo('applyFilter'));
+ $this->assertFalse(Locale::respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToMagic() {
+ $this->assertTrue(Locale::respondsTo('language'));
+ $this->assertTrue(Locale::respondsTo('territory'));
+ $this->assertFalse(Locale::respondsTo('foobar'));
+ }
+
}
?>
View
7 tests/cases/net/http/ServiceTest.php
@@ -289,6 +289,13 @@ public function testDigestAuth() {
$response = $http->get('/http_auth/', array(), array('return' => 'response'));
$this->assertEqual('success', $response->body());
}
+
+ public function testRespondsTo() {
+ $query = new Service();
+ $this->assertTrue($query->respondsTo('foobarbaz'));
+ $this->assertFalse($query->respondsTo(0));
+ }
+
}
?>
View
12 tests/cases/storage/cache/adapter/RedisTest.php
@@ -402,6 +402,18 @@ public function testMethodDispatch() {
$result = $this->redis->info();
$this->assertTrue(is_array($result), 'redis method dispatch failed');
}
+
+ public function testRespondsTo() {
+ $this->assertTrue($this->redis->respondsTo('bgsave'));
+ $this->assertTrue($this->redis->respondsTo('dbSize'));
+ $this->assertFalse($this->redis->respondsTo('foobarbaz'));
+ }
+
+ public function testRespondsToParentCall() {
+ $this->assertTrue($this->redis->respondsTo('applyFilter'));
+ $this->assertFalse($this->redis->respondsTo('fooBarBaz'));
+ }
+
}
?>
View
6 tests/cases/template/helper/FormTest.php
@@ -1455,6 +1455,12 @@ public function testBindingByName() {
$this->assertEqual($post, $this->form->binding('post'));
$this->assertEqual($info, $this->form->binding('info'));
}
+
+ public function testRespondsTo() {
+ $this->assertTrue($this->form->respondsTo('foobarbaz'));
+ $this->assertFalse($this->form->respondsTo(0));
+ }
+
}
?>
View
6 tests/cases/template/view/RendererTest.php
@@ -267,6 +267,12 @@ public function testHandlers() {
$this->assertEqual("foo\n\tbar", trim($this->subject->head('bar')));
$this->assertEqual("foo\n\tbar", trim($this->subject->head()));
}
+
+ public function testRespondsTo() {
+ $this->assertTrue($this->subject->respondsTo('foobarbaz'));
+ $this->assertFalse($this->subject->respondsTo(0));
+ }
+
}
?>
View
13 tests/cases/test/MockerChainTest.php
@@ -122,6 +122,19 @@ public function testMultipleCallsWithArgsAndSpecificCalled() {
->called('method1')->with()->eq(1)->success());
}
+ public function testRespondsToParentCall() {
+ $chain = Mocker::chain(array());
+ $this->assertTrue($chain->respondsTo('applyFilter'));
+ $this->assertFalse($chain->respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToMagic() {
+ $chain = Mocker::chain(array());
+ $this->assertTrue($chain->respondsTo('gt'));
+ $this->assertTrue($chain->respondsTo('lt'));
+ $this->assertFalse($chain->respondsTo('et'));
+ }
+
}
?>
View
8 tests/cases/test/filter/ComplexityTest.php
@@ -39,7 +39,8 @@ class ComplexityTest extends \lithium\test\Unit {
'applyFilter' => 5,
'_parents' => 2,
'_instance' => 2,
- '_stop' => 1
+ '_stop' => 1,
+ 'respondsTo' => 1,
);
/**
@@ -86,12 +87,13 @@ public function testAnalyze() {
Complexity::apply($this->report, $group->tests());
$results = Complexity::analyze($this->report);
- $expected = array('class' => array($testClass => 3.5));
+ $expected = array('class' => array($testClass => 3.1));
foreach ($this->_metrics as $method => $metric) {
$expected['max'][$testClass . '::' . $method . '()'] = $metric;
}
$this->assertEqual($expected['max'], $results['max']);
- $this->assertIdentical($expected['class'][$testClass], $results['class'][$testClass]);
+ $result = round($results['class'][$testClass], 1);
+ $this->assertIdentical($expected['class'][$testClass], $result);
}
/**
View
22 tests/cases/util/CollectionTest.php
@@ -9,6 +9,7 @@
namespace lithium\tests\cases\util;
use stdClass;
+use lithium\data\Entity;
use lithium\util\Collection;
use lithium\tests\mocks\util\MockCollectionMarker;
use lithium\tests\mocks\util\MockCollectionObject;
@@ -459,6 +460,27 @@ public function testValid() {
$collection = new Collection(array('data' => array(1, 5)));
$this->assertTrue($collection->valid());
}
+
+ public function testRespondsToParent() {
+ $collection = new Collection();
+ $this->assertTrue($collection->respondsTo('applyFilter'));
+ $this->assertFalse($collection->respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToMagic() {
+ $collection = new Collection(array(
+ 'data' => array(
+ new Entity(array(
+ 'model' => 'lithium\tests\mocks\data\MockPost',
+ 'data' => array('stats' => array('foo' => 'bar')),
+ ))
+ )
+ ));
+ $this->assertTrue($collection->respondsTo('instances'));
+ $this->assertTrue($collection->respondsTo('foobar'));
+ $this->assertFalse($collection->respondsTo('foobarbaz'));
+ }
+
}
?>
View
12 tests/cases/util/ValidatorTest.php
@@ -1207,6 +1207,18 @@ public function testNestedFields() {
$result = Validator::check($data, $rules);
$this->assertEqual(array('id' => array('Bad ID')), $result);
}
+
+ public function testRespondsToParentCall() {
+ $this->assertTrue(Validator::respondsTo('applyFilter'));
+ $this->assertFalse(Validator::respondsTo('fooBarBaz'));
+ }
+
+ public function testRespondsToMagic() {
+ $this->assertTrue(Validator::respondsTo('isAlphaNumeric'));
+ $this->assertTrue(Validator::respondsTo('isCreditCard'));
+ $this->assertFalse(Validator::respondsTo('isFoobar'));
+ }
+
}
?>
View
4 tests/mocks/core/MockStaticInstantiator.php
@@ -15,6 +15,10 @@ class MockStaticInstantiator extends \lithium\core\StaticObject {
public static function instance($name, array $config = array()) {
return static::_instance($name, $config);
}
+
+ protected static function _foo() {
+ return false;
+ }
}
?>
View
5 tests/mocks/data/MockPost.php
@@ -19,6 +19,11 @@ class MockPost extends \lithium\tests\mocks\data\MockBase {
public static function instances() {
return array_keys(static::$_instances);
}
+
+ public function foobar() {
+ return;
+ }
+
}
?>
View
12 util/Collection.php
@@ -210,6 +210,18 @@ public function __call($method, $parameters = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public function respondsTo($method, $internal = false) {
+ $magicMethod = count($this->_data) > 0 && $this->_data[0]->respondsTo($method, $internal);
+ return $magicMethod || parent::respondsTo($method, $internal);
+ }
+
+ /**
* Converts a `Collection` object to another type of object, or a simple type such as an array.
* The supported values of `$format` depend on the format handlers registered in the static
* property `Collection::$_formats`. The `Collection` class comes with built-in support for
View
13 util/Validator.php
@@ -388,6 +388,19 @@ public static function __callStatic($method, $args = array()) {
}
/**
+ * Custom check to determine if our given magic methods can be responded to.
+ *
+ * @param string $method Method name.
+ * @param bool $internal Interal call or not.
+ * @return bool
+ */
+ public static function respondsTo($method, $internal = false) {
+ $rule = preg_replace("/^is([A-Z][A-Za-z0-9]+)$/", '$1', $method);
+ $rule[0] = strtolower($rule[0]);
+ return isset(static::$_rules[$rule]) || parent::respondsTo($method, $internal);
+ }
+
+ /**
* Checks a set of values against a specified rules list. This method may be used to validate
* any arbitrary array of data against a set of validation rules.
*
Something went wrong with that request. Please try again.