Page MenuHomePhabricator

D21293.diff
No OneTemporary

D21293.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -612,6 +612,7 @@
'PhutilArgumentUsageException' => 'parser/argument/exception/PhutilArgumentUsageException.php',
'PhutilArgumentWorkflow' => 'parser/argument/workflow/PhutilArgumentWorkflow.php',
'PhutilArray' => 'utils/PhutilArray.php',
+ 'PhutilArrayCheck' => 'utils/PhutilArrayCheck.php',
'PhutilArrayTestCase' => 'utils/__tests__/PhutilArrayTestCase.php',
'PhutilArrayWithDefaultValue' => 'utils/PhutilArrayWithDefaultValue.php',
'PhutilAsanaFuture' => 'future/asana/PhutilAsanaFuture.php',
@@ -1619,6 +1620,7 @@
'ArrayAccess',
'Iterator',
),
+ 'PhutilArrayCheck' => 'Phobject',
'PhutilArrayTestCase' => 'PhutilTestCase',
'PhutilArrayWithDefaultValue' => 'PhutilArray',
'PhutilAsanaFuture' => 'FutureProxy',
diff --git a/src/utils/PhutilArrayCheck.php b/src/utils/PhutilArrayCheck.php
new file mode 100644
--- /dev/null
+++ b/src/utils/PhutilArrayCheck.php
@@ -0,0 +1,251 @@
+<?php
+
+final class PhutilArrayCheck
+ extends Phobject {
+
+ private $instancesOf;
+ private $uniqueMethod;
+ private $context;
+
+ private $object;
+ private $method;
+
+ public function setInstancesOf($instances_of) {
+ $this->instancesOf = $instances_of;
+ return $this;
+ }
+
+ public function getInstancesOf() {
+ return $this->instancesOf;
+ }
+
+ public function setUniqueMethod($unique_method) {
+ $this->uniqueMethod = $unique_method;
+ return $this;
+ }
+
+ public function getUniqueMethod() {
+ return $this->uniqueMethod;
+ }
+
+ public function setContext($object, $method) {
+ if (!is_object($object) && !is_string($object)) {
+ throw new Exception(
+ pht(
+ 'Expected an object or string for "object" context, got "%s".',
+ phutil_describe_type($object)));
+ }
+
+ if (!is_string($method)) {
+ throw new Exception(
+ pht(
+ 'Expected a string for "method" context, got "%s".',
+ phutil_describe_type($method)));
+ }
+
+ $argv = func_get_args();
+ $argv = array_slice($argv, 2);
+
+ $this->context = array(
+ 'object' => $object,
+ 'method' => $method,
+ 'argv' => $argv,
+ );
+
+ return $this;
+ }
+
+ public function checkValues($maps) {
+ foreach ($maps as $idx => $map) {
+ $maps[$idx] = $this->checkValue($map);
+ }
+
+ $unique = $this->getUniqueMethod();
+ if ($unique === null) {
+ $result = array();
+
+ foreach ($maps as $map) {
+ foreach ($map as $value) {
+ $result[] = $value;
+ }
+ }
+ } else {
+ $items = array();
+ foreach ($maps as $idx => $map) {
+ foreach ($map as $key => $value) {
+ $items[$key][$idx] = $value;
+ }
+ }
+
+ foreach ($items as $key => $values) {
+ if (count($values) === 1) {
+ continue;
+ }
+ $this->raiseValueException(
+ pht(
+ 'Unexpected return value from calls to "%s(...)". More than one '.
+ 'object returned a value with unique key "%s". This key was '.
+ 'returned by objects with indexes: %s.',
+ $unique,
+ $key,
+ implode(', ', array_keys($values))));
+ }
+
+ $result = array();
+ foreach ($items as $key => $values) {
+ $result[$key] = head($values);
+ }
+ }
+
+ return $result;
+ }
+
+ public function checkValue($items) {
+ if (!$this->context) {
+ throw new PhutilInvalidStateException('setContext');
+ }
+
+ if (!is_array($items)) {
+ $this->raiseValueException(
+ pht(
+ 'Expected value to be a list, got "%s".',
+ phutil_describe_type($items)));
+ }
+
+ $instances_of = $this->getInstancesOf();
+ if ($instances_of !== null) {
+ foreach ($items as $idx => $item) {
+ if (!($item instanceof $instances_of)) {
+ $this->raiseValueException(
+ pht(
+ 'Expected value to be a list of objects which are instances of '.
+ '"%s", but item with index "%s" is "%s".',
+ $instances_of,
+ $idx,
+ phutil_describe_type($item)));
+ }
+ }
+ }
+
+ $unique = $this->getUniqueMethod();
+ if ($unique !== null) {
+ if ($instances_of === null) {
+ foreach ($items as $idx => $item) {
+ if (!is_object($item)) {
+ $this->raiseValueException(
+ pht(
+ 'Expected value to be a list of objects to support calling '.
+ '"%s" to generate unique keys, but item with index "%s" is '.
+ '"%s".',
+ $unique,
+ $idx,
+ phutil_describe_type($item)));
+ }
+ }
+ }
+
+ $map = array();
+
+ foreach ($items as $idx => $item) {
+ $key = call_user_func(array($item, $unique));
+
+ if (!is_string($key) && !is_int($key)) {
+ $this->raiseValueException(
+ pht(
+ 'Expected method "%s->%s()" to return a string or integer for '.
+ 'use as a unique key, got "%s" from object at index "%s".',
+ get_class($item),
+ $unique,
+ phutil_describe_type($key),
+ $idx));
+ }
+
+ $key = phutil_string_cast($key);
+
+ $map[$key][$idx] = $item;
+ }
+
+ $result = array();
+ foreach ($map as $key => $values) {
+ if (count($values) === 1) {
+ $result[$key] = head($values);
+ continue;
+ }
+
+ $classes = array();
+ foreach ($values as $value) {
+ $classes[] = get_class($value);
+ }
+ $classes = array_fuse($classes);
+
+ if (count($classes) === 1) {
+ $class_display = head($classes);
+ } else {
+ $class_display = sprintf(
+ '[%s]',
+ implode(', ', $classes));
+ }
+
+ $index_display = array();
+ foreach ($values as $idx => $value) {
+ $index_display[] = pht(
+ '"%s" (%s)',
+ $idx,
+ get_class($value));
+ }
+ $index_display = implode(', ', $index_display);
+
+ $this->raiseValueException(
+ pht(
+ 'Expected method "%s->%s()" to return a unique key, got "%s" '.
+ 'from %s object(s) at indexes: %s.',
+ $class_display,
+ $unique,
+ $key,
+ phutil_count($values),
+ $index_display));
+ }
+
+ $items = $result;
+ }
+
+ return $items;
+ }
+
+ private function raiseValueException($message) {
+ $context = $this->context;
+
+ $object = $context['object'];
+ $method = $context['method'];
+ $argv = $context['argv'];
+
+ if (is_object($object)) {
+ $object_display = sprintf(
+ '%s->%s',
+ get_class($object),
+ $method);
+ } else {
+ $object_display = sprintf(
+ '%s::%s',
+ $object,
+ $method);
+ }
+
+ if (count($argv)) {
+ $call_display = sprintf(
+ '%s(...)',
+ $object_display);
+ } else {
+ $call_display = sprintf(
+ '%s()',
+ $object_display);
+ }
+
+ throw new Exception(
+ pht(
+ 'Unexpected return value from call to "%s": %s.',
+ $call_display,
+ $message));
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Fri, May 10, 11:29 PM (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6284346
Default Alt Text
D21293.diff (7 KB)

Event Timeline