diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 3b7e473..55d5153 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -415,11 +415,16 @@ class Executor if ($mapFn) { try { $mapped = call_user_func($mapFn, $sourceValueList, $args, $info); + $validType = is_array($mapped) || ($mapped instanceof \Traversable && $mapped instanceof \Countable); + $mappedCount = count($mapped); + $sourceCount = count($sourceValueList); Utils::invariant( - is_array($mapped) && count($mapped) === count($sourceValueList), - "Function `map` of $parentType.$fieldName is expected to return array " . - "with exact same number of items as list being mapped (first argument of `map`)" + $validType && count($mapped) === count($sourceValueList), + "Function `map` of $parentType.$fieldName is expected to return array or " . + "countable traversable with exact same number of items as list being mapped. ". + "Got '%s' with count '$mappedCount' against '$sourceCount' expected.", + Utils::getVariableType($mapped) ); } catch (\Exception $error) { @@ -682,7 +687,8 @@ class Executor // Collect sub-fields to execute to complete this value. $subFieldASTs = self::collectSubFields($exeContext, $objectType, $fieldASTs); - return self::executeFields($exeContext, $objectType, [$result], $subFieldASTs)[0]; + $executed = self::executeFields($exeContext, $objectType, [$result], $subFieldASTs); + return isset($executed[0]) ? $executed[0] : null; } /** diff --git a/src/Utils.php b/src/Utils.php index b484442..4dee896 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -86,6 +86,24 @@ class Utils return $map; } + /** + * @param $traversable + * @param callable $fn + * @return array + * @throws \Exception + */ + public static function mapKeyValue($traversable, callable $fn) + { + self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); + + $map = []; + foreach ($traversable as $key => $value) { + list($newKey, $newValue) = $fn($value, $key); + $map[$newKey] = $newValue; + } + return $map; + } + /** * @param $traversable * @param callable $keyFn function($value, $key) => $newKey @@ -106,6 +124,19 @@ class Utils return $map; } + /** + * @param $traversable + * @param callable $fn + */ + public static function each($traversable, callable $fn) + { + self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); + + foreach ($traversable as $key => $item) { + $fn($item, $key); + } + } + /** * Splits original traversable to several arrays with keys equal to $keyFn return *