Blame view

app/Support/AutoMapper.php 5.73 KB
e77200db5   nologostudio.ru   Initial commit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  <?php
  
  namespace FootyRoom\Support;
  
  use ArrayObject;
  use ReflectionClass;
  use ReflectionObject;
  use ReflectionProperty;
  use MongoDB\Model\BSONArray;
  
  class AutoMapper
  {
      /**
       * Maps properties from given object to a new instance of specified type.
       *
       * Only public properties of source will be mapped to target. Target
       * properties can still be private. If target contains properties with
       * interal PHP classes then those will not be mapped. Primitive types are
       * mapped without casting.
       *
       * @param mixed $source
       * @param string $name Class name
       * @param array $mappings Mappings in ['to' => 'from'] configuration.
       * @param bool $merge If set to true, AutoMapper will use user defined
       *                    mappings as an addition to automatic same name
       *                    mappings. If set to false then only user defined
       *                    mappings will be performed.
       *
       * @return mixed
       */
      public static function map($source, $name, $mappings = [], $merge = true)
      {
          $targetReflection = new ReflectionClass($name);
  
          // We don't map PHP internal classes.
          if ($targetReflection->isInternal()) {
              return;
          }
  
          $target = $targetReflection->newInstanceWithoutConstructor();
  
          if (empty($mappings) || $merge) {
              $properties = $targetReflection->getProperties();
  
              foreach ($properties as $prop) {
                  if (!isset($mappings[$prop->getName()])) {
                      $mappings[$prop->getName()] = $prop->getName();
                  }
              }
          }
  
          foreach ($mappings as $to => $from) {
              self::mapProperty($source, $target, $targetReflection, $from, $to);
          }
  
          return $target;
      }
  
      /**
       * Sets a non-public property to a specified value.
       *
       * @param mixed $target
       * @param string $key
       * @param mixed $value
       */
      public static function setValue($target, $key, $value)
      {
          $targetReflection = new ReflectionObject($target);
  
          $targetProperty = $targetReflection->getProperty($key);
          $targetProperty->setAccessible(true);
          $targetProperty->setValue($target, $value);
      }
  
      /**
       * Maps single property from source to target.
       *
       * @param object $source
       * @param mixed $target
       * @param \ReflectionClass $targetReflection
       * @param string $from Property name on source
       * @param string $to Property name on target
       */
      protected static function mapProperty($source, $target, $targetReflection, $from, $to)
      {
          if ($source === null || !property_exists($source, $from)) {
              return;
          }
  
          $targetProperty = $targetReflection->getProperty($to);
          $targetProperty->setAccessible(true);
  
          $sourceProperty = $source->{$from};
  
          $sourceClass = null;
          $targetClass = null;
          $targetArrayClass = null;
          $isPrimitiveType = true;
  
          // We will only parse type of target if source is an object or array
          // because if it's not then we wont cast premitive type into an object
          // anyway.
          if (is_object($sourceProperty) && !($sourceProperty instanceof BSONArray)) {
              $targetClass = self::parseType($targetProperty);
  
              if ($targetClass) {
                  $isPrimitiveType = self::isPrimitiveType($targetClass);
                  $sourceClass = get_class($sourceProperty);
              }
          } elseif (is_array($sourceProperty) || $sourceProperty instanceof ArrayObject) {
              $targetArrayClass = self::parseArrayType($targetProperty);
              $isPrimitiveType = self::isPrimitiveType($targetArrayClass);
          }
  
          // If a target is an object.
          if ($targetClass && $targetClass !== $sourceClass && !$isPrimitiveType) {
              $targetProperty->setValue(
                  $target,
                  self::map($sourceProperty, $targetClass)
              );
          }
          // If target is an array of objects.
          elseif ($targetArrayClass && !$isPrimitiveType) {
              $items = [];
  
              foreach ($sourceProperty as $item) {
                  $items[] = self::map($item, $targetArrayClass);
              }
  
              $targetProperty->setValue($target, $items);
          } else {
              $targetProperty->setValue($target, $sourceProperty);
          }
      }
  
      /**
       * Returns type of property.
       *
       * @param \ReflectionProperty $property
       *
       * @return string|null
       */
      protected static function parseType(ReflectionProperty $property)
      {
          $result = preg_match('/@var\\s*([^\\s\\|]*)/i', $property->getDocComment(), $matches);
  
          return isset($matches[1]) ? trim($matches[1], '\\') : null;
      }
  
      /**
       * Returns type of typed array property.
       *
       * @param \ReflectionProperty $property
       *
       * @return string|null
       */
      protected static function parseArrayType(ReflectionProperty $property)
      {
          $result = preg_match('/@var\\s*([^\\s\\[]*)\\[\\]/i', $property->getDocComment(), $matches);
  
          return isset($matches[1]) ? trim($matches[1], '\\') : null;
      }
  
      /**
       * Determines if type is one of PHP's primitive types.
       *
       * @param string $type
       *
       * @return bool
       */
      protected static function isPrimitiveType($type)
      {
          switch (strtolower($type)) {
              case 'int':
              case 'integer':
              case 'bool':
              case 'boolean':
              case 'float':
              case 'double':
              case 'real':
              case 'string':
              case 'array':
              case 'object':
              case 'callable':
              case 'mixed':
                  return true;
              default:
                  return false;
          }
      }
  }