From 1f1f93188ca7759ef334de6652c1678e9c6bef9e Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sat, 24 Sep 2016 18:30:43 +1200 Subject: [PATCH] Refactored field resolvers Needed a "setter" functionality, and in fact it has little to do with actual "resolving" in GraphQL sense --- src/Resolver/DataObjectLowerCamelResolver.php | 44 ----- src/Resolver/IResolver.php | 18 -- src/Util/CaseInsensitiveFieldAccessor.php | 163 ++++++++++++++++++ tests/Fake/DataObjectFake.php | 18 ++ .../Util/CaseInsensitiveFieldAccessorTest.php | 116 +++++++++++++ 5 files changed, 297 insertions(+), 62 deletions(-) delete mode 100644 src/Resolver/DataObjectLowerCamelResolver.php delete mode 100644 src/Resolver/IResolver.php create mode 100644 src/Util/CaseInsensitiveFieldAccessor.php create mode 100644 tests/Util/CaseInsensitiveFieldAccessorTest.php diff --git a/src/Resolver/DataObjectLowerCamelResolver.php b/src/Resolver/DataObjectLowerCamelResolver.php deleted file mode 100644 index 83e0e2827..000000000 --- a/src/Resolver/DataObjectLowerCamelResolver.php +++ /dev/null @@ -1,44 +0,0 @@ -hasMethod($info->fieldName)) { - return $object->{$info->fieldName}(); - } - - // Correct case (and getters) - if($object->hasField($info->fieldName)) { - return $object->{$info->fieldName}; - } - - // Infer casing - if($object instanceof DataObject) { - $parents = ClassInfo::ancestry($object, true); - foreach($parents as $parent) { - $fields = DataObject::database_fields($parent); - foreach($fields as $fieldName => $fieldClass) { - if(strcasecmp($fieldName, $info->fieldName) === 0) { - return $object->getField($fieldName); - } - } - } - } - } - -} diff --git a/src/Resolver/IResolver.php b/src/Resolver/IResolver.php deleted file mode 100644 index 233e052fd..000000000 --- a/src/Resolver/IResolver.php +++ /dev/null @@ -1,18 +0,0 @@ - true] + * @return mixed + */ + public function getValue(ViewableData $object, $fieldName, $opts = []) + { + $opts = $opts ?: []; + $opts = array_merge([ + self::HAS_METHOD => true, + self::HAS_FIELD => true, + self::HAS_SETTER => false, + self::DATAOBJECT => true, + ], $opts); + + $objectFieldName = $this->getObjectFieldName($object, $fieldName, $opts); + + if(!$objectFieldName) { + throw new InvalidArgumentException(sprintf( + 'Field name or method "%s" does not exist on %s', + $fieldName, + (string)$object + )); + } + + // Correct case for methods (e.g. canView) + if($object->hasMethod($objectFieldName)) { + return $object->{$objectFieldName}(); + } + + // Correct case (and getters) + if($object->hasField($objectFieldName)) { + return $object->{$objectFieldName}; + } + + return null; + } + + /** + * @param ViewableData $object The parent resolved object + * @param string $fieldName Name of the field/getter/method + * @param mixed $value + * @param array $opts Map of which lookups to use (class constants to booleans). + * Example: [ViewableDataCaseInsensitiveFieldMapper::HAS_METHOD => true] + * @return mixed + */ + public function setValue(ViewableData $object, $fieldName, $value, $opts = []) + { + $opts = $opts ?: []; + $opts = [ + self::HAS_METHOD => true, + self::HAS_FIELD => true, + self::HAS_SETTER => true, + self::DATAOBJECT => true, + ] + $opts; + + $objectFieldName = $this->getObjectFieldName($object, $fieldName, $opts); + + if(!$objectFieldName) { + throw new InvalidArgumentException(sprintf( + 'Field name "%s" does not exist on %s', + $fieldName, + (string)$object + )); + } + + // Correct case for methods (e.g. canView) + if($object->hasMethod($objectFieldName)) { + $object->{$objectFieldName}($value); + } + + // Correct case (and getters) + if($object->hasField($objectFieldName)) { + $object->{$objectFieldName} = $value; + } + + // Infer casing + if($object instanceof DataObject) { + $object->setField($objectFieldName, $value); + } + + return null; + } + + /** + * @param $object The object to resolve a name on + * @param string $fieldName Name in different casing + * @param array $opts Map of which lookups to use (class constants to booleans). + * Example: [ViewableDataCaseInsensitiveFieldMapper::HAS_METHOD => true] + * @return null|string Name in actual casing on $object + */ + protected function getObjectFieldName(ViewableData $object, $fieldName, $opts = []) + { + $optFn = function($type) use(&$opts) { + return (in_array($type, $opts) && $opts[$type] === true); + }; + + // Correct case (and getters) + if($optFn(self::HAS_FIELD) && $object->hasField($fieldName)) { + return $fieldName; + } + + // Infer casing from DataObject fields + if($optFn(self::DATAOBJECT) && $object instanceof DataObject) { + $parents = ClassInfo::ancestry($object, true); + foreach($parents as $parent) { + $fields = DataObject::database_fields($parent); + foreach($fields as $objectFieldName => $fieldClass) { + if(strcasecmp($objectFieldName, $fieldName) === 0) { + return $objectFieldName; + } + } + } + } + + // Setters + // TODO Support for Object::$extra_methods (case sensitive array key check) + $setterName = "set" . ucfirst($fieldName); + if($optFn(self::HAS_SETTER) && $object->hasMethod($setterName)) { + return $setterName; + } + + // Correct case for methods (e.g. canView) - method_exists() is case insensitive + if($optFn(self::HAS_METHOD) && $object->hasMethod($fieldName)) { + return $fieldName; + } + + return null; + } + +} diff --git a/tests/Fake/DataObjectFake.php b/tests/Fake/DataObjectFake.php index 243f1a255..ecbd2da9b 100644 --- a/tests/Fake/DataObjectFake.php +++ b/tests/Fake/DataObjectFake.php @@ -7,6 +7,14 @@ class DataObjectFake extends DataObject implements TestOnly { + private static $db = [ + 'MyField' => 'Varchar' + ]; + + public $customSetterFieldResult; + + public $customSetterMethodResult; + public function getCustomGetter() { return 'customGetterValue'; @@ -16,4 +24,14 @@ public function customMethod() { return 'customMethodValue'; } + + public function setCustomSetterField($val) + { + $this->customSetterFieldResult = $val; + } + + public function customSetterMethod($val) + { + $this->customSetterMethodResult = $val; + } } diff --git a/tests/Util/CaseInsensitiveFieldAccessorTest.php b/tests/Util/CaseInsensitiveFieldAccessorTest.php new file mode 100644 index 000000000..0469d2b59 --- /dev/null +++ b/tests/Util/CaseInsensitiveFieldAccessorTest.php @@ -0,0 +1,116 @@ + 'myValue' + ]); + $mapper = new CaseInsensitiveFieldAccessor(); + $this->assertEquals('myValue', $mapper->getValue($fake, 'MyField')); + } + + public function testGetValueWithDifferentCasing() + { + $fake = new DataObjectFake([ + 'MyField' => 'myValue' + ]); + $mapper = new CaseInsensitiveFieldAccessor(); + $this->assertEquals('myValue', $mapper->getValue($fake, 'myfield')); + } + + public function testGetValueWithCustomGetter() + { + $fake = new DataObjectFake([]); + $mapper = new CaseInsensitiveFieldAccessor(); + $this->assertEquals('customGetterValue', $mapper->getValue($fake, 'customGetter')); + } + + public function testGetValueWithMethod() + { + $fake = new DataObjectFake([]); + $mapper = new CaseInsensitiveFieldAccessor(); + $this->assertEquals('customMethodValue', $mapper->getValue($fake, 'customMethod')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetValueWithUnknownFieldThrowsException() + { + $fake = new DataObjectFake([]); + $mapper = new CaseInsensitiveFieldAccessor(); + $mapper->getValue($fake, 'unknownField'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetValueWithCustomOpts() + { + $fake = new DataObjectFake([ + 'MyField' => 'myValue' + ]); + $mapper = new CaseInsensitiveFieldAccessor(); + $opts = [ + // only check for methods + CaseInsensitiveFieldAccessor::HAS_FIELD => false, + CaseInsensitiveFieldAccessor::DATAOBJECT => false, + ]; + $mapper->getValue($fake, 'MyField', $opts); + } + + public function testSetValueWithOriginalCasing() + { + $fake = new DataObjectFake([ + 'MyField' => 'myValue' + ]); + $mapper = new CaseInsensitiveFieldAccessor(); + $mapper->setValue($fake, 'MyField', 'myNewValue'); + $this->assertEquals('myNewValue', $fake->MyField); + } + + public function testSetValueWithDifferentCasing() + { + $fake = new DataObjectFake([ + 'MyField' => 'myValue' + ]); + $mapper = new CaseInsensitiveFieldAccessor(); + $mapper->setValue($fake, 'myfield', 'myNewValue'); + $this->assertEquals('myNewValue', $fake->MyField); + } + + public function testSetValueWithCustomGetter() + { + $fake = new DataObjectFake([]); + $mapper = new CaseInsensitiveFieldAccessor(); + $mapper->setValue($fake, 'customsetterfield', 'myNewValue'); + $this->assertEquals('myNewValue', $fake->customSetterFieldResult); + } + + public function testSetValueWithMethod() + { + $fake = new DataObjectFake([]); + $mapper = new CaseInsensitiveFieldAccessor(); + $mapper->setValue($fake, 'customsettermethod', 'myNewValue'); + $this->assertEquals('myNewValue', $fake->customSetterMethodResult); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetValueWithUnknownFieldThrowsException() + { + $fake = new DataObjectFake([]); + $mapper = new CaseInsensitiveFieldAccessor(); + $mapper->setValue($fake, 'unknownField', true); + } +}