2323 */
2424class Options implements \ArrayAccess, \Iterator, \Countable
2525{
26+ /**
27+ * Whether to format {@link \DateTime} objects as RFC-3339 dates
28+ * during exceptions ("Y-m-d H:i:s").
29+ *
30+ * @var int
31+ */
32+ const PRETTY_DATE = 1 ;
33+
34+ /**
35+ * Whether to cast objects with a "__toString()" method to strings.
36+ *
37+ * @var int
38+ */
39+ const OBJECT_TO_STRING = 2 ;
40+
2641 /**
2742 * A list of option values.
43+ *
2844 * @var array
2945 */
3046 private $ options = array ();
3147
3248 /**
3349 * A list of normalizer closures.
50+ *
3451 * @var array
3552 */
3653 private $ normalizers = array ();
3754
3855 /**
3956 * A list of closures for evaluating lazy options.
57+ *
4058 * @var array
4159 */
4260 private $ lazy = array ();
4361
4462 /**
4563 * A list containing the currently locked options.
64+ *
4665 * @var array
4766 */
4867 private $ lock = array ();
@@ -155,7 +174,7 @@ public static function validateNames(array $options, $acceptedOptions, $namesAsK
155174 ksort ($ diff );
156175
157176 throw new InvalidOptionsException (sprintf (
158- (count ($ diff ) > 1 ? 'The options "%s" do not exist. ' : 'The option "%s" does not exist. ' ). ' Known options are: "%s" ' ,
177+ (count ($ diff ) > 1 ? 'The options "%s" do not exist. ' : 'The option "%s" does not exist. ' ) . ' Known options are: "%s" ' ,
159178 implode ('", " ' , array_keys ($ diff )),
160179 implode ('", " ' , array_keys ($ acceptedOptions ))
161180 ));
@@ -228,11 +247,11 @@ public static function validateTypes(array $options, array $acceptedTypes)
228247 continue ;
229248 }
230249
231- $ value = $ options [$ option ];
250+ $ value = $ options [$ option ];
232251 $ optionTypes = (array ) $ optionTypes ;
233252
234253 foreach ($ optionTypes as $ type ) {
235- $ isFunction = 'is_ ' . $ type ;
254+ $ isFunction = 'is_ ' . $ type ;
236255
237256 if (function_exists ($ isFunction ) && $ isFunction ($ value )) {
238257 continue 2 ;
@@ -244,61 +263,12 @@ public static function validateTypes(array $options, array $acceptedTypes)
244263 throw new InvalidOptionsException (sprintf (
245264 'The option "%s" with value "%s" is expected to be of type "%s" ' ,
246265 $ option ,
247- self ::createPrintableValue ($ value ),
248- implode ('", " ' , array_map ( function ( $ optionType ) { return self ::createPrintableType ( $ optionType ); }, $ optionTypes ))
266+ self ::formatValue ($ value ),
267+ implode ('", " ' , self ::formatTypesOf ( $ optionTypes ))
249268 ));
250269 }
251270 }
252271
253- /**
254- * Attempts to convert a given value into a (short) string or integer.
255- *
256- * @param mixed $value The value to convert.
257- *
258- * @return string The printable value.
259- */
260- private static function createPrintableValue ($ value )
261- {
262- if (is_object ($ value )) {
263- return get_class ($ value );
264- }
265-
266- if (is_array ($ value )) {
267- return 'Array ' ;
268- }
269-
270- if (null === $ value ) {
271- return 'null ' ;
272- }
273-
274- if (is_string ($ value )) {
275- return '" ' .$ value .'" ' ;
276- }
277-
278- if (is_bool ($ value )) {
279- return (string ) var_export ($ value , true );
280- }
281-
282- return (string ) $ value ;
283- }
284-
285- /**
286- * Attempts to convert a given value's type into a printable string.
287- *
288- * @param mixed $value The value to convert.
289- *
290- * @return string Objects get returned as their classname,
291- * all other values are put through gettype().
292- */
293- private static function createPrintableType ($ value )
294- {
295- if (is_object ($ value )) {
296- return get_class ($ value );
297- } else {
298- return gettype ($ value );
299- }
300- }
301-
302272 /**
303273 * Validates that the given option values match the accepted values and
304274 * throws an exception otherwise.
@@ -318,7 +288,7 @@ public static function validateValues(array $options, array $acceptedValues)
318288 foreach ($ acceptedValues as $ option => $ optionValues ) {
319289 if (array_key_exists ($ option , $ options )) {
320290 if (is_array ($ optionValues ) && !in_array ($ options [$ option ], $ optionValues , true )) {
321- throw new InvalidOptionsException (sprintf ('The option "%s" has the value "%s", but is expected to be one of "%s" ' , $ option , $ options [$ option ], implode ('", " ' , $ optionValues )));
291+ throw new InvalidOptionsException (sprintf ('The option "%s" has the value "%s", but is expected to be one of "%s" ' , $ option , $ options [$ option ], implode ('", " ' , self :: formatValues ( $ optionValues) )));
322292 }
323293
324294 if (is_callable ($ optionValues ) && !call_user_func ($ optionValues , $ options [$ option ])) {
@@ -328,6 +298,118 @@ public static function validateValues(array $options, array $acceptedValues)
328298 }
329299 }
330300
301+ /**
302+ * Returns a string representation of the type of the value.
303+ *
304+ * @param mixed $value
305+ *
306+ * @return string
307+ */
308+ private static function formatTypeOf ($ value )
309+ {
310+ return is_object ($ value ) ? get_class ($ value ) : gettype ($ value );
311+ }
312+
313+ /**
314+ * @param array $optionTypes
315+ *
316+ * @return array
317+ */
318+ private static function formatTypesOf (array $ optionTypes )
319+ {
320+ return array_map (function ($ optionType ) {
321+ return self ::formatTypeOf ($ optionType );
322+ }, $ optionTypes );
323+ }
324+
325+ /**
326+ * Returns a string representation of the value.
327+ *
328+ * This method returns the equivalent PHP tokens for most scalar types
329+ * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped
330+ * in double quotes ("). Objects, arrays and resources are formatted as
331+ * "object", "array" and "resource". If the parameter $prettyDateTime
332+ * is set to true, {@link \DateTime} objects will be formatted as
333+ * RFC-3339 dates ("Y-m-d H:i:s").
334+ *
335+ * @param mixed $value The value to format as string
336+ * @param int $format A bitwise combination of the format
337+ * constants in this class
338+ *
339+ * @return string The string representation of the passed value
340+ */
341+ private static function formatValue ($ value , $ format = 0 )
342+ {
343+ $ isDateTime = $ value instanceof \DateTime || $ value instanceof \DateTimeInterface;
344+ if (($ format & self ::PRETTY_DATE ) && $ isDateTime ) {
345+ if (class_exists ('IntlDateFormatter ' )) {
346+ $ locale = \Locale::getDefault ();
347+ $ formatter = new \IntlDateFormatter ($ locale , \IntlDateFormatter::MEDIUM , \IntlDateFormatter::SHORT );
348+ // neither the native nor the stub IntlDateFormatter support
349+ // DateTimeImmutable as of yet
350+ if (!$ value instanceof \DateTime) {
351+ $ value = new \DateTime (
352+ $ value ->format ('Y-m-d H:i:s.u e ' ),
353+ $ value ->getTimezone ()
354+ );
355+ }
356+
357+ return $ formatter ->format ($ value );
358+ }
359+
360+ return $ value ->format ('Y-m-d H:i:s ' );
361+ }
362+
363+ if (is_object ($ value )) {
364+ if ($ format & self ::OBJECT_TO_STRING && method_exists ($ value , '__toString ' )) {
365+ return $ value ->__toString ();
366+ }
367+
368+ return 'object ' ;
369+ }
370+
371+ if (is_array ($ value )) {
372+ return 'array ' ;
373+ }
374+
375+ if (is_string ($ value )) {
376+ return '" ' . $ value . '" ' ;
377+ }
378+
379+ if (is_resource ($ value )) {
380+ return 'resource ' ;
381+ }
382+
383+ if (null === $ value ) {
384+ return 'null ' ;
385+ }
386+
387+ if (false === $ value ) {
388+ return 'false ' ;
389+ }
390+
391+ if (true === $ value ) {
392+ return 'true ' ;
393+ }
394+
395+ return (string ) $ value ;
396+ }
397+
398+ /**
399+ * Returns a string representation of a list of values.
400+ *
401+ * @param array $values
402+ * @param bool $prettyDateTime
403+ *
404+ * @return string
405+ */
406+ private static function formatValues (array $ values , $ prettyDateTime = false )
407+ {
408+ return array_map (function ($ value ) use ($ prettyDateTime ) {
409+ return self ::formatValue ($ value , $ prettyDateTime );
410+ }, $ values );
411+ }
412+
331413 /**
332414 * Constructs a new object with a set of default options.
333415 *
@@ -425,8 +507,8 @@ public function replace(array $options)
425507 throw new OptionDefinitionException ('Options cannot be replaced anymore once options have been read. ' );
426508 }
427509
428- $ this ->options = array ();
429- $ this ->lazy = array ();
510+ $ this ->options = array ();
511+ $ this ->lazy = array ();
430512 $ this ->normalizers = array ();
431513
432514 foreach ($ options as $ option => $ value ) {
@@ -467,7 +549,7 @@ public function overload($option, $value)
467549 $ reflClosure = is_array ($ value )
468550 ? new \ReflectionMethod ($ value [0 ], $ value [1 ])
469551 : new \ReflectionFunction ($ value );
470- $ params = $ reflClosure ->getParameters ();
552+ $ params = $ reflClosure ->getParameters ();
471553
472554 if (isset ($ params [0 ]) && null !== ($ class = $ params [0 ]->getClass ()) && __CLASS__ === $ class ->name ) {
473555 // Initialize the option if no previous value exists
@@ -530,7 +612,7 @@ public function get($option)
530612 *
531613 * @param string $option The option name.
532614 *
533- * @return bool Whether the option exists.
615+ * @return bool Whether the option exists.
534616 */
535617 public function has ($ option )
536618 {
@@ -570,8 +652,8 @@ public function clear()
570652 throw new OptionDefinitionException ('Options cannot be cleared anymore once options have been read. ' );
571653 }
572654
573- $ this ->options = array ();
574- $ this ->lazy = array ();
655+ $ this ->options = array ();
656+ $ this ->lazy = array ();
575657 $ this ->normalizers = array ();
576658 }
577659
@@ -610,7 +692,7 @@ public function all()
610692 *
611693 * @param string $option The option name.
612694 *
613- * @return bool Whether the option exists.
695+ * @return bool Whether the option exists.
614696 *
615697 * @see \ArrayAccess::offsetExists()
616698 */
@@ -789,7 +871,7 @@ private function normalizeOption($option)
789871 /** @var \Closure $normalizer */
790872 $ normalizer = $ this ->normalizers [$ option ];
791873
792- $ this ->lock [$ option ] = true ;
874+ $ this ->lock [$ option ] = true ;
793875 $ this ->options [$ option ] = $ normalizer ($ this , array_key_exists ($ option , $ this ->options ) ? $ this ->options [$ option ] : null );
794876 unset($ this ->lock [$ option ]);
795877
0 commit comments