MDL-67850 lib: add new plist library
[moodle.git] / lib / plist / classes / CFPropertyList / CFTypeDetector.php
1 <?php
3  /**
4   * CFTypeDetector
5   * Interface for converting native PHP data structures to CFPropertyList objects.
6   * @author Rodney Rehm <rodney.rehm@medialize.de>
7   * @author Christian Kruse <cjk@wwwtech.de>
8   * @package plist
9   * @subpackage plist.types
10   * @example example-create-02.php Using {@link CFTypeDetector}
11   * @example example-create-03.php Using {@link CFTypeDetector} with {@link CFDate} and {@link CFData}
12   * @example example-create-04.php Using and extended {@link CFTypeDetector}
13   */
15 namespace CFPropertyList;
16 use \DateTime, \Iterator;
18 class CFTypeDetector {
20   /**
21    * flag stating if all arrays should automatically be converted to {@link CFDictionary}
22    * @var boolean
23    */
24   protected $autoDictionary = false;
26   /**
27    * flag stating if exceptions should be suppressed or thrown
28    * @var boolean
29    */
30   protected $suppressExceptions = false;
32   /**
33    * name of a method that will be used for array to object conversations
34    * @var callable
35    */
36   protected $objectToArrayMethod = null;
38   /**
39    * flag stating if "123.23" should be converted to float (true) or preserved as string (false)
40    * @var boolean
41    */
42   protected $castNumericStrings = true;
45   /**
46    * Create new CFTypeDetector
47    * @param array $options Configuration for casting values [autoDictionary, suppressExceptions, objectToArrayMethod, castNumericStrings]
48    */
49   public function __construct(array $options=array()) {
50     //$autoDicitionary=false,$suppressExceptions=false,$objectToArrayMethod=null
51     foreach ($options as $key => $value) {
52       if (property_exists($this, $key)) {
53         $this->$key = $value;
54       }
55     }
56   }
58   /**
59    * Determine if an array is associative or numerical.
60    * Numerical Arrays have incrementing index-numbers that don't contain gaps.
61    * @param array $value Array to check indexes of
62    * @return boolean true if array is associative, false if array has numeric indexes
63    */
64   protected function isAssociativeArray($value) {
65     $numericKeys = true;
66     $i = 0;
67     foreach($value as $key => $v) {
68       if($i !== $key) {
69         $numericKeys = false;
70         break;
71       }
72       $i++;
73     }
74     return !$numericKeys;
75   }
77   /**
78    * Get the default value
79    * @return CFType the default value to return if no suitable type could be determined
80    */
81   protected function defaultValue() {
82     return new CFString();
83   }
85   /**
86    * Create CFType-structure by guessing the data-types.
87    * {@link CFArray}, {@link CFDictionary}, {@link CFBoolean}, {@link CFNumber} and {@link CFString} can be created, {@link CFDate} and {@link CFData} cannot.
88    * <br /><b>Note:</b>Distinguishing between {@link CFArray} and {@link CFDictionary} is done by examining the keys.
89    * Keys must be strictly incrementing integers to evaluate to a {@link CFArray}.
90    * Since PHP does not offer a function to test for associative arrays,
91    * this test causes the input array to be walked twice and thus work rather slow on large collections.
92    * If you work with large arrays and can live with all arrays evaluating to {@link CFDictionary},
93    * feel free to set the appropriate flag.
94    * <br /><b>Note:</b> If $value is an instance of CFType it is simply returned.
95    * <br /><b>Note:</b> If $value is neither a CFType, array, numeric, boolean nor string, it is omitted.
96    * @param mixed $value Value to convert to CFType
97    * @param boolean $autoDictionary if true {@link CFArray}-detection is bypassed and arrays will be returned as {@link CFDictionary}.
98    * @return CFType CFType based on guessed type
99    * @uses isAssociativeArray() to check if an array only has numeric indexes
100    */
101   public function toCFType($value) {
102     switch(true) {
103       case $value instanceof CFType:
104         return $value;
105       break;
107       case is_object($value):
108         // DateTime should be CFDate
109         if(class_exists( 'DateTime' ) && $value instanceof DateTime){
110           return new CFDate($value->getTimestamp());
111         }
113         // convert possible objects to arrays, arrays will be arrays
114         if($this->objectToArrayMethod && is_callable(array($value, $this->objectToArrayMethod))){
115           $value = call_user_func( array( $value, $this->objectToArrayMethod ) );
116         }
118         if(!is_array($value)){
119           if($this->suppressExceptions)
120             return $this->defaultValue();
122           throw new PListException('Could not determine CFType for object of type '. get_class($value));
123         }
124       /* break; omitted */
126       case $value instanceof Iterator:
127       case is_array($value):
128         // test if $value is simple or associative array
129         if(!$this->autoDictionary) {
130           if(!$this->isAssociativeArray($value)) {
131             $t = new CFArray();
132             foreach($value as $v) $t->add($this->toCFType($v));
133             return $t;
134           }
135         }
137         $t = new CFDictionary();
138         foreach($value as $k => $v) $t->add($k, $this->toCFType($v));
140         return $t;
141       break;
143       case is_bool($value):
144         return new CFBoolean($value);
145       break;
147       case is_null($value):
148         return new CFString();
149       break;
151       case is_resource($value):
152         if ($this->suppressExceptions) {
153           return $this->defaultValue();
154         }
156         throw new PListException('Could not determine CFType for resource of type '. get_resource_type($value));
157       break;
159       case is_numeric($value):
160         if (!$this->castNumericStrings && is_string($value)) {
161           return new CFString($value);
162         }
164         return new CFNumber($value);
165       break;
167       case is_string($value):
168         if(strpos($value, "\x00") !== false) {
169           return new CFData($value);
170         }
171         return new CFString($value);
173       break;
175       default:
176         if ($this->suppressExceptions) {
177           return $this->defaultValue();
178         }
180         throw new PListException('Could not determine CFType for '. gettype($value));
181       break;
182     }
183   }