Completed
Branch 5.3 (f8a22c)
by Rémi
08:52
created
src/System/Proxies/CollectionProxy.php 1 patch
Indentation   +210 added lines, -210 removed lines patch added patch discarded remove patch
@@ -17,218 +17,218 @@
 block discarded – undo
17 17
  */
18 18
 class CollectionProxy extends Proxy implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
19 19
 {
20
-    /**
21
-     * Underlying Lazyloaded collection
22
-     * @var EntityCollection
23
-     */
24
-    protected $loadedCollection;
25
-
26
-    /**
27
-     * Added Items Collection
28
-     * @var EntityCollection
29
-     */
30
-    protected $addedItems;
31
-
32
-    /**
33
-     * @param mixed  $parentEntity
34
-     * @param string $relation relationship method handled by the proxy.
35
-     */
36
-    public function __construct($parentEntity, $relation)
37
-    {
38
-        $this->addedItems = new EntityCollection;
39
-
40
-        parent::__construct($parentEntity, $relation);
41
-    }
42
-
43
-    /**
44
-     * Add an entity to the proxy collection, weither it's loaded or not
45
-     *
46
-     * @param mixed $entity
47
-     * @return self|void
48
-     */
49
-    public function add($entity)
50
-    {
51
-        if ($this->isLoaded()) {
52
-            return $this->loadedCollection->add($entity);
53
-        } else {
54
-            $this->addedItems->add($entity);
55
-        }
56
-    }
57
-
58
-    /**
59
-     * Check if Proxy collection has been lazy-loaded
60
-     *
61
-     * @return boolean
62
-     */
63
-    public function isLoaded()
64
-    {
65
-        return !is_null($this->loadedCollection);
66
-    }
67
-
68
-    /**
69
-     * Return the underlying collection
70
-     *
71
-     * @return EntityCollection
72
-     */
73
-    public function getUnderlyingCollection()
74
-    {
75
-        return $this->loadedCollection;
76
-    }
77
-
78
-    /**
79
-     * Return Items that has been added prior to lazy-loading
80
-     *
81
-     * @return EntityCollection
82
-     */
83
-    public function getAddedItems()
84
-    {
85
-        return $this->addedItems;
86
-    }
87
-
88
-    /**
89
-     * Load the underlying relation
90
-     *
91
-     * @return void
92
-     */
93
-    protected function loadOnce()
94
-    {
95
-        if ($this->isLoaded()) {
96
-            return;
97
-        }
20
+	/**
21
+	 * Underlying Lazyloaded collection
22
+	 * @var EntityCollection
23
+	 */
24
+	protected $loadedCollection;
25
+
26
+	/**
27
+	 * Added Items Collection
28
+	 * @var EntityCollection
29
+	 */
30
+	protected $addedItems;
31
+
32
+	/**
33
+	 * @param mixed  $parentEntity
34
+	 * @param string $relation relationship method handled by the proxy.
35
+	 */
36
+	public function __construct($parentEntity, $relation)
37
+	{
38
+		$this->addedItems = new EntityCollection;
39
+
40
+		parent::__construct($parentEntity, $relation);
41
+	}
42
+
43
+	/**
44
+	 * Add an entity to the proxy collection, weither it's loaded or not
45
+	 *
46
+	 * @param mixed $entity
47
+	 * @return self|void
48
+	 */
49
+	public function add($entity)
50
+	{
51
+		if ($this->isLoaded()) {
52
+			return $this->loadedCollection->add($entity);
53
+		} else {
54
+			$this->addedItems->add($entity);
55
+		}
56
+	}
57
+
58
+	/**
59
+	 * Check if Proxy collection has been lazy-loaded
60
+	 *
61
+	 * @return boolean
62
+	 */
63
+	public function isLoaded()
64
+	{
65
+		return !is_null($this->loadedCollection);
66
+	}
67
+
68
+	/**
69
+	 * Return the underlying collection
70
+	 *
71
+	 * @return EntityCollection
72
+	 */
73
+	public function getUnderlyingCollection()
74
+	{
75
+		return $this->loadedCollection;
76
+	}
77
+
78
+	/**
79
+	 * Return Items that has been added prior to lazy-loading
80
+	 *
81
+	 * @return EntityCollection
82
+	 */
83
+	public function getAddedItems()
84
+	{
85
+		return $this->addedItems;
86
+	}
87
+
88
+	/**
89
+	 * Load the underlying relation
90
+	 *
91
+	 * @return void
92
+	 */
93
+	protected function loadOnce()
94
+	{
95
+		if ($this->isLoaded()) {
96
+			return;
97
+		}
98 98
         
99
-        $this->loadedCollection = $this->load();
99
+		$this->loadedCollection = $this->load();
100 100
 
101
-        foreach ($this->addedItems as $entity) {
102
-            $this->loadedCollection->add($entity);
103
-        }
101
+		foreach ($this->addedItems as $entity) {
102
+			$this->loadedCollection->add($entity);
103
+		}
104 104
 
105
-        $this->addedItems = null;
106
-    }
105
+		$this->addedItems = null;
106
+	}
107 107
     
108
-    /**
109
-     * Count the number of items in the collection.
110
-     *
111
-     * @return int
112
-     */
113
-    public function count()
114
-    {
115
-        $this->loadOnce();
116
-
117
-        return $this->getUnderlyingCollection()->count();
118
-    }
119
-
120
-    /**
121
-     * Determine if an item exists at an offset.
122
-     *
123
-     * @param  mixed $key
124
-     * @return bool
125
-     */
126
-    public function offsetExists($key)
127
-    {
128
-        $this->loadOnce();
129
-
130
-        return $this->getUnderlyingCollection()->offsetExists($key);
131
-    }
132
-
133
-    /**
134
-     * Get an item at a given offset.
135
-     *
136
-     * @param  mixed $key
137
-     * @return mixed
138
-     */
139
-    public function offsetGet($key)
140
-    {
141
-        $this->loadOnce();
142
-
143
-        return $this->getUnderlyingCollection()->offsetGet($key);
144
-    }
145
-
146
-    /**
147
-     * Set the item at a given offset.
148
-     *
149
-     * @param mixed $key
150
-     * @param mixed $value
151
-     */
152
-    public function offsetSet($key, $value)
153
-    {
154
-        $this->loadOnce();
155
-
156
-        $this->getUnderlyingCollection()->offsetSet($key, $value);
157
-    }
158
-
159
-    /**
160
-     * Unset the item at a given offset.
161
-     *
162
-     * @param string $key
163
-     */
164
-    public function offsetUnset($key)
165
-    {
166
-        $this->loadOnce();
167
-
168
-        $this->getUnderlyingCollection()->offsetUnset($key);
169
-    }
170
-
171
-    /**
172
-     * Get the collection of items as a plain array.
173
-     *
174
-     * @return array
175
-     */
176
-    public function toArray()
177
-    {
178
-        $this->loadOnce();
179
-
180
-        return $this->getUnderlyingCollection()->toArray();
181
-    }
182
-
183
-    /**
184
-     * Convert the object into something JSON serializable.
185
-     *
186
-     * @return array
187
-     */
188
-    public function jsonSerialize()
189
-    {
190
-        $this->loadOnce();
191
-
192
-        return $this->getUnderlyingCollection()->jsonSerialize();
193
-    }
194
-
195
-    /**
196
-     * Get the collection of items as JSON.
197
-     *
198
-     * @param  int $options
199
-     * @return string
200
-     */
201
-    public function toJson($options = 0)
202
-    {
203
-        $this->loadOnce();
204
-
205
-        return $this->getUnderlyingCollection()->toJson();
206
-    }
207
-
208
-    /**
209
-     * Get an iterator for the items.
210
-     *
211
-     * @return \ArrayIterator
212
-     */
213
-    public function getIterator()
214
-    {
215
-        $this->loadOnce();
216
-
217
-        return $this->getUnderlyingCollection()->getIterator();
218
-    }
219
-
220
-
221
-    /**
222
-     * @param  $method
223
-     * @param  $parameters
224
-     * @return mixed
225
-     */
226
-    public function __call($method, $parameters)
227
-    {
228
-        if (!$this->isLoaded()) {
229
-            $this->loadOnce();
230
-        }
231
-
232
-        return call_user_func_array([$this->loadedCollection, $method], $parameters);
233
-    }
108
+	/**
109
+	 * Count the number of items in the collection.
110
+	 *
111
+	 * @return int
112
+	 */
113
+	public function count()
114
+	{
115
+		$this->loadOnce();
116
+
117
+		return $this->getUnderlyingCollection()->count();
118
+	}
119
+
120
+	/**
121
+	 * Determine if an item exists at an offset.
122
+	 *
123
+	 * @param  mixed $key
124
+	 * @return bool
125
+	 */
126
+	public function offsetExists($key)
127
+	{
128
+		$this->loadOnce();
129
+
130
+		return $this->getUnderlyingCollection()->offsetExists($key);
131
+	}
132
+
133
+	/**
134
+	 * Get an item at a given offset.
135
+	 *
136
+	 * @param  mixed $key
137
+	 * @return mixed
138
+	 */
139
+	public function offsetGet($key)
140
+	{
141
+		$this->loadOnce();
142
+
143
+		return $this->getUnderlyingCollection()->offsetGet($key);
144
+	}
145
+
146
+	/**
147
+	 * Set the item at a given offset.
148
+	 *
149
+	 * @param mixed $key
150
+	 * @param mixed $value
151
+	 */
152
+	public function offsetSet($key, $value)
153
+	{
154
+		$this->loadOnce();
155
+
156
+		$this->getUnderlyingCollection()->offsetSet($key, $value);
157
+	}
158
+
159
+	/**
160
+	 * Unset the item at a given offset.
161
+	 *
162
+	 * @param string $key
163
+	 */
164
+	public function offsetUnset($key)
165
+	{
166
+		$this->loadOnce();
167
+
168
+		$this->getUnderlyingCollection()->offsetUnset($key);
169
+	}
170
+
171
+	/**
172
+	 * Get the collection of items as a plain array.
173
+	 *
174
+	 * @return array
175
+	 */
176
+	public function toArray()
177
+	{
178
+		$this->loadOnce();
179
+
180
+		return $this->getUnderlyingCollection()->toArray();
181
+	}
182
+
183
+	/**
184
+	 * Convert the object into something JSON serializable.
185
+	 *
186
+	 * @return array
187
+	 */
188
+	public function jsonSerialize()
189
+	{
190
+		$this->loadOnce();
191
+
192
+		return $this->getUnderlyingCollection()->jsonSerialize();
193
+	}
194
+
195
+	/**
196
+	 * Get the collection of items as JSON.
197
+	 *
198
+	 * @param  int $options
199
+	 * @return string
200
+	 */
201
+	public function toJson($options = 0)
202
+	{
203
+		$this->loadOnce();
204
+
205
+		return $this->getUnderlyingCollection()->toJson();
206
+	}
207
+
208
+	/**
209
+	 * Get an iterator for the items.
210
+	 *
211
+	 * @return \ArrayIterator
212
+	 */
213
+	public function getIterator()
214
+	{
215
+		$this->loadOnce();
216
+
217
+		return $this->getUnderlyingCollection()->getIterator();
218
+	}
219
+
220
+
221
+	/**
222
+	 * @param  $method
223
+	 * @param  $parameters
224
+	 * @return mixed
225
+	 */
226
+	public function __call($method, $parameters)
227
+	{
228
+		if (!$this->isLoaded()) {
229
+			$this->loadOnce();
230
+		}
231
+
232
+		return call_user_func_array([$this->loadedCollection, $method], $parameters);
233
+	}
234 234
 }
Please login to merge, or discard this patch.
src/System/Proxies/ProxyInterface.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -5,17 +5,17 @@
 block discarded – undo
5 5
 
6 6
 interface ProxyInterface
7 7
 {
8
-    /**
9
-     * Convert a proxy into the underlying related Object
10
-     *
11
-     * @return Mappable|\Analogue\ORM\EntityCollection
12
-     */
13
-    public function load();
8
+	/**
9
+	 * Convert a proxy into the underlying related Object
10
+	 *
11
+	 * @return Mappable|\Analogue\ORM\EntityCollection
12
+	 */
13
+	public function load();
14 14
 
15
-    /**
16
-     * Return true if the underlying relation has been lazy loaded
17
-     *
18
-     * @return boolean
19
-     */
20
-    public function isLoaded();
15
+	/**
16
+	 * Return true if the underlying relation has been lazy loaded
17
+	 *
18
+	 * @return boolean
19
+	 */
20
+	public function isLoaded();
21 21
 }
Please login to merge, or discard this patch.
src/System/Proxies/Proxy.php 1 patch
Indentation   +72 added lines, -72 removed lines patch added patch discarded remove patch
@@ -7,87 +7,87 @@
 block discarded – undo
7 7
 
8 8
 abstract class Proxy implements ProxyInterface
9 9
 {
10
-    /**
11
-     * The name of the relationship method handled by the proxy.
12
-     *
13
-     * @var string
14
-     */
15
-    protected $relation;
10
+	/**
11
+	 * The name of the relationship method handled by the proxy.
12
+	 *
13
+	 * @var string
14
+	 */
15
+	protected $relation;
16 16
 
17
-    /**
18
-     * Reference to parent entity object
19
-     *
20
-     * @var \Analogue\ORM\System\InternallyMappable
21
-     */
22
-    protected $parentEntity;
17
+	/**
18
+	 * Reference to parent entity object
19
+	 *
20
+	 * @var \Analogue\ORM\System\InternallyMappable
21
+	 */
22
+	protected $parentEntity;
23 23
 
24
-    /**
25
-     * Lazy loaded relation flag
26
-     *
27
-     * @var boolean
28
-     */
29
-    protected $loaded = false;
24
+	/**
25
+	 * Lazy loaded relation flag
26
+	 *
27
+	 * @var boolean
28
+	 */
29
+	protected $loaded = false;
30 30
 
31
-    /**
32
-     * @param mixed  $parentEntity
33
-     * @param string $relation     relationship method handled by the proxy.
34
-     */
35
-    public function __construct($parentEntity, $relation)
36
-    {
37
-        $this->parentEntity = $parentEntity;
31
+	/**
32
+	 * @param mixed  $parentEntity
33
+	 * @param string $relation     relationship method handled by the proxy.
34
+	 */
35
+	public function __construct($parentEntity, $relation)
36
+	{
37
+		$this->parentEntity = $parentEntity;
38 38
 
39
-        $this->relation = $relation;
40
-    }
39
+		$this->relation = $relation;
40
+	}
41 41
 
42
-    /**
43
-     * Call the relationship method on the underlying entity map
44
-     *
45
-     * @throws MappingException
46
-     * @return mixed
47
-     */
48
-    public function load()
49
-    {
50
-        $entities = $this->query($this->parentEntity, $this->relation)->getResults($this->relation);
42
+	/**
43
+	 * Call the relationship method on the underlying entity map
44
+	 *
45
+	 * @throws MappingException
46
+	 * @return mixed
47
+	 */
48
+	public function load()
49
+	{
50
+		$entities = $this->query($this->parentEntity, $this->relation)->getResults($this->relation);
51 51
 
52
-        $this->loaded = true;
52
+		$this->loaded = true;
53 53
 
54
-        return $entities;
55
-    }
54
+		return $entities;
55
+	}
56 56
 
57
-    /**
58
-     * Return true if the underlying relation has been lazy loaded
59
-     *
60
-     * @return boolean
61
-     */
62
-    public function isLoaded()
63
-    {
64
-        return $this->loaded;
65
-    }
57
+	/**
58
+	 * Return true if the underlying relation has been lazy loaded
59
+	 *
60
+	 * @return boolean
61
+	 */
62
+	public function isLoaded()
63
+	{
64
+		return $this->loaded;
65
+	}
66 66
 
67
-    /**
68
-     * Return the Query Builder on the relation
69
-     *
70
-     * @param  \Analogue\ORM\System\InternallyMappable  $entity
71
-     * @param  string $relation
72
-     * @throws MappingException
73
-     * @return \Analogue\ORM\System\Query
74
-     */
75
-    protected function query($entity, $relation)
76
-    {
77
-        $entityMap = $this->getMapper($entity)->getEntityMap();
67
+	/**
68
+	 * Return the Query Builder on the relation
69
+	 *
70
+	 * @param  \Analogue\ORM\System\InternallyMappable  $entity
71
+	 * @param  string $relation
72
+	 * @throws MappingException
73
+	 * @return \Analogue\ORM\System\Query
74
+	 */
75
+	protected function query($entity, $relation)
76
+	{
77
+		$entityMap = $this->getMapper($entity)->getEntityMap();
78 78
 
79
-        return $entityMap->$relation($entity);
80
-    }
79
+		return $entityMap->$relation($entity);
80
+	}
81 81
 
82
-    /**
83
-     * Get the mapper instance for the entity
84
-     *
85
-     * @param  \Analogue\ORM\System\InternallyMappable $entity
86
-     * @throws MappingException
87
-     * @return \Analogue\ORM\System\Mapper
88
-     */
89
-    protected function getMapper($entity)
90
-    {
91
-        return Manager::getMapper($entity);
92
-    }
82
+	/**
83
+	 * Get the mapper instance for the entity
84
+	 *
85
+	 * @param  \Analogue\ORM\System\InternallyMappable $entity
86
+	 * @throws MappingException
87
+	 * @return \Analogue\ORM\System\Mapper
88
+	 */
89
+	protected function getMapper($entity)
90
+	{
91
+		return Manager::getMapper($entity);
92
+	}
93 93
 }
Please login to merge, or discard this patch.
src/System/EntityBuilder.php 3 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -152,13 +152,13 @@
 block discarded – undo
152 152
         $valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
153 153
 
154 154
         foreach ($embeddedAttributes as $key) {
155
-            $prefix = snake_case(class_basename($valueClass)) . '_';
155
+            $prefix = snake_case(class_basename($valueClass)).'_';
156 156
 
157 157
             $voWrapper = $this->factory->make($valueObject);
158 158
 
159
-            $voWrapper->setEntityAttribute($key, $attributes[$prefix . $key]);
159
+            $voWrapper->setEntityAttribute($key, $attributes[$prefix.$key]);
160 160
             
161
-            unset($attributes[$prefix . $key]);
161
+            unset($attributes[$prefix.$key]);
162 162
         }
163 163
         
164 164
         $attributes[$localKey] = $valueObject;
Please login to merge, or discard this patch.
Indentation   +184 added lines, -184 removed lines patch added patch discarded remove patch
@@ -11,188 +11,188 @@
 block discarded – undo
11 11
  */
12 12
 class EntityBuilder
13 13
 {
14
-    /**
15
-     * The mapper for the entity to build
16
-     * @var \Analogue\ORM\System\Mapper
17
-     */
18
-    protected $mapper;
19
-
20
-    /**
21
-     * The Entity Map for the entity to build.
22
-     *
23
-     * @var \Analogue\ORM\EntityMap
24
-     */
25
-    protected $entityMap;
26
-
27
-    /**
28
-     * Relations that will be eager loaded on this query
29
-     *
30
-     * @var array
31
-     */
32
-    protected $eagerLoads;
33
-
34
-    /**
35
-     * Relations that will be lazy loaded on this query
36
-     *
37
-     * @var array
38
-     */
39
-    protected $lazyLoads;
40
-
41
-    /**
42
-     * @var array
43
-     */
44
-    protected $casts;
45
-
46
-    /**
47
-     * Entity Wrapper Factory
48
-     * @var \Analogue\ORM\System\Wrappers\Factory
49
-     */
50
-    protected $factory;
51
-
52
-    /**
53
-     * EntityBuilder constructor.
54
-     * @param Mapper $mapper
55
-     * @param array  $eagerLoads
56
-     */
57
-    public function __construct(Mapper $mapper, array $eagerLoads)
58
-    {
59
-        $this->mapper = $mapper;
60
-
61
-        $this->entityMap = $mapper->getEntityMap();
62
-
63
-        $this->eagerLoads = $eagerLoads;
64
-
65
-        $this->lazyLoads = $this->prepareLazyLoading();
66
-
67
-        $this->factory = new Factory;
68
-    }
69
-
70
-    /**
71
-     * Convert an array of values into an entity.
72
-     *
73
-     * @param  array $result
74
-     * @return array
75
-     */
76
-    public function build(array $result)
77
-    {
78
-        $keyName = $this->entityMap->getKeyName();
79
-
80
-        $tmpCache = [];
81
-
82
-        $instance = $this->getWrapperInstance();
83
-
84
-        $tmpCache[$result[$keyName]] = $result;
85
-
86
-        // Hydrate any embedded Value Object
87
-        $this->hydrateValueObjects($result);
88
-
89
-        // Hydrate relationship attributes with lazyloading proxies
90
-        if (count($this->lazyLoads) > 0) {
91
-            $proxies = $this->getLazyLoadingProxies($instance);
92
-            $instance->setEntityAttributes($result + $proxies);
93
-        }
94
-        else {
95
-            $instance->setEntityAttributes($result);
96
-        }
97
-
98
-        // Directly Unwrap the entity now that it has been hydrated
99
-        $entity = $instance->getObject();
100
-
101
-        $this->mapper->getEntityCache()->add($tmpCache);
102
-
103
-        return $entity;
104
-    }
105
-
106
-    /**
107
-     * Get the correct wrapper prototype corresponding to the object type
108
-     *
109
-     * @throws \Analogue\ORM\Exceptions\MappingException
110
-     * @return InternallyMappable
111
-     */
112
-    protected function getWrapperInstance()
113
-    {
114
-        return $this->factory->make($this->mapper->newInstance());
115
-    }
116
-
117
-    /**
118
-     * Hydrate value object embedded in this entity
119
-     *
120
-     * @param  array $attributes
121
-     * @throws \Analogue\ORM\Exceptions\MappingException
122
-     * @return void
123
-     */
124
-    protected function hydrateValueObjects(& $attributes)
125
-    {
126
-        foreach ($this->entityMap->getEmbeddables() as $localKey => $valueClass) {
127
-            $this->hydrateValueObject($attributes, $localKey, $valueClass);
128
-        }
129
-    }
130
-
131
-    /**
132
-     * Hydrate a single value object
133
-     *
134
-     * @param  array  $attributes
135
-     * @param  string $localKey
136
-     * @param  string $valueClass
137
-     * @throws \Analogue\ORM\Exceptions\MappingException
138
-     * @return void
139
-     */
140
-    protected function hydrateValueObject(& $attributes, $localKey, $valueClass)
141
-    {
142
-        $map = $this->mapper->getManager()->getValueMap($valueClass);
143
-
144
-        $embeddedAttributes = $map->getAttributes();
145
-
146
-        $valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
147
-
148
-        foreach ($embeddedAttributes as $key) {
149
-            $prefix = snake_case(class_basename($valueClass)) . '_';
150
-
151
-            $voWrapper = $this->factory->make($valueObject);
152
-
153
-            $voWrapper->setEntityAttribute($key, $attributes[$prefix . $key]);
154
-
155
-            unset($attributes[$prefix . $key]);
156
-        }
157
-
158
-        $attributes[$localKey] = $valueObject;
159
-    }
160
-
161
-    /**
162
-     * Deduce the relationships that will be lazy loaded from the eagerLoads array
163
-     *
164
-     * @return array
165
-     */
166
-    protected function prepareLazyLoading()
167
-    {
168
-        $relations = $this->entityMap->getRelationships();
169
-
170
-        return array_diff($relations, $this->eagerLoads);
171
-    }
172
-
173
-    /**
174
-     * Build lazy loading proxies for the current entity
175
-     *
176
-     * @param InternallyMappable $entity
177
-     *
178
-     * @return array
179
-     */
180
-    protected function getLazyLoadingProxies(InternallyMappable $entity)
181
-    {
182
-        $proxies = [];
183
-
184
-        $singleRelations = $this->entityMap->getSingleRelationships();
185
-        $manyRelations = $this->entityMap->getManyRelationships();
186
-
187
-        foreach ($this->lazyLoads as $relation) {
188
-            if (in_array($relation, $singleRelations)) {
189
-                $proxies[$relation] = new EntityProxy($entity->getObject(), $relation);
190
-            }
191
-            if (in_array($relation, $manyRelations)) {
192
-                $proxies[$relation] = new CollectionProxy($entity->getObject(), $relation);
193
-            }
194
-        }
195
-
196
-        return $proxies;
197
-    }
14
+	/**
15
+	 * The mapper for the entity to build
16
+	 * @var \Analogue\ORM\System\Mapper
17
+	 */
18
+	protected $mapper;
19
+
20
+	/**
21
+	 * The Entity Map for the entity to build.
22
+	 *
23
+	 * @var \Analogue\ORM\EntityMap
24
+	 */
25
+	protected $entityMap;
26
+
27
+	/**
28
+	 * Relations that will be eager loaded on this query
29
+	 *
30
+	 * @var array
31
+	 */
32
+	protected $eagerLoads;
33
+
34
+	/**
35
+	 * Relations that will be lazy loaded on this query
36
+	 *
37
+	 * @var array
38
+	 */
39
+	protected $lazyLoads;
40
+
41
+	/**
42
+	 * @var array
43
+	 */
44
+	protected $casts;
45
+
46
+	/**
47
+	 * Entity Wrapper Factory
48
+	 * @var \Analogue\ORM\System\Wrappers\Factory
49
+	 */
50
+	protected $factory;
51
+
52
+	/**
53
+	 * EntityBuilder constructor.
54
+	 * @param Mapper $mapper
55
+	 * @param array  $eagerLoads
56
+	 */
57
+	public function __construct(Mapper $mapper, array $eagerLoads)
58
+	{
59
+		$this->mapper = $mapper;
60
+
61
+		$this->entityMap = $mapper->getEntityMap();
62
+
63
+		$this->eagerLoads = $eagerLoads;
64
+
65
+		$this->lazyLoads = $this->prepareLazyLoading();
66
+
67
+		$this->factory = new Factory;
68
+	}
69
+
70
+	/**
71
+	 * Convert an array of values into an entity.
72
+	 *
73
+	 * @param  array $result
74
+	 * @return array
75
+	 */
76
+	public function build(array $result)
77
+	{
78
+		$keyName = $this->entityMap->getKeyName();
79
+
80
+		$tmpCache = [];
81
+
82
+		$instance = $this->getWrapperInstance();
83
+
84
+		$tmpCache[$result[$keyName]] = $result;
85
+
86
+		// Hydrate any embedded Value Object
87
+		$this->hydrateValueObjects($result);
88
+
89
+		// Hydrate relationship attributes with lazyloading proxies
90
+		if (count($this->lazyLoads) > 0) {
91
+			$proxies = $this->getLazyLoadingProxies($instance);
92
+			$instance->setEntityAttributes($result + $proxies);
93
+		}
94
+		else {
95
+			$instance->setEntityAttributes($result);
96
+		}
97
+
98
+		// Directly Unwrap the entity now that it has been hydrated
99
+		$entity = $instance->getObject();
100
+
101
+		$this->mapper->getEntityCache()->add($tmpCache);
102
+
103
+		return $entity;
104
+	}
105
+
106
+	/**
107
+	 * Get the correct wrapper prototype corresponding to the object type
108
+	 *
109
+	 * @throws \Analogue\ORM\Exceptions\MappingException
110
+	 * @return InternallyMappable
111
+	 */
112
+	protected function getWrapperInstance()
113
+	{
114
+		return $this->factory->make($this->mapper->newInstance());
115
+	}
116
+
117
+	/**
118
+	 * Hydrate value object embedded in this entity
119
+	 *
120
+	 * @param  array $attributes
121
+	 * @throws \Analogue\ORM\Exceptions\MappingException
122
+	 * @return void
123
+	 */
124
+	protected function hydrateValueObjects(& $attributes)
125
+	{
126
+		foreach ($this->entityMap->getEmbeddables() as $localKey => $valueClass) {
127
+			$this->hydrateValueObject($attributes, $localKey, $valueClass);
128
+		}
129
+	}
130
+
131
+	/**
132
+	 * Hydrate a single value object
133
+	 *
134
+	 * @param  array  $attributes
135
+	 * @param  string $localKey
136
+	 * @param  string $valueClass
137
+	 * @throws \Analogue\ORM\Exceptions\MappingException
138
+	 * @return void
139
+	 */
140
+	protected function hydrateValueObject(& $attributes, $localKey, $valueClass)
141
+	{
142
+		$map = $this->mapper->getManager()->getValueMap($valueClass);
143
+
144
+		$embeddedAttributes = $map->getAttributes();
145
+
146
+		$valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
147
+
148
+		foreach ($embeddedAttributes as $key) {
149
+			$prefix = snake_case(class_basename($valueClass)) . '_';
150
+
151
+			$voWrapper = $this->factory->make($valueObject);
152
+
153
+			$voWrapper->setEntityAttribute($key, $attributes[$prefix . $key]);
154
+
155
+			unset($attributes[$prefix . $key]);
156
+		}
157
+
158
+		$attributes[$localKey] = $valueObject;
159
+	}
160
+
161
+	/**
162
+	 * Deduce the relationships that will be lazy loaded from the eagerLoads array
163
+	 *
164
+	 * @return array
165
+	 */
166
+	protected function prepareLazyLoading()
167
+	{
168
+		$relations = $this->entityMap->getRelationships();
169
+
170
+		return array_diff($relations, $this->eagerLoads);
171
+	}
172
+
173
+	/**
174
+	 * Build lazy loading proxies for the current entity
175
+	 *
176
+	 * @param InternallyMappable $entity
177
+	 *
178
+	 * @return array
179
+	 */
180
+	protected function getLazyLoadingProxies(InternallyMappable $entity)
181
+	{
182
+		$proxies = [];
183
+
184
+		$singleRelations = $this->entityMap->getSingleRelationships();
185
+		$manyRelations = $this->entityMap->getManyRelationships();
186
+
187
+		foreach ($this->lazyLoads as $relation) {
188
+			if (in_array($relation, $singleRelations)) {
189
+				$proxies[$relation] = new EntityProxy($entity->getObject(), $relation);
190
+			}
191
+			if (in_array($relation, $manyRelations)) {
192
+				$proxies[$relation] = new CollectionProxy($entity->getObject(), $relation);
193
+			}
194
+		}
195
+
196
+		return $proxies;
197
+	}
198 198
 }
199 199
\ No newline at end of file
Please login to merge, or discard this patch.
Braces   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -221,8 +221,7 @@
 block discarded – undo
221 221
     {
222 222
         if(array_key_exists($key, $this->virtualAttributes)) {
223 223
             return $this->virtualAttributes[$key];
224
-        }
225
-        else {
224
+        } else {
226 225
             return null;
227 226
         }
228 227
     }
Please login to merge, or discard this patch.
src/System/EntityCache.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -231,7 +231,7 @@
 block discarded – undo
231 231
 
232 232
         $keyName = $mapper->getEntityMap()->getKeyName();
233 233
 
234
-        return $class . '.' . $entity->getEntityAttribute($keyName);
234
+        return $class.'.'.$entity->getEntityAttribute($keyName);
235 235
     }
236 236
 
237 237
     /**
Please login to merge, or discard this patch.
Indentation   +313 added lines, -313 removed lines patch added patch discarded remove patch
@@ -15,319 +15,319 @@
 block discarded – undo
15 15
  */
16 16
 class EntityCache
17 17
 {
18
-    /**
19
-     * Entity's raw attributes/relationships
20
-     *
21
-     * @var array
22
-     */
23
-    protected $cache = [];
24
-
25
-    /**
26
-     * Entity Map for the current Entity Type
27
-     * @var \Analogue\ORM\EntityMap
28
-     */
29
-    protected $entityMap;
30
-
31
-    /**
32
-     * Wrapper factory
33
-     *
34
-     * @var \Analogue\ORM\System\Wrappers\Factory
35
-     */
36
-    protected $factory;
37
-
38
-    /**
39
-     * Associative array containing list of pivot attributes per relationship
40
-     * so we don't have to call relationship method on refresh.
41
-     *
42
-     * @var array
43
-     */
44
-    protected $pivotAttributes = [];
45
-
46
-    /**
47
-     * EntityCache constructor.
48
-     * @param EntityMap $entityMap
49
-     */
50
-    public function __construct(EntityMap $entityMap)
51
-    {
52
-        $this->entityMap = $entityMap;
53
-
54
-        $this->factory = new Factory;
55
-    }
56
-
57
-    /**
58
-     * Add an array of key=>attributes representing
59
-     * the initial state of loaded entities.
60
-     *
61
-     * @param array $entities
62
-     */
63
-    public function add(array $entities)
64
-    {
65
-        if (count($this->cache) == 0) {
66
-            $this->cache = $entities;
67
-        } else {
68
-            $this->mergeCacheResults($entities);
69
-        }
70
-    }
71
-
72
-    /**
73
-     * Retrieve initial attributes for a single entity
74
-     *
75
-     * @param  string $id
76
-     * @return array
77
-     */
78
-    public function get($id)
79
-    {
80
-        if ($this->has($id)) {
81
-            return $this->cache[$id];
82
-        } else {
83
-            return [];
84
-        }
85
-    }
86
-
87
-    /**
88
-     * Check if a record for this id exists.
89
-     *
90
-     * @param  string  $id
91
-     * @return boolean
92
-     */
93
-    public function has($id)
94
-    {
95
-        return array_key_exists($id, $this->cache);
96
-    }
97
-
98
-    /**
99
-     * Combine new result set with existing attributes in
100
-     * cache.
101
-     *
102
-     * @param  array $entities
103
-     * @return void
104
-     */
105
-    protected function mergeCacheResults($entities)
106
-    {
107
-        foreach ($entities as $key => $entity) {
108
-            $this->cache[$key] = $entity;
109
-        }
110
-    }
111
-
112
-    /**
113
-     * Cache Relation's query result for an entity
114
-     *
115
-     * @param  mixed        $parent
116
-     * @param  string       $relation name of the relation
117
-     * @param  mixed        $results  results of the relationship's query
118
-     * @param  Relationship $relationship
119
-     * @throws MappingException
120
-     * @return void
121
-     */
122
-    public function cacheLoadedRelationResult($parent, $relation, $results, Relationship $relationship)
123
-    {
124
-        $keyName = $this->entityMap->getKeyName();
125
-
126
-        if (!$parent instanceof InternallyMappable) {
127
-            $parent = $this->factory->make($parent);
128
-        }
129
-
130
-        $key = $parent->getEntityAttribute($keyName);
18
+	/**
19
+	 * Entity's raw attributes/relationships
20
+	 *
21
+	 * @var array
22
+	 */
23
+	protected $cache = [];
24
+
25
+	/**
26
+	 * Entity Map for the current Entity Type
27
+	 * @var \Analogue\ORM\EntityMap
28
+	 */
29
+	protected $entityMap;
30
+
31
+	/**
32
+	 * Wrapper factory
33
+	 *
34
+	 * @var \Analogue\ORM\System\Wrappers\Factory
35
+	 */
36
+	protected $factory;
37
+
38
+	/**
39
+	 * Associative array containing list of pivot attributes per relationship
40
+	 * so we don't have to call relationship method on refresh.
41
+	 *
42
+	 * @var array
43
+	 */
44
+	protected $pivotAttributes = [];
45
+
46
+	/**
47
+	 * EntityCache constructor.
48
+	 * @param EntityMap $entityMap
49
+	 */
50
+	public function __construct(EntityMap $entityMap)
51
+	{
52
+		$this->entityMap = $entityMap;
53
+
54
+		$this->factory = new Factory;
55
+	}
56
+
57
+	/**
58
+	 * Add an array of key=>attributes representing
59
+	 * the initial state of loaded entities.
60
+	 *
61
+	 * @param array $entities
62
+	 */
63
+	public function add(array $entities)
64
+	{
65
+		if (count($this->cache) == 0) {
66
+			$this->cache = $entities;
67
+		} else {
68
+			$this->mergeCacheResults($entities);
69
+		}
70
+	}
71
+
72
+	/**
73
+	 * Retrieve initial attributes for a single entity
74
+	 *
75
+	 * @param  string $id
76
+	 * @return array
77
+	 */
78
+	public function get($id)
79
+	{
80
+		if ($this->has($id)) {
81
+			return $this->cache[$id];
82
+		} else {
83
+			return [];
84
+		}
85
+	}
86
+
87
+	/**
88
+	 * Check if a record for this id exists.
89
+	 *
90
+	 * @param  string  $id
91
+	 * @return boolean
92
+	 */
93
+	public function has($id)
94
+	{
95
+		return array_key_exists($id, $this->cache);
96
+	}
97
+
98
+	/**
99
+	 * Combine new result set with existing attributes in
100
+	 * cache.
101
+	 *
102
+	 * @param  array $entities
103
+	 * @return void
104
+	 */
105
+	protected function mergeCacheResults($entities)
106
+	{
107
+		foreach ($entities as $key => $entity) {
108
+			$this->cache[$key] = $entity;
109
+		}
110
+	}
111
+
112
+	/**
113
+	 * Cache Relation's query result for an entity
114
+	 *
115
+	 * @param  mixed        $parent
116
+	 * @param  string       $relation name of the relation
117
+	 * @param  mixed        $results  results of the relationship's query
118
+	 * @param  Relationship $relationship
119
+	 * @throws MappingException
120
+	 * @return void
121
+	 */
122
+	public function cacheLoadedRelationResult($parent, $relation, $results, Relationship $relationship)
123
+	{
124
+		$keyName = $this->entityMap->getKeyName();
125
+
126
+		if (!$parent instanceof InternallyMappable) {
127
+			$parent = $this->factory->make($parent);
128
+		}
129
+
130
+		$key = $parent->getEntityAttribute($keyName);
131 131
         
132
-        if ($results instanceof EntityCollection) {
133
-            $this->cacheManyRelationResults($key, $relation, $results, $relationship);
134
-        }
135
-
136
-        // POPO : Maybe this check isn't needed, or we have to check for stdClass
137
-        // instead
138
-        if ($results instanceof Mappable) {
139
-            $this->cacheSingleRelationResult($key, $relation, $results, $relationship);
140
-        }
141
-    }
142
-
143
-    /**
144
-     * Create a cachedRelationship instance which will hold related entity's hash and pivot attributes, if any.
145
-     *
146
-     * @param  string       $parentKey
147
-     * @param  string       $relation
148
-     * @param  array        $result
149
-     * @param  Relationship $relationship
150
-     * @throws MappingException
151
-     * @return CachedRelationship
152
-     */
153
-    protected function getCachedRelationship($parentKey, $relation, $result, Relationship $relationship)
154
-    {
155
-        $pivotColumns = $relationship->getPivotAttributes();
156
-
157
-        if (!array_key_exists($relation, $this->pivotAttributes)) {
158
-            $this->pivotAttributes[$relation] = $pivotColumns;
159
-        }
160
-
161
-        $wrapper = $this->factory->make($result);
162
-
163
-        $hash = $this->getEntityHash($wrapper);
164
-
165
-        if (count($pivotColumns) > 0) {
166
-            $pivotAttributes = [];
167
-            foreach ($pivotColumns as $column) {
168
-                $pivot = $wrapper->getEntityAttribute('pivot');
169
-
170
-                $pivotWrapper = $this->factory->make($pivot);
171
-
172
-                $pivotAttributes[$column] = $pivotWrapper->getEntityAttribute($column);
173
-            }
174
-
175
-            $cachedRelationship = new CachedRelationship($hash, $pivotAttributes);
176
-        } else {
177
-            $cachedRelationship = new CachedRelationship($hash);
178
-        }
179
-
180
-        return $cachedRelationship;
181
-    }
182
-
183
-    /**
184
-     * Cache a many relationship
185
-     *
186
-     * @param                  $parentKey
187
-     * @param string           $relation
188
-     * @param EntityCollection $results
189
-     * @param Relationship     $relationship
190
-     * @throws MappingException
191
-     */
192
-    protected function cacheManyRelationResults($parentKey, $relation, $results, Relationship $relationship)
193
-    {
194
-        $this->cache[$parentKey][$relation] = [];
195
-
196
-        foreach ($results as $result) {
197
-            $cachedRelationship = $this->getCachedRelationship($parentKey, $relation, $result, $relationship);
198
-
199
-            $relatedHash = $cachedRelationship->getHash();
200
-
201
-            $this->cache[$parentKey][$relation][$relatedHash] = $cachedRelationship;
202
-        }
203
-    }
204
-
205
-    /**
206
-     * Cache a single relationship
207
-     *
208
-     * @param              $parentKey
209
-     * @param string       $relation
210
-     * @param Mappable     $result
211
-     * @param Relationship $relationship
212
-     * @throws MappingException
213
-     */
214
-    protected function cacheSingleRelationResult($parentKey, $relation, $result, Relationship $relationship)
215
-    {
216
-        $this->cache[$parentKey][$relation] = $this->getCachedRelationship($parentKey, $relation, $result, $relationship);
217
-    }
218
-
219
-    /**
220
-     * Get Entity's Hash
221
-     *
222
-     * @param  $entity
223
-     * @throws MappingException
224
-     * @return string
225
-     */
226
-    protected function getEntityHash(InternallyMappable $entity)
227
-    {
228
-        $class = get_class($entity->getObject());
229
-
230
-        $mapper = Manager::getMapper($class);
231
-
232
-        $keyName = $mapper->getEntityMap()->getKeyName();
233
-
234
-        return $class . '.' . $entity->getEntityAttribute($keyName);
235
-    }
236
-
237
-    /**
238
-     * Refresh the cache record for an aggregated entity after a write operation
239
-     * @param Aggregate $entity
240
-     */
241
-    public function refresh(Aggregate $entity)
242
-    {
243
-        $this->cache[$entity->getEntityId()] = $this->transform($entity);
244
-    }
245
-
246
-    /**
247
-     * Transform an Aggregated Entity into a cache record
248
-     *
249
-     * @param  Aggregate $aggregatedEntity
250
-     * @throws MappingException
251
-     * @return array
252
-     */
253
-    protected function transform(Aggregate $aggregatedEntity)
254
-    {
255
-        $baseAttributes = $aggregatedEntity->getRawAttributes();
256
-
257
-        $relationAttributes = [];
258
-
259
-        // First we'll handle each relationships that are a one to one
260
-        // relation, and which will be saved as a CachedRelationship
261
-        // object inside the cache.
262
-
263
-        // NOTE : storing localRelationships maybe useless has we store
264
-        // the foreign key in the attributes already.
265
-
266
-        foreach ($this->entityMap->getSingleRelationships() as $relation) {
267
-            $aggregates = $aggregatedEntity->getRelationship($relation);
132
+		if ($results instanceof EntityCollection) {
133
+			$this->cacheManyRelationResults($key, $relation, $results, $relationship);
134
+		}
135
+
136
+		// POPO : Maybe this check isn't needed, or we have to check for stdClass
137
+		// instead
138
+		if ($results instanceof Mappable) {
139
+			$this->cacheSingleRelationResult($key, $relation, $results, $relationship);
140
+		}
141
+	}
142
+
143
+	/**
144
+	 * Create a cachedRelationship instance which will hold related entity's hash and pivot attributes, if any.
145
+	 *
146
+	 * @param  string       $parentKey
147
+	 * @param  string       $relation
148
+	 * @param  array        $result
149
+	 * @param  Relationship $relationship
150
+	 * @throws MappingException
151
+	 * @return CachedRelationship
152
+	 */
153
+	protected function getCachedRelationship($parentKey, $relation, $result, Relationship $relationship)
154
+	{
155
+		$pivotColumns = $relationship->getPivotAttributes();
156
+
157
+		if (!array_key_exists($relation, $this->pivotAttributes)) {
158
+			$this->pivotAttributes[$relation] = $pivotColumns;
159
+		}
160
+
161
+		$wrapper = $this->factory->make($result);
162
+
163
+		$hash = $this->getEntityHash($wrapper);
164
+
165
+		if (count($pivotColumns) > 0) {
166
+			$pivotAttributes = [];
167
+			foreach ($pivotColumns as $column) {
168
+				$pivot = $wrapper->getEntityAttribute('pivot');
169
+
170
+				$pivotWrapper = $this->factory->make($pivot);
171
+
172
+				$pivotAttributes[$column] = $pivotWrapper->getEntityAttribute($column);
173
+			}
174
+
175
+			$cachedRelationship = new CachedRelationship($hash, $pivotAttributes);
176
+		} else {
177
+			$cachedRelationship = new CachedRelationship($hash);
178
+		}
179
+
180
+		return $cachedRelationship;
181
+	}
182
+
183
+	/**
184
+	 * Cache a many relationship
185
+	 *
186
+	 * @param                  $parentKey
187
+	 * @param string           $relation
188
+	 * @param EntityCollection $results
189
+	 * @param Relationship     $relationship
190
+	 * @throws MappingException
191
+	 */
192
+	protected function cacheManyRelationResults($parentKey, $relation, $results, Relationship $relationship)
193
+	{
194
+		$this->cache[$parentKey][$relation] = [];
195
+
196
+		foreach ($results as $result) {
197
+			$cachedRelationship = $this->getCachedRelationship($parentKey, $relation, $result, $relationship);
198
+
199
+			$relatedHash = $cachedRelationship->getHash();
200
+
201
+			$this->cache[$parentKey][$relation][$relatedHash] = $cachedRelationship;
202
+		}
203
+	}
204
+
205
+	/**
206
+	 * Cache a single relationship
207
+	 *
208
+	 * @param              $parentKey
209
+	 * @param string       $relation
210
+	 * @param Mappable     $result
211
+	 * @param Relationship $relationship
212
+	 * @throws MappingException
213
+	 */
214
+	protected function cacheSingleRelationResult($parentKey, $relation, $result, Relationship $relationship)
215
+	{
216
+		$this->cache[$parentKey][$relation] = $this->getCachedRelationship($parentKey, $relation, $result, $relationship);
217
+	}
218
+
219
+	/**
220
+	 * Get Entity's Hash
221
+	 *
222
+	 * @param  $entity
223
+	 * @throws MappingException
224
+	 * @return string
225
+	 */
226
+	protected function getEntityHash(InternallyMappable $entity)
227
+	{
228
+		$class = get_class($entity->getObject());
229
+
230
+		$mapper = Manager::getMapper($class);
231
+
232
+		$keyName = $mapper->getEntityMap()->getKeyName();
233
+
234
+		return $class . '.' . $entity->getEntityAttribute($keyName);
235
+	}
236
+
237
+	/**
238
+	 * Refresh the cache record for an aggregated entity after a write operation
239
+	 * @param Aggregate $entity
240
+	 */
241
+	public function refresh(Aggregate $entity)
242
+	{
243
+		$this->cache[$entity->getEntityId()] = $this->transform($entity);
244
+	}
245
+
246
+	/**
247
+	 * Transform an Aggregated Entity into a cache record
248
+	 *
249
+	 * @param  Aggregate $aggregatedEntity
250
+	 * @throws MappingException
251
+	 * @return array
252
+	 */
253
+	protected function transform(Aggregate $aggregatedEntity)
254
+	{
255
+		$baseAttributes = $aggregatedEntity->getRawAttributes();
256
+
257
+		$relationAttributes = [];
258
+
259
+		// First we'll handle each relationships that are a one to one
260
+		// relation, and which will be saved as a CachedRelationship
261
+		// object inside the cache.
262
+
263
+		// NOTE : storing localRelationships maybe useless has we store
264
+		// the foreign key in the attributes already.
265
+
266
+		foreach ($this->entityMap->getSingleRelationships() as $relation) {
267
+			$aggregates = $aggregatedEntity->getRelationship($relation);
268 268
             
269
-            if (count($aggregates) == 1) {
270
-                $related = $aggregates[0];
271
-                $relationAttributes[$relation] = new CachedRelationship($related->getEntityHash());
272
-            }
273
-            if (count($aggregates) > 1) {
274
-                throw new MappingException("Single Relationship '$relation' contains several related entities");
275
-            }
276
-        }
277
-
278
-        // Then we'll handle the 'many' relationships and store them as
279
-        // an array of CachedRelationship objects.
280
-
281
-        foreach ($this->entityMap->getManyRelationships() as $relation) {
282
-            $aggregates = $aggregatedEntity->getRelationship($relation);
283
-
284
-            $relationAttributes[$relation] = [];
285
-
286
-            foreach ($aggregates as $aggregate) {
287
-                $relationAttributes[$relation][] = new CachedRelationship(
288
-                    $aggregate->getEntityHash(),
289
-                    $aggregate->getPivotAttributes()
290
-                );
291
-            }
292
-        }
293
-
294
-        return $baseAttributes + $relationAttributes;
295
-    }
296
-
297
-    /**
298
-     * Get pivot attributes for a relation
299
-     * 
300
-     * @param  string             $relation
301
-     * @param  InternallyMappable $entity
302
-     * @return array
303
-     */
304
-    protected function getPivotValues($relation, InternallyMappable $entity)
305
-    {
306
-        $values = [];
307
-
308
-        $entityAttributes = $entity->getEntityAttributes();
309
-
310
-        if (array_key_exists($relation, $this->pivotAttributes)) {
311
-            foreach ($this->pivotAttributes[$relation] as $attribute) {
312
-                if (array_key_exists($attribute, $entityAttributes)) {
313
-                    $values[$attribute] = $entity->getEntityAttribute('pivot')->$attribute;
314
-                }
315
-            }
316
-        }
317
-
318
-        return $values;
319
-    }
320
-
321
-    /**
322
-     * Clear the entity Cache. Use with caution as it could result
323
-     * in impredictable behaviour if the cached entities are stored
324
-     * after the cache clear operation. 
325
-     * 
326
-     * @return void
327
-     */
328
-    public function clear()
329
-    {   
330
-        $this->cache = [];
331
-        $this->pivotAttributes = [];
332
-    }
269
+			if (count($aggregates) == 1) {
270
+				$related = $aggregates[0];
271
+				$relationAttributes[$relation] = new CachedRelationship($related->getEntityHash());
272
+			}
273
+			if (count($aggregates) > 1) {
274
+				throw new MappingException("Single Relationship '$relation' contains several related entities");
275
+			}
276
+		}
277
+
278
+		// Then we'll handle the 'many' relationships and store them as
279
+		// an array of CachedRelationship objects.
280
+
281
+		foreach ($this->entityMap->getManyRelationships() as $relation) {
282
+			$aggregates = $aggregatedEntity->getRelationship($relation);
283
+
284
+			$relationAttributes[$relation] = [];
285
+
286
+			foreach ($aggregates as $aggregate) {
287
+				$relationAttributes[$relation][] = new CachedRelationship(
288
+					$aggregate->getEntityHash(),
289
+					$aggregate->getPivotAttributes()
290
+				);
291
+			}
292
+		}
293
+
294
+		return $baseAttributes + $relationAttributes;
295
+	}
296
+
297
+	/**
298
+	 * Get pivot attributes for a relation
299
+	 * 
300
+	 * @param  string             $relation
301
+	 * @param  InternallyMappable $entity
302
+	 * @return array
303
+	 */
304
+	protected function getPivotValues($relation, InternallyMappable $entity)
305
+	{
306
+		$values = [];
307
+
308
+		$entityAttributes = $entity->getEntityAttributes();
309
+
310
+		if (array_key_exists($relation, $this->pivotAttributes)) {
311
+			foreach ($this->pivotAttributes[$relation] as $attribute) {
312
+				if (array_key_exists($attribute, $entityAttributes)) {
313
+					$values[$attribute] = $entity->getEntityAttribute('pivot')->$attribute;
314
+				}
315
+			}
316
+		}
317
+
318
+		return $values;
319
+	}
320
+
321
+	/**
322
+	 * Clear the entity Cache. Use with caution as it could result
323
+	 * in impredictable behaviour if the cached entities are stored
324
+	 * after the cache clear operation. 
325
+	 * 
326
+	 * @return void
327
+	 */
328
+	public function clear()
329
+	{   
330
+		$this->cache = [];
331
+		$this->pivotAttributes = [];
332
+	}
333 333
 }
Please login to merge, or discard this patch.
src/System/Proxies/EntityProxy.php 1 patch
Indentation   +68 added lines, -68 removed lines patch added patch discarded remove patch
@@ -4,81 +4,81 @@
 block discarded – undo
4 4
 
5 5
 class EntityProxy extends Proxy
6 6
 {
7
-    /**
8
-     * Underlying entity
9
-     *
10
-     * @var mixed
11
-     */
12
-    protected $entity;
7
+	/**
8
+	 * Underlying entity
9
+	 *
10
+	 * @var mixed
11
+	 */
12
+	protected $entity;
13 13
 
14
-    /**
15
-     * Load the underlying relation
16
-     *
17
-     * @return void
18
-     */
19
-    protected function loadOnce()
20
-    {
21
-        $this->entity = $this->load();
22
-    }
14
+	/**
15
+	 * Load the underlying relation
16
+	 *
17
+	 * @return void
18
+	 */
19
+	protected function loadOnce()
20
+	{
21
+		$this->entity = $this->load();
22
+	}
23 23
 
24
-    /**
25
-     * Return the actual Entity
26
-     *
27
-     * @return mixed
28
-     */
29
-    public function getUnderlyingObject()
30
-    {
31
-        if (!$this->isLoaded()) {
32
-            $this->loadOnce();
33
-        }
24
+	/**
25
+	 * Return the actual Entity
26
+	 *
27
+	 * @return mixed
28
+	 */
29
+	public function getUnderlyingObject()
30
+	{
31
+		if (!$this->isLoaded()) {
32
+			$this->loadOnce();
33
+		}
34 34
 
35
-        return $this->entity;
36
-    }
35
+		return $this->entity;
36
+	}
37 37
 
38
-    /**
39
-     * Transparently passes get operation to underlying entity
40
-     *
41
-     * @param  string $attribute
42
-     * @return mixed
43
-     */
44
-    public function __get($attribute)
45
-    {
46
-        if (!$this->isLoaded()) {
47
-            $this->loadOnce();
48
-        }
38
+	/**
39
+	 * Transparently passes get operation to underlying entity
40
+	 *
41
+	 * @param  string $attribute
42
+	 * @return mixed
43
+	 */
44
+	public function __get($attribute)
45
+	{
46
+		if (!$this->isLoaded()) {
47
+			$this->loadOnce();
48
+		}
49 49
 
50
-        return $this->entity->$attribute;
51
-    }
50
+		return $this->entity->$attribute;
51
+	}
52 52
 
53
-    /**
54
-     * Transparently passes set operation to underlying entity
55
-     *
56
-     * @param  string $attribute [description]
57
-     * @param  mixed
58
-     * @return void
59
-     */
60
-    public function __set($attribute, $value)
61
-    {
62
-        if (!$this->isLoaded()) {
63
-            $this->loadOnce();
64
-        }
53
+	/**
54
+	 * Transparently passes set operation to underlying entity
55
+	 *
56
+	 * @param  string $attribute [description]
57
+	 * @param  mixed
58
+	 * @return void
59
+	 */
60
+	public function __set($attribute, $value)
61
+	{
62
+		if (!$this->isLoaded()) {
63
+			$this->loadOnce();
64
+		}
65 65
 
66
-        $this->entity->$attribute = $value;
67
-    }
66
+		$this->entity->$attribute = $value;
67
+	}
68 68
 
69
-    /**
70
-     * Transparently Redirect non overrided calls to the lazy loaded Entity
71
-     *
72
-     * @param  string $method
73
-     * @param  array  $parameters
74
-     * @return mixed
75
-     */
76
-    public function __call($method, $parameters)
77
-    {
78
-        if (!$this->isLoaded()) {
79
-            $this->loadOnce();
80
-        }
69
+	/**
70
+	 * Transparently Redirect non overrided calls to the lazy loaded Entity
71
+	 *
72
+	 * @param  string $method
73
+	 * @param  array  $parameters
74
+	 * @return mixed
75
+	 */
76
+	public function __call($method, $parameters)
77
+	{
78
+		if (!$this->isLoaded()) {
79
+			$this->loadOnce();
80
+		}
81 81
 
82
-        return call_user_func_array([$this->entity, $method], $parameters);
83
-    }
82
+		return call_user_func_array([$this->entity, $method], $parameters);
83
+	}
84 84
 }
Please login to merge, or discard this patch.
src/System/Query.php 2 patches
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -466,7 +466,7 @@  discard block
 block discarded – undo
466 466
             $count = new Expression($count);
467 467
         }
468 468
 
469
-        return $this->where(new Expression('(' . $hasQuery->toSql() . ')'), $operator, $count, $boolean);
469
+        return $this->where(new Expression('('.$hasQuery->toSql().')'), $operator, $count, $boolean);
470 470
     }
471 471
 
472 472
     /**
@@ -499,7 +499,7 @@  discard block
 block discarded – undo
499 499
      */
500 500
     protected function getHasRelationQuery($relation, $entity)
501 501
     {
502
-        return Relationship::noConstraints(function () use ($relation, $entity) {
502
+        return Relationship::noConstraints(function() use ($relation, $entity) {
503 503
             return $this->entityMap->$relation($entity);
504 504
         });
505 505
     }
@@ -548,7 +548,7 @@  discard block
 block discarded – undo
548 548
             // constraints have been specified for the eager load and we'll just put
549 549
             // an empty Closure with the loader so that we can treat all the same.
550 550
             if (is_numeric($name)) {
551
-                $f = function () {};
551
+                $f = function() {};
552 552
 
553 553
                 list($name, $constraints) = [$constraints, $f];
554 554
             }
@@ -583,7 +583,7 @@  discard block
 block discarded – undo
583 583
             $progress[] = $segment;
584 584
 
585 585
             if (!isset($results[$last = implode('.', $progress)])) {
586
-                $results[$last] = function () {};
586
+                $results[$last] = function() {};
587 587
             }
588 588
         }
589 589
 
@@ -672,7 +672,7 @@  discard block
 block discarded – undo
672 672
         // We want to run a relationship query without any constrains so that we will
673 673
         // not have to remove these where clauses manually which gets really hacky
674 674
         // and is error prone while we remove the developer's own where clauses.
675
-        $query = Relationship::noConstraints(function () use ($relation) {
675
+        $query = Relationship::noConstraints(function() use ($relation) {
676 676
             return $this->entityMap->$relation($this->getEntityInstance());
677 677
         });
678 678
 
@@ -703,7 +703,7 @@  discard block
 block discarded – undo
703 703
         // that start with the given top relations and adds them to our arrays.
704 704
         foreach ($this->eagerLoad as $name => $constraints) {
705 705
             if ($this->isNested($name, $relation)) {
706
-                $nested[substr($name, strlen($relation . '.'))] = $constraints;
706
+                $nested[substr($name, strlen($relation.'.'))] = $constraints;
707 707
             }
708 708
         }
709 709
 
@@ -721,7 +721,7 @@  discard block
 block discarded – undo
721 721
     {
722 722
         $dots = str_contains($name, '.');
723 723
 
724
-        return $dots && starts_with($name, $relation . '.');
724
+        return $dots && starts_with($name, $relation.'.');
725 725
     }
726 726
 
727 727
     /**
Please login to merge, or discard this patch.
Indentation   +842 added lines, -842 removed lines patch added patch discarded remove patch
@@ -19,846 +19,846 @@
 block discarded – undo
19 19
  */
20 20
 class Query
21 21
 {
22
-    /**
23
-     * Mapper Instance
24
-     *
25
-     * @var \Analogue\ORM\System\Mapper
26
-     */
27
-    protected $mapper;
28
-
29
-    /**
30
-     * DB Adatper
31
-     *
32
-     * @var \Analogue\ORM\Drivers\DBAdapter
33
-     */
34
-    protected $adapter;
35
-
36
-    /**
37
-     * Query Builder Instance
38
-     *
39
-     * @var \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
40
-     */
41
-    protected $query;
42
-
43
-    /**
44
-     * Entity Map Instance
45
-     *
46
-     * @var \Analogue\ORM\EntityMap
47
-     */
48
-    protected $entityMap;
49
-
50
-    /**
51
-     * The relationships that should be eager loaded.
52
-     *
53
-     * @var array
54
-     */
55
-    protected $eagerLoad = [];
56
-
57
-    /**
58
-     * All of the registered builder macros.
59
-     *
60
-     * @var array
61
-     */
62
-    protected $macros = [];
63
-
64
-    /**
65
-     * The methods that should be returned from query builder.
66
-     *
67
-     * @var array
68
-     */
69
-    protected $passthru = [
70
-        'toSql',
71
-        'lists',
72
-        'pluck',
73
-        'count',
74
-        'min',
75
-        'max',
76
-        'avg',
77
-        'sum',
78
-        'exists',
79
-        'getBindings',
80
-    ];
81
-
82
-    /**
83
-     * Query Builder Blacklist
84
-     */
85
-    protected $blacklist = [
86
-        'insert',
87
-        'insertGetId',
88
-        'lock',
89
-        'lockForUpdate',
90
-        'sharedLock',
91
-        'update',
92
-        'increment',
93
-        'decrement',
94
-        'delete',
95
-        'truncate',
96
-        'raw',
97
-    ];
98
-
99
-    /**
100
-     * Create a new Analogue Query Builder instance.
101
-     *
102
-     * @param  Mapper    $mapper
103
-     * @param  DBAdapter $adapter
104
-     */
105
-    public function __construct(Mapper $mapper, DBAdapter $adapter)
106
-    {
107
-        $this->mapper = $mapper;
108
-
109
-        $this->adapter = $adapter;
110
-
111
-        $this->entityMap = $mapper->getEntityMap();
112
-
113
-        // Specify the table to work on
114
-        $this->query = $adapter->getQuery()->from($this->entityMap->getTable());
115
-
116
-        $this->with($this->entityMap->getEagerloadedRelationships());
117
-    }
118
-
119
-    /**
120
-     * Run the query and return the result
121
-     *
122
-     * @param  array $columns
123
-     * @return \Analogue\ORM\EntityCollection
124
-     */
125
-    public function get($columns = ['*'])
126
-    {
127
-        $entities = $this->getEntities($columns);
128
-
129
-        // If we actually found models we will also eager load any relationships that
130
-        // have been specified as needing to be eager loaded, which will solve the
131
-        // n+1 query issue for the developers to avoid running a lot of queries.
132
-
133
-        if (count($entities) > 0) {
134
-            $entities = $this->eagerLoadRelations($entities);
135
-        }
136
-
137
-        return $this->entityMap->newCollection($entities);
138
-    }
139
-
140
-    /**
141
-     * Find an entity by its primary key
142
-     *
143
-     * @param  string|integer $id
144
-     * @param  array          $columns
145
-     * @return \Analogue\ORM\Mappable
146
-     */
147
-    public function find($id, $columns = ['*'])
148
-    {
149
-        if (is_array($id)) {
150
-            return $this->findMany($id, $columns);
151
-        }
152
-
153
-        $this->query->where($this->entityMap->getQualifiedKeyName(), '=', $id);
154
-
155
-        return $this->first($columns);
156
-    }
157
-
158
-    /**
159
-     * Find many entities by their primary keys.
160
-     *
161
-     * @param  array $id
162
-     * @param  array $columns
163
-     * @return EntityCollection
164
-     */
165
-    public function findMany($id, $columns = ['*'])
166
-    {
167
-        if (empty($id)) {
168
-            return new EntityCollection;
169
-        }
170
-
171
-        $this->query->whereIn($this->entityMap->getQualifiedKeyName(), $id);
172
-
173
-        return $this->get($columns);
174
-    }
175
-
176
-    /**
177
-     * Find a model by its primary key or throw an exception.
178
-     *
179
-     * @param  mixed $id
180
-     * @param  array $columns
181
-     * @throws \Analogue\ORM\Exceptions\EntityNotFoundException
182
-     * @return mixed|self
183
-     */
184
-    public function findOrFail($id, $columns = ['*'])
185
-    {
186
-        if (!is_null($entity = $this->find($id, $columns))) {
187
-            return $entity;
188
-        }
189
-
190
-        throw (new EntityNotFoundException)->setEntity(get_class($this->entityMap));
191
-    }
192
-
193
-
194
-    /**
195
-     * Execute the query and get the first result.
196
-     *
197
-     * @param  array $columns
198
-     * @return \Analogue\ORM\Entity
199
-     */
200
-    public function first($columns = ['*'])
201
-    {
202
-        return $this->take(1)->get($columns)->first();
203
-    }
204
-
205
-    /**
206
-     * Execute the query and get the first result or throw an exception.
207
-     *
208
-     * @param  array $columns
209
-     * @throws EntityNotFoundException
210
-     * @return \Analogue\ORM\Entity
211
-     */
212
-    public function firstOrFail($columns = ['*'])
213
-    {
214
-        if (!is_null($entity = $this->first($columns))) {
215
-            return $entity;
216
-        }
217
-
218
-        throw (new EntityNotFoundException)->setEntity(get_class($this->entityMap));
219
-    }
220
-
221
-    /**
222
-     * Pluck a single column from the database.
223
-     *
224
-     * @param  string $column
225
-     * @return mixed
226
-     */
227
-    public function pluck($column)
228
-    {
229
-        $result = $this->first([$column]);
230
-
231
-        if ($result) {
232
-            return $result->{$column};
233
-        }
234
-    }
235
-
236
-    /**
237
-     * Chunk the results of the query.
238
-     *
239
-     * @param  int      $count
240
-     * @param  callable $callback
241
-     * @return void
242
-     */
243
-    public function chunk($count, callable $callback)
244
-    {
245
-        $results = $this->forPage($page = 1, $count)->get();
246
-
247
-        while (count($results) > 0) {
248
-            // On each chunk result set, we will pass them to the callback and then let the
249
-            // developer take care of everything within the callback, which allows us to
250
-            // keep the memory low for spinning through large result sets for working.
251
-            call_user_func($callback, $results);
252
-
253
-            $page++;
254
-
255
-            $results = $this->forPage($page, $count)->get();
256
-        }
257
-    }
258
-
259
-    /**
260
-     * Get an array with the values of a given column.
261
-     *
262
-     * @param  string $column
263
-     * @param  string $key
264
-     * @return array
265
-     */
266
-    public function lists($column, $key = null)
267
-    {
268
-        return $this->query->pluck($column, $key);
269
-    }
270
-
271
-    /**
272
-     * Get a paginator for the "select" statement.
273
-     *
274
-     * @param  int   $perPage
275
-     * @param  array $columns
276
-     * @return LengthAwarePaginator
277
-     */
278
-    public function paginate($perPage = null, $columns = ['*'])
279
-    {
280
-        $total = $this->query->getCountForPagination();
281
-
282
-        $this->query->forPage(
283
-            $page = Paginator::resolveCurrentPage(),
284
-            $perPage = $perPage ?: $this->entityMap->getPerPage()
285
-        );
286
-
287
-        return new LengthAwarePaginator($this->get($columns)->all(), $total, $perPage, $page, [
288
-            'path' => Paginator::resolveCurrentPath()
289
-        ]);
290
-    }
291
-
292
-    /**
293
-     * Get a paginator for a grouped statement.
294
-     *
295
-     * @param  \Illuminate\Pagination\Factory $paginator
296
-     * @param  int                            $perPage
297
-     * @param  array                          $columns
298
-     * @return \Illuminate\Pagination\Paginator
299
-     */
300
-    protected function groupedPaginate($paginator, $perPage, $columns)
301
-    {
302
-        $results = $this->get($columns)->all();
303
-
304
-        return $this->query->buildRawPaginator($paginator, $results, $perPage);
305
-    }
306
-
307
-    /**
308
-     * Get a paginator for an ungrouped statement.
309
-     *
310
-     * @param  \Illuminate\Pagination\Factory $paginator
311
-     * @param  int                            $perPage
312
-     * @param  array                          $columns
313
-     * @return \Illuminate\Pagination\Paginator
314
-     */
315
-    protected function ungroupedPaginate($paginator, $perPage, $columns)
316
-    {
317
-        $total = $this->query->getPaginationCount();
318
-
319
-        // Once we have the paginator we need to set the limit and offset values for
320
-        // the query so we can get the properly paginated items. Once we have an
321
-        // array of items we can create the paginator instances for the items.
322
-        $page = $paginator->getCurrentPage($total);
323
-
324
-        $this->query->forPage($page, $perPage);
325
-
326
-        return $paginator->make($this->get($columns)->all(), $total, $perPage);
327
-    }
328
-
329
-    /**
330
-     * Paginate the given query into a simple paginator.
331
-     *
332
-     * @param  int   $perPage
333
-     * @param  array $columns
334
-     * @return \Illuminate\Contracts\Pagination\Paginator
335
-     */
336
-    public function simplePaginate($perPage = null, $columns = ['*'])
337
-    {
338
-        $page = Paginator::resolveCurrentPage();
339
-
340
-        $perPage = $perPage ?: $this->entityMap->getPerPage();
341
-
342
-        $this->skip(($page - 1) * $perPage)->take($perPage + 1);
343
-
344
-        return new Paginator($this->get($columns)->all(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()]);
345
-    }
346
-
347
-    /**
348
-     * Add a basic where clause to the query.
349
-     *
350
-     * @param  string $column
351
-     * @param  string $operator
352
-     * @param  mixed  $value
353
-     * @param  string $boolean
354
-     * @return $this
355
-     */
356
-    public function where($column, $operator = null, $value = null, $boolean = 'and')
357
-    {
358
-        if ($column instanceof Closure) {
359
-            $query = $this->newQueryWithoutScopes();
360
-
361
-            call_user_func($column, $query);
362
-
363
-            $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
364
-        } else {
365
-            call_user_func_array([$this->query, 'where'], func_get_args());
366
-        }
367
-
368
-        return $this;
369
-    }
370
-
371
-    /**
372
-     * Add an "or where" clause to the query.
373
-     *
374
-     * @param  string $column
375
-     * @param  string $operator
376
-     * @param  mixed  $value
377
-     * @return \Analogue\ORM\System\Query
378
-     */
379
-    public function orWhere($column, $operator = null, $value = null)
380
-    {
381
-        return $this->where($column, $operator, $value, 'or');
382
-    }
383
-
384
-    /**
385
-     * Add a relationship count condition to the query.
386
-     *
387
-     * @param  string   $relation
388
-     * @param  string   $operator
389
-     * @param  int      $count
390
-     * @param  string   $boolean
391
-     * @param  \Closure $callback
392
-     * @return \Analogue\ORM\System\Query
393
-     */
394
-    public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
395
-    {
396
-        $entity = $this->mapper->newInstance();
397
-
398
-        $relation = $this->getHasRelationQuery($relation, $entity);
399
-
400
-        $query = $relation->getRelationCountQuery($relation->getRelatedMapper()->getQuery(), $this);
401
-
402
-        if ($callback) {
403
-            call_user_func($callback, $query);
404
-        }
405
-
406
-        return $this->addHasWhere($query, $relation, $operator, $count, $boolean);
407
-    }
408
-
409
-    /**
410
-     * Add a relationship count condition to the query with where clauses.
411
-     *
412
-     * @param  string   $relation
413
-     * @param  \Closure $callback
414
-     * @param  string   $operator
415
-     * @param  int      $count
416
-     * @return \Analogue\ORM\System\Query
417
-     */
418
-    public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
419
-    {
420
-        return $this->has($relation, $operator, $count, 'and', $callback);
421
-    }
422
-
423
-    /**
424
-     * Add a relationship count condition to the query with an "or".
425
-     *
426
-     * @param  string $relation
427
-     * @param  string $operator
428
-     * @param  int    $count
429
-     * @return \Analogue\ORM\System\Query
430
-     */
431
-    public function orHas($relation, $operator = '>=', $count = 1)
432
-    {
433
-        return $this->has($relation, $operator, $count, 'or');
434
-    }
435
-
436
-    /**
437
-     * Add a relationship count condition to the query with where clauses and an "or".
438
-     *
439
-     * @param  string   $relation
440
-     * @param  \Closure $callback
441
-     * @param  string   $operator
442
-     * @param  int      $count
443
-     * @return \Analogue\ORM\System\Query
444
-     */
445
-    public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
446
-    {
447
-        return $this->has($relation, $operator, $count, 'or', $callback);
448
-    }
449
-
450
-    /**
451
-     * Add the "has" condition where clause to the query.
452
-     *
453
-     * @param  \Analogue\ORM\System\Query               $hasQuery
454
-     * @param  \Analogue\ORM\Relationships\Relationship $relation
455
-     * @param  string                                   $operator
456
-     * @param  int                                      $count
457
-     * @param  string                                   $boolean
458
-     * @return \Analogue\ORM\System\Query
459
-     */
460
-    protected function addHasWhere(Query $hasQuery, Relationship $relation, $operator, $count, $boolean)
461
-    {
462
-        $this->mergeWheresToHas($hasQuery, $relation);
463
-
464
-        if (is_numeric($count)) {
465
-            $count = new Expression($count);
466
-        }
467
-
468
-        return $this->where(new Expression('(' . $hasQuery->toSql() . ')'), $operator, $count, $boolean);
469
-    }
470
-
471
-    /**
472
-     * Merge the "wheres" from a relation query to a has query.
473
-     *
474
-     * @param  \Analogue\ORM\System\Query               $hasQuery
475
-     * @param  \Analogue\ORM\Relationships\Relationship $relation
476
-     * @return void
477
-     */
478
-    protected function mergeWheresToHas(Query $hasQuery, Relationship $relation)
479
-    {
480
-        // Here we have the "has" query and the original relation. We need to copy over any
481
-        // where clauses the developer may have put in the relationship function over to
482
-        // the has query, and then copy the bindings from the "has" query to the main.
483
-        $relationQuery = $relation->getBaseQuery();
484
-
485
-        $hasQuery->mergeWheres(
486
-            $relationQuery->wheres, $relationQuery->getBindings()
487
-        );
488
-
489
-        $this->query->mergeBindings($hasQuery->getQuery());
490
-    }
491
-
492
-    /**
493
-     * Get the "has relation" base query instance.
494
-     *
495
-     * @param  string $relation
496
-     * @param         $entity
497
-     * @return \Analogue\ORM\System\Query
498
-     */
499
-    protected function getHasRelationQuery($relation, $entity)
500
-    {
501
-        return Relationship::noConstraints(function () use ($relation, $entity) {
502
-            return $this->entityMap->$relation($entity);
503
-        });
504
-    }
505
-
506
-    /**
507
-     * Get the table for the current query object
508
-     *
509
-     * @return string
510
-     */
511
-    public function getTable()
512
-    {
513
-        return $this->entityMap->getTable();
514
-    }
515
-
516
-    /**
517
-     * Set the relationships that should be eager loaded.
518
-     *
519
-     * @param  mixed $relations
520
-     * @return $this
521
-     */
522
-    public function with($relations)
523
-    {
524
-        if (is_string($relations)) {
525
-            $relations = func_get_args();
526
-        }
527
-
528
-        $eagers = $this->parseRelations($relations);
529
-
530
-        $this->eagerLoad = array_merge($this->eagerLoad, $eagers);
531
-
532
-        return $this;
533
-    }
534
-
535
-    /**
536
-     * Parse a list of relations into individuals.
537
-     *
538
-     * @param  array $relations
539
-     * @return array
540
-     */
541
-    protected function parseRelations(array $relations)
542
-    {
543
-        $results = [];
544
-
545
-        foreach ($relations as $name => $constraints) {
546
-            // If the "relation" value is actually a numeric key, we can assume that no
547
-            // constraints have been specified for the eager load and we'll just put
548
-            // an empty Closure with the loader so that we can treat all the same.
549
-            if (is_numeric($name)) {
550
-                $f = function () {};
551
-
552
-                list($name, $constraints) = [$constraints, $f];
553
-            }
554
-
555
-            // We need to separate out any nested includes. Which allows the developers
556
-            // to load deep relationships using "dots" without stating each level of
557
-            // the relationship with its own key in the array of eager load names.
558
-            $results = $this->parseNested($name, $results);
559
-
560
-            $results[$name] = $constraints;
561
-        }
562
-
563
-        return $results;
564
-    }
565
-
566
-
567
-    /**
568
-     * Parse the nested relationships in a relation.
569
-     *
570
-     * @param  string $name
571
-     * @param  array  $results
572
-     * @return array
573
-     */
574
-    protected function parseNested($name, $results)
575
-    {
576
-        $progress = [];
577
-
578
-        // If the relation has already been set on the result array, we will not set it
579
-        // again, since that would override any constraints that were already placed
580
-        // on the relationships. We will only set the ones that are not specified.
581
-        foreach (explode('.', $name) as $segment) {
582
-            $progress[] = $segment;
583
-
584
-            if (!isset($results[$last = implode('.', $progress)])) {
585
-                $results[$last] = function () {};
586
-            }
587
-        }
588
-
589
-        return $results;
590
-    }
591
-
592
-    /**
593
-     * Get the relationships being eagerly loaded.
594
-     *
595
-     * @return array
596
-     */
597
-    public function getEagerLoads()
598
-    {
599
-        return $this->eagerLoad;
600
-    }
601
-
602
-    /**
603
-     * Set the relationships being eagerly loaded.
604
-     *
605
-     * @param  array $eagerLoad
606
-     * @return void
607
-     */
608
-    public function setEagerLoads(array $eagerLoad)
609
-    {
610
-        $this->eagerLoad = $eagerLoad;
611
-    }
612
-
613
-    /**
614
-     * Eager load the relationships for the entities.
615
-     *
616
-     * @param  array $entities
617
-     * @return array
618
-     */
619
-    public function eagerLoadRelations($entities)
620
-    {
621
-        foreach ($this->eagerLoad as $name => $constraints) {
622
-            // For nested eager loads we'll skip loading them here and they will be set as an
623
-            // eager load on the query to retrieve the relation so that they will be eager
624
-            // loaded on that query, because that is where they get hydrated as models.
625
-            if (strpos($name, '.') === false) {
626
-                $entities = $this->loadRelation($entities, $name, $constraints);
627
-            }
628
-        }
629
-
630
-        return $entities;
631
-    }
632
-
633
-    /**
634
-     * Eagerly load the relationship on a set of entities.
635
-     *
636
-     * @param  array    $entities
637
-     * @param  string   $name
638
-     * @param  \Closure $constraints
639
-     * @return array
640
-     */
641
-    protected function loadRelation(array $entities, $name, Closure $constraints)
642
-    {
643
-        // First we will "back up" the existing where conditions on the query so we can
644
-        // add our eager constraints. Then we will merge the wheres that were on the
645
-        // query back to it in order that any where conditions might be specified.
646
-        $relation = $this->getRelation($name);
647
-
648
-        $relation->addEagerConstraints($entities);
649
-
650
-        call_user_func($constraints, $relation);
651
-
652
-        $entities = $relation->initRelation($entities, $name);
653
-
654
-        // Once we have the results, we just match those back up to their parent models
655
-        // using the relationship instance. Then we just return the finished arrays
656
-        // of models which have been eagerly hydrated and are readied for return.
657
-
658
-        $results = $relation->getEager();
659
-
660
-        return $relation->match($entities, $results, $name);
661
-    }
662
-
663
-    /**
664
-     * Get the relation instance for the given relation name.
665
-     *
666
-     * @param  string $relation
667
-     * @return \Analogue\ORM\Relationships\Relationship
668
-     */
669
-    public function getRelation($relation)
670
-    {
671
-        // We want to run a relationship query without any constrains so that we will
672
-        // not have to remove these where clauses manually which gets really hacky
673
-        // and is error prone while we remove the developer's own where clauses.
674
-        $query = Relationship::noConstraints(function () use ($relation) {
675
-            return $this->entityMap->$relation($this->getEntityInstance());
676
-        });
677
-
678
-        $nested = $this->nestedRelations($relation);
679
-
680
-        // If there are nested relationships set on the query, we will put those onto
681
-        // the query instances so that they can be handled after this relationship
682
-        // is loaded. In this way they will all trickle down as they are loaded.
683
-        if (count($nested) > 0) {
684
-            $query->getQuery()->with($nested);
685
-        }
686
-
687
-        return $query;
688
-    }
689
-
690
-    /**
691
-     * Get the deeply nested relations for a given top-level relation.
692
-     *
693
-     * @param  string $relation
694
-     * @return array
695
-     */
696
-    protected function nestedRelations($relation)
697
-    {
698
-        $nested = [];
699
-
700
-        // We are basically looking for any relationships that are nested deeper than
701
-        // the given top-level relationship. We will just check for any relations
702
-        // that start with the given top relations and adds them to our arrays.
703
-        foreach ($this->eagerLoad as $name => $constraints) {
704
-            if ($this->isNested($name, $relation)) {
705
-                $nested[substr($name, strlen($relation . '.'))] = $constraints;
706
-            }
707
-        }
708
-
709
-        return $nested;
710
-    }
711
-
712
-    /**
713
-     * Determine if the relationship is nested.
714
-     *
715
-     * @param  string $name
716
-     * @param  string $relation
717
-     * @return bool
718
-     */
719
-    protected function isNested($name, $relation)
720
-    {
721
-        $dots = str_contains($name, '.');
722
-
723
-        return $dots && starts_with($name, $relation . '.');
724
-    }
725
-
726
-    /**
727
-     * Add the Entity primary key if not in requested columns
728
-     *
729
-     * @param  array $columns
730
-     * @return array
731
-     */
732
-    protected function enforceIdColumn($columns)
733
-    {
734
-        if (!in_array($this->entityMap->getKeyName(), $columns)) {
735
-            $columns[] = $this->entityMap->getKeyName();
736
-        }
737
-        return $columns;
738
-    }
739
-
740
-    /**
741
-     * Get the hydrated models without eager loading.
742
-     *
743
-     * @param  array  $columns
744
-     * @return \Analogue\ORM\EntityCollection
745
-     */
746
-    public function getEntities($columns = ['*'])
747
-    {
748
-        // As we need the primary key to feed the
749
-        // entity cache, we need it loaded on each
750
-        // request
751
-        $columns = $this->enforceIdColumn($columns);
752
-
753
-        // Run the query
754
-        $results = $this->query->get($columns)->toArray();
755
-
756
-        // Create a result builder.
757
-        $builder = new ResultBuilder(Manager::getInstance(), $this->mapper, array_keys($this->getEagerLoads()));
758
-
759
-        return $builder->build($results);
760
-    }
761
-
762
-    /**
763
-     * Get a new instance for the entity
764
-     *
765
-     * @return \Analogue\ORM\Entity
766
-     */
767
-    public function getEntityInstance()
768
-    {
769
-        return $this->mapper->newInstance();
770
-    }
771
-
772
-    /**
773
-     * Extend the builder with a given callback.
774
-     *
775
-     * @param  string   $name
776
-     * @param  \Closure $callback
777
-     * @return void
778
-     */
779
-    public function macro($name, Closure $callback)
780
-    {
781
-        $this->macros[$name] = $callback;
782
-    }
783
-
784
-    /**
785
-     * Get the given macro by name.
786
-     *
787
-     * @param  string $name
788
-     * @return \Closure
789
-     */
790
-    public function getMacro($name)
791
-    {
792
-        return array_get($this->macros, $name);
793
-    }
794
-
795
-    /**
796
-     * Get a new query builder for the model's table.
797
-     *
798
-     * @return \Analogue\ORM\System\Query
799
-     */
800
-    public function newQuery()
801
-    {
802
-        $builder = new Query($this->mapper, $this->adapter);
803
-
804
-        return $this->applyGlobalScopes($builder);
805
-    }
806
-
807
-    /**
808
-     * Get a new query builder without any scope applied.
809
-     *
810
-     * @return \Analogue\ORM\System\Query
811
-     */
812
-    public function newQueryWithoutScopes()
813
-    {
814
-        return new Query($this->mapper, $this->adapter);
815
-    }
816
-
817
-    /**
818
-     * Get the Mapper instance for this Query Builder
819
-     *
820
-     * @return \Analogue\ORM\System\Mapper
821
-     */
822
-    public function getMapper()
823
-    {
824
-        return $this->mapper;
825
-    }
826
-
827
-    /**
828
-     * Get the underlying query adapter
829
-     *
830
-     * (REFACTOR: this method should move out, we need to provide the client classes
831
-     * with the adapter instead.)
832
-     *
833
-     * @return \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
834
-     */
835
-    public function getQuery()
836
-    {
837
-        return $this->query;
838
-    }
839
-
840
-    /**
841
-     * Dynamically handle calls into the query instance.
842
-     *
843
-     * @param  string $method
844
-     * @param  array  $parameters
845
-     * @throws Exception
846
-     * @return mixed
847
-     */
848
-    public function __call($method, $parameters)
849
-    {
850
-        if (isset($this->macros[$method])) {
851
-            array_unshift($parameters, $this);
852
-
853
-            return call_user_func_array($this->macros[$method], $parameters);
854
-        }
855
-
856
-        if (in_array($method, $this->blacklist)) {
857
-            throw new Exception("Method $method doesn't exist");
858
-        }
859
-
860
-        $result = call_user_func_array([$this->query, $method], $parameters);
861
-
862
-        return in_array($method, $this->passthru) ? $result : $this;
863
-    }
22
+	/**
23
+	 * Mapper Instance
24
+	 *
25
+	 * @var \Analogue\ORM\System\Mapper
26
+	 */
27
+	protected $mapper;
28
+
29
+	/**
30
+	 * DB Adatper
31
+	 *
32
+	 * @var \Analogue\ORM\Drivers\DBAdapter
33
+	 */
34
+	protected $adapter;
35
+
36
+	/**
37
+	 * Query Builder Instance
38
+	 *
39
+	 * @var \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
40
+	 */
41
+	protected $query;
42
+
43
+	/**
44
+	 * Entity Map Instance
45
+	 *
46
+	 * @var \Analogue\ORM\EntityMap
47
+	 */
48
+	protected $entityMap;
49
+
50
+	/**
51
+	 * The relationships that should be eager loaded.
52
+	 *
53
+	 * @var array
54
+	 */
55
+	protected $eagerLoad = [];
56
+
57
+	/**
58
+	 * All of the registered builder macros.
59
+	 *
60
+	 * @var array
61
+	 */
62
+	protected $macros = [];
63
+
64
+	/**
65
+	 * The methods that should be returned from query builder.
66
+	 *
67
+	 * @var array
68
+	 */
69
+	protected $passthru = [
70
+		'toSql',
71
+		'lists',
72
+		'pluck',
73
+		'count',
74
+		'min',
75
+		'max',
76
+		'avg',
77
+		'sum',
78
+		'exists',
79
+		'getBindings',
80
+	];
81
+
82
+	/**
83
+	 * Query Builder Blacklist
84
+	 */
85
+	protected $blacklist = [
86
+		'insert',
87
+		'insertGetId',
88
+		'lock',
89
+		'lockForUpdate',
90
+		'sharedLock',
91
+		'update',
92
+		'increment',
93
+		'decrement',
94
+		'delete',
95
+		'truncate',
96
+		'raw',
97
+	];
98
+
99
+	/**
100
+	 * Create a new Analogue Query Builder instance.
101
+	 *
102
+	 * @param  Mapper    $mapper
103
+	 * @param  DBAdapter $adapter
104
+	 */
105
+	public function __construct(Mapper $mapper, DBAdapter $adapter)
106
+	{
107
+		$this->mapper = $mapper;
108
+
109
+		$this->adapter = $adapter;
110
+
111
+		$this->entityMap = $mapper->getEntityMap();
112
+
113
+		// Specify the table to work on
114
+		$this->query = $adapter->getQuery()->from($this->entityMap->getTable());
115
+
116
+		$this->with($this->entityMap->getEagerloadedRelationships());
117
+	}
118
+
119
+	/**
120
+	 * Run the query and return the result
121
+	 *
122
+	 * @param  array $columns
123
+	 * @return \Analogue\ORM\EntityCollection
124
+	 */
125
+	public function get($columns = ['*'])
126
+	{
127
+		$entities = $this->getEntities($columns);
128
+
129
+		// If we actually found models we will also eager load any relationships that
130
+		// have been specified as needing to be eager loaded, which will solve the
131
+		// n+1 query issue for the developers to avoid running a lot of queries.
132
+
133
+		if (count($entities) > 0) {
134
+			$entities = $this->eagerLoadRelations($entities);
135
+		}
136
+
137
+		return $this->entityMap->newCollection($entities);
138
+	}
139
+
140
+	/**
141
+	 * Find an entity by its primary key
142
+	 *
143
+	 * @param  string|integer $id
144
+	 * @param  array          $columns
145
+	 * @return \Analogue\ORM\Mappable
146
+	 */
147
+	public function find($id, $columns = ['*'])
148
+	{
149
+		if (is_array($id)) {
150
+			return $this->findMany($id, $columns);
151
+		}
152
+
153
+		$this->query->where($this->entityMap->getQualifiedKeyName(), '=', $id);
154
+
155
+		return $this->first($columns);
156
+	}
157
+
158
+	/**
159
+	 * Find many entities by their primary keys.
160
+	 *
161
+	 * @param  array $id
162
+	 * @param  array $columns
163
+	 * @return EntityCollection
164
+	 */
165
+	public function findMany($id, $columns = ['*'])
166
+	{
167
+		if (empty($id)) {
168
+			return new EntityCollection;
169
+		}
170
+
171
+		$this->query->whereIn($this->entityMap->getQualifiedKeyName(), $id);
172
+
173
+		return $this->get($columns);
174
+	}
175
+
176
+	/**
177
+	 * Find a model by its primary key or throw an exception.
178
+	 *
179
+	 * @param  mixed $id
180
+	 * @param  array $columns
181
+	 * @throws \Analogue\ORM\Exceptions\EntityNotFoundException
182
+	 * @return mixed|self
183
+	 */
184
+	public function findOrFail($id, $columns = ['*'])
185
+	{
186
+		if (!is_null($entity = $this->find($id, $columns))) {
187
+			return $entity;
188
+		}
189
+
190
+		throw (new EntityNotFoundException)->setEntity(get_class($this->entityMap));
191
+	}
192
+
193
+
194
+	/**
195
+	 * Execute the query and get the first result.
196
+	 *
197
+	 * @param  array $columns
198
+	 * @return \Analogue\ORM\Entity
199
+	 */
200
+	public function first($columns = ['*'])
201
+	{
202
+		return $this->take(1)->get($columns)->first();
203
+	}
204
+
205
+	/**
206
+	 * Execute the query and get the first result or throw an exception.
207
+	 *
208
+	 * @param  array $columns
209
+	 * @throws EntityNotFoundException
210
+	 * @return \Analogue\ORM\Entity
211
+	 */
212
+	public function firstOrFail($columns = ['*'])
213
+	{
214
+		if (!is_null($entity = $this->first($columns))) {
215
+			return $entity;
216
+		}
217
+
218
+		throw (new EntityNotFoundException)->setEntity(get_class($this->entityMap));
219
+	}
220
+
221
+	/**
222
+	 * Pluck a single column from the database.
223
+	 *
224
+	 * @param  string $column
225
+	 * @return mixed
226
+	 */
227
+	public function pluck($column)
228
+	{
229
+		$result = $this->first([$column]);
230
+
231
+		if ($result) {
232
+			return $result->{$column};
233
+		}
234
+	}
235
+
236
+	/**
237
+	 * Chunk the results of the query.
238
+	 *
239
+	 * @param  int      $count
240
+	 * @param  callable $callback
241
+	 * @return void
242
+	 */
243
+	public function chunk($count, callable $callback)
244
+	{
245
+		$results = $this->forPage($page = 1, $count)->get();
246
+
247
+		while (count($results) > 0) {
248
+			// On each chunk result set, we will pass them to the callback and then let the
249
+			// developer take care of everything within the callback, which allows us to
250
+			// keep the memory low for spinning through large result sets for working.
251
+			call_user_func($callback, $results);
252
+
253
+			$page++;
254
+
255
+			$results = $this->forPage($page, $count)->get();
256
+		}
257
+	}
258
+
259
+	/**
260
+	 * Get an array with the values of a given column.
261
+	 *
262
+	 * @param  string $column
263
+	 * @param  string $key
264
+	 * @return array
265
+	 */
266
+	public function lists($column, $key = null)
267
+	{
268
+		return $this->query->pluck($column, $key);
269
+	}
270
+
271
+	/**
272
+	 * Get a paginator for the "select" statement.
273
+	 *
274
+	 * @param  int   $perPage
275
+	 * @param  array $columns
276
+	 * @return LengthAwarePaginator
277
+	 */
278
+	public function paginate($perPage = null, $columns = ['*'])
279
+	{
280
+		$total = $this->query->getCountForPagination();
281
+
282
+		$this->query->forPage(
283
+			$page = Paginator::resolveCurrentPage(),
284
+			$perPage = $perPage ?: $this->entityMap->getPerPage()
285
+		);
286
+
287
+		return new LengthAwarePaginator($this->get($columns)->all(), $total, $perPage, $page, [
288
+			'path' => Paginator::resolveCurrentPath()
289
+		]);
290
+	}
291
+
292
+	/**
293
+	 * Get a paginator for a grouped statement.
294
+	 *
295
+	 * @param  \Illuminate\Pagination\Factory $paginator
296
+	 * @param  int                            $perPage
297
+	 * @param  array                          $columns
298
+	 * @return \Illuminate\Pagination\Paginator
299
+	 */
300
+	protected function groupedPaginate($paginator, $perPage, $columns)
301
+	{
302
+		$results = $this->get($columns)->all();
303
+
304
+		return $this->query->buildRawPaginator($paginator, $results, $perPage);
305
+	}
306
+
307
+	/**
308
+	 * Get a paginator for an ungrouped statement.
309
+	 *
310
+	 * @param  \Illuminate\Pagination\Factory $paginator
311
+	 * @param  int                            $perPage
312
+	 * @param  array                          $columns
313
+	 * @return \Illuminate\Pagination\Paginator
314
+	 */
315
+	protected function ungroupedPaginate($paginator, $perPage, $columns)
316
+	{
317
+		$total = $this->query->getPaginationCount();
318
+
319
+		// Once we have the paginator we need to set the limit and offset values for
320
+		// the query so we can get the properly paginated items. Once we have an
321
+		// array of items we can create the paginator instances for the items.
322
+		$page = $paginator->getCurrentPage($total);
323
+
324
+		$this->query->forPage($page, $perPage);
325
+
326
+		return $paginator->make($this->get($columns)->all(), $total, $perPage);
327
+	}
328
+
329
+	/**
330
+	 * Paginate the given query into a simple paginator.
331
+	 *
332
+	 * @param  int   $perPage
333
+	 * @param  array $columns
334
+	 * @return \Illuminate\Contracts\Pagination\Paginator
335
+	 */
336
+	public function simplePaginate($perPage = null, $columns = ['*'])
337
+	{
338
+		$page = Paginator::resolveCurrentPage();
339
+
340
+		$perPage = $perPage ?: $this->entityMap->getPerPage();
341
+
342
+		$this->skip(($page - 1) * $perPage)->take($perPage + 1);
343
+
344
+		return new Paginator($this->get($columns)->all(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()]);
345
+	}
346
+
347
+	/**
348
+	 * Add a basic where clause to the query.
349
+	 *
350
+	 * @param  string $column
351
+	 * @param  string $operator
352
+	 * @param  mixed  $value
353
+	 * @param  string $boolean
354
+	 * @return $this
355
+	 */
356
+	public function where($column, $operator = null, $value = null, $boolean = 'and')
357
+	{
358
+		if ($column instanceof Closure) {
359
+			$query = $this->newQueryWithoutScopes();
360
+
361
+			call_user_func($column, $query);
362
+
363
+			$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
364
+		} else {
365
+			call_user_func_array([$this->query, 'where'], func_get_args());
366
+		}
367
+
368
+		return $this;
369
+	}
370
+
371
+	/**
372
+	 * Add an "or where" clause to the query.
373
+	 *
374
+	 * @param  string $column
375
+	 * @param  string $operator
376
+	 * @param  mixed  $value
377
+	 * @return \Analogue\ORM\System\Query
378
+	 */
379
+	public function orWhere($column, $operator = null, $value = null)
380
+	{
381
+		return $this->where($column, $operator, $value, 'or');
382
+	}
383
+
384
+	/**
385
+	 * Add a relationship count condition to the query.
386
+	 *
387
+	 * @param  string   $relation
388
+	 * @param  string   $operator
389
+	 * @param  int      $count
390
+	 * @param  string   $boolean
391
+	 * @param  \Closure $callback
392
+	 * @return \Analogue\ORM\System\Query
393
+	 */
394
+	public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
395
+	{
396
+		$entity = $this->mapper->newInstance();
397
+
398
+		$relation = $this->getHasRelationQuery($relation, $entity);
399
+
400
+		$query = $relation->getRelationCountQuery($relation->getRelatedMapper()->getQuery(), $this);
401
+
402
+		if ($callback) {
403
+			call_user_func($callback, $query);
404
+		}
405
+
406
+		return $this->addHasWhere($query, $relation, $operator, $count, $boolean);
407
+	}
408
+
409
+	/**
410
+	 * Add a relationship count condition to the query with where clauses.
411
+	 *
412
+	 * @param  string   $relation
413
+	 * @param  \Closure $callback
414
+	 * @param  string   $operator
415
+	 * @param  int      $count
416
+	 * @return \Analogue\ORM\System\Query
417
+	 */
418
+	public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
419
+	{
420
+		return $this->has($relation, $operator, $count, 'and', $callback);
421
+	}
422
+
423
+	/**
424
+	 * Add a relationship count condition to the query with an "or".
425
+	 *
426
+	 * @param  string $relation
427
+	 * @param  string $operator
428
+	 * @param  int    $count
429
+	 * @return \Analogue\ORM\System\Query
430
+	 */
431
+	public function orHas($relation, $operator = '>=', $count = 1)
432
+	{
433
+		return $this->has($relation, $operator, $count, 'or');
434
+	}
435
+
436
+	/**
437
+	 * Add a relationship count condition to the query with where clauses and an "or".
438
+	 *
439
+	 * @param  string   $relation
440
+	 * @param  \Closure $callback
441
+	 * @param  string   $operator
442
+	 * @param  int      $count
443
+	 * @return \Analogue\ORM\System\Query
444
+	 */
445
+	public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
446
+	{
447
+		return $this->has($relation, $operator, $count, 'or', $callback);
448
+	}
449
+
450
+	/**
451
+	 * Add the "has" condition where clause to the query.
452
+	 *
453
+	 * @param  \Analogue\ORM\System\Query               $hasQuery
454
+	 * @param  \Analogue\ORM\Relationships\Relationship $relation
455
+	 * @param  string                                   $operator
456
+	 * @param  int                                      $count
457
+	 * @param  string                                   $boolean
458
+	 * @return \Analogue\ORM\System\Query
459
+	 */
460
+	protected function addHasWhere(Query $hasQuery, Relationship $relation, $operator, $count, $boolean)
461
+	{
462
+		$this->mergeWheresToHas($hasQuery, $relation);
463
+
464
+		if (is_numeric($count)) {
465
+			$count = new Expression($count);
466
+		}
467
+
468
+		return $this->where(new Expression('(' . $hasQuery->toSql() . ')'), $operator, $count, $boolean);
469
+	}
470
+
471
+	/**
472
+	 * Merge the "wheres" from a relation query to a has query.
473
+	 *
474
+	 * @param  \Analogue\ORM\System\Query               $hasQuery
475
+	 * @param  \Analogue\ORM\Relationships\Relationship $relation
476
+	 * @return void
477
+	 */
478
+	protected function mergeWheresToHas(Query $hasQuery, Relationship $relation)
479
+	{
480
+		// Here we have the "has" query and the original relation. We need to copy over any
481
+		// where clauses the developer may have put in the relationship function over to
482
+		// the has query, and then copy the bindings from the "has" query to the main.
483
+		$relationQuery = $relation->getBaseQuery();
484
+
485
+		$hasQuery->mergeWheres(
486
+			$relationQuery->wheres, $relationQuery->getBindings()
487
+		);
488
+
489
+		$this->query->mergeBindings($hasQuery->getQuery());
490
+	}
491
+
492
+	/**
493
+	 * Get the "has relation" base query instance.
494
+	 *
495
+	 * @param  string $relation
496
+	 * @param         $entity
497
+	 * @return \Analogue\ORM\System\Query
498
+	 */
499
+	protected function getHasRelationQuery($relation, $entity)
500
+	{
501
+		return Relationship::noConstraints(function () use ($relation, $entity) {
502
+			return $this->entityMap->$relation($entity);
503
+		});
504
+	}
505
+
506
+	/**
507
+	 * Get the table for the current query object
508
+	 *
509
+	 * @return string
510
+	 */
511
+	public function getTable()
512
+	{
513
+		return $this->entityMap->getTable();
514
+	}
515
+
516
+	/**
517
+	 * Set the relationships that should be eager loaded.
518
+	 *
519
+	 * @param  mixed $relations
520
+	 * @return $this
521
+	 */
522
+	public function with($relations)
523
+	{
524
+		if (is_string($relations)) {
525
+			$relations = func_get_args();
526
+		}
527
+
528
+		$eagers = $this->parseRelations($relations);
529
+
530
+		$this->eagerLoad = array_merge($this->eagerLoad, $eagers);
531
+
532
+		return $this;
533
+	}
534
+
535
+	/**
536
+	 * Parse a list of relations into individuals.
537
+	 *
538
+	 * @param  array $relations
539
+	 * @return array
540
+	 */
541
+	protected function parseRelations(array $relations)
542
+	{
543
+		$results = [];
544
+
545
+		foreach ($relations as $name => $constraints) {
546
+			// If the "relation" value is actually a numeric key, we can assume that no
547
+			// constraints have been specified for the eager load and we'll just put
548
+			// an empty Closure with the loader so that we can treat all the same.
549
+			if (is_numeric($name)) {
550
+				$f = function () {};
551
+
552
+				list($name, $constraints) = [$constraints, $f];
553
+			}
554
+
555
+			// We need to separate out any nested includes. Which allows the developers
556
+			// to load deep relationships using "dots" without stating each level of
557
+			// the relationship with its own key in the array of eager load names.
558
+			$results = $this->parseNested($name, $results);
559
+
560
+			$results[$name] = $constraints;
561
+		}
562
+
563
+		return $results;
564
+	}
565
+
566
+
567
+	/**
568
+	 * Parse the nested relationships in a relation.
569
+	 *
570
+	 * @param  string $name
571
+	 * @param  array  $results
572
+	 * @return array
573
+	 */
574
+	protected function parseNested($name, $results)
575
+	{
576
+		$progress = [];
577
+
578
+		// If the relation has already been set on the result array, we will not set it
579
+		// again, since that would override any constraints that were already placed
580
+		// on the relationships. We will only set the ones that are not specified.
581
+		foreach (explode('.', $name) as $segment) {
582
+			$progress[] = $segment;
583
+
584
+			if (!isset($results[$last = implode('.', $progress)])) {
585
+				$results[$last] = function () {};
586
+			}
587
+		}
588
+
589
+		return $results;
590
+	}
591
+
592
+	/**
593
+	 * Get the relationships being eagerly loaded.
594
+	 *
595
+	 * @return array
596
+	 */
597
+	public function getEagerLoads()
598
+	{
599
+		return $this->eagerLoad;
600
+	}
601
+
602
+	/**
603
+	 * Set the relationships being eagerly loaded.
604
+	 *
605
+	 * @param  array $eagerLoad
606
+	 * @return void
607
+	 */
608
+	public function setEagerLoads(array $eagerLoad)
609
+	{
610
+		$this->eagerLoad = $eagerLoad;
611
+	}
612
+
613
+	/**
614
+	 * Eager load the relationships for the entities.
615
+	 *
616
+	 * @param  array $entities
617
+	 * @return array
618
+	 */
619
+	public function eagerLoadRelations($entities)
620
+	{
621
+		foreach ($this->eagerLoad as $name => $constraints) {
622
+			// For nested eager loads we'll skip loading them here and they will be set as an
623
+			// eager load on the query to retrieve the relation so that they will be eager
624
+			// loaded on that query, because that is where they get hydrated as models.
625
+			if (strpos($name, '.') === false) {
626
+				$entities = $this->loadRelation($entities, $name, $constraints);
627
+			}
628
+		}
629
+
630
+		return $entities;
631
+	}
632
+
633
+	/**
634
+	 * Eagerly load the relationship on a set of entities.
635
+	 *
636
+	 * @param  array    $entities
637
+	 * @param  string   $name
638
+	 * @param  \Closure $constraints
639
+	 * @return array
640
+	 */
641
+	protected function loadRelation(array $entities, $name, Closure $constraints)
642
+	{
643
+		// First we will "back up" the existing where conditions on the query so we can
644
+		// add our eager constraints. Then we will merge the wheres that were on the
645
+		// query back to it in order that any where conditions might be specified.
646
+		$relation = $this->getRelation($name);
647
+
648
+		$relation->addEagerConstraints($entities);
649
+
650
+		call_user_func($constraints, $relation);
651
+
652
+		$entities = $relation->initRelation($entities, $name);
653
+
654
+		// Once we have the results, we just match those back up to their parent models
655
+		// using the relationship instance. Then we just return the finished arrays
656
+		// of models which have been eagerly hydrated and are readied for return.
657
+
658
+		$results = $relation->getEager();
659
+
660
+		return $relation->match($entities, $results, $name);
661
+	}
662
+
663
+	/**
664
+	 * Get the relation instance for the given relation name.
665
+	 *
666
+	 * @param  string $relation
667
+	 * @return \Analogue\ORM\Relationships\Relationship
668
+	 */
669
+	public function getRelation($relation)
670
+	{
671
+		// We want to run a relationship query without any constrains so that we will
672
+		// not have to remove these where clauses manually which gets really hacky
673
+		// and is error prone while we remove the developer's own where clauses.
674
+		$query = Relationship::noConstraints(function () use ($relation) {
675
+			return $this->entityMap->$relation($this->getEntityInstance());
676
+		});
677
+
678
+		$nested = $this->nestedRelations($relation);
679
+
680
+		// If there are nested relationships set on the query, we will put those onto
681
+		// the query instances so that they can be handled after this relationship
682
+		// is loaded. In this way they will all trickle down as they are loaded.
683
+		if (count($nested) > 0) {
684
+			$query->getQuery()->with($nested);
685
+		}
686
+
687
+		return $query;
688
+	}
689
+
690
+	/**
691
+	 * Get the deeply nested relations for a given top-level relation.
692
+	 *
693
+	 * @param  string $relation
694
+	 * @return array
695
+	 */
696
+	protected function nestedRelations($relation)
697
+	{
698
+		$nested = [];
699
+
700
+		// We are basically looking for any relationships that are nested deeper than
701
+		// the given top-level relationship. We will just check for any relations
702
+		// that start with the given top relations and adds them to our arrays.
703
+		foreach ($this->eagerLoad as $name => $constraints) {
704
+			if ($this->isNested($name, $relation)) {
705
+				$nested[substr($name, strlen($relation . '.'))] = $constraints;
706
+			}
707
+		}
708
+
709
+		return $nested;
710
+	}
711
+
712
+	/**
713
+	 * Determine if the relationship is nested.
714
+	 *
715
+	 * @param  string $name
716
+	 * @param  string $relation
717
+	 * @return bool
718
+	 */
719
+	protected function isNested($name, $relation)
720
+	{
721
+		$dots = str_contains($name, '.');
722
+
723
+		return $dots && starts_with($name, $relation . '.');
724
+	}
725
+
726
+	/**
727
+	 * Add the Entity primary key if not in requested columns
728
+	 *
729
+	 * @param  array $columns
730
+	 * @return array
731
+	 */
732
+	protected function enforceIdColumn($columns)
733
+	{
734
+		if (!in_array($this->entityMap->getKeyName(), $columns)) {
735
+			$columns[] = $this->entityMap->getKeyName();
736
+		}
737
+		return $columns;
738
+	}
739
+
740
+	/**
741
+	 * Get the hydrated models without eager loading.
742
+	 *
743
+	 * @param  array  $columns
744
+	 * @return \Analogue\ORM\EntityCollection
745
+	 */
746
+	public function getEntities($columns = ['*'])
747
+	{
748
+		// As we need the primary key to feed the
749
+		// entity cache, we need it loaded on each
750
+		// request
751
+		$columns = $this->enforceIdColumn($columns);
752
+
753
+		// Run the query
754
+		$results = $this->query->get($columns)->toArray();
755
+
756
+		// Create a result builder.
757
+		$builder = new ResultBuilder(Manager::getInstance(), $this->mapper, array_keys($this->getEagerLoads()));
758
+
759
+		return $builder->build($results);
760
+	}
761
+
762
+	/**
763
+	 * Get a new instance for the entity
764
+	 *
765
+	 * @return \Analogue\ORM\Entity
766
+	 */
767
+	public function getEntityInstance()
768
+	{
769
+		return $this->mapper->newInstance();
770
+	}
771
+
772
+	/**
773
+	 * Extend the builder with a given callback.
774
+	 *
775
+	 * @param  string   $name
776
+	 * @param  \Closure $callback
777
+	 * @return void
778
+	 */
779
+	public function macro($name, Closure $callback)
780
+	{
781
+		$this->macros[$name] = $callback;
782
+	}
783
+
784
+	/**
785
+	 * Get the given macro by name.
786
+	 *
787
+	 * @param  string $name
788
+	 * @return \Closure
789
+	 */
790
+	public function getMacro($name)
791
+	{
792
+		return array_get($this->macros, $name);
793
+	}
794
+
795
+	/**
796
+	 * Get a new query builder for the model's table.
797
+	 *
798
+	 * @return \Analogue\ORM\System\Query
799
+	 */
800
+	public function newQuery()
801
+	{
802
+		$builder = new Query($this->mapper, $this->adapter);
803
+
804
+		return $this->applyGlobalScopes($builder);
805
+	}
806
+
807
+	/**
808
+	 * Get a new query builder without any scope applied.
809
+	 *
810
+	 * @return \Analogue\ORM\System\Query
811
+	 */
812
+	public function newQueryWithoutScopes()
813
+	{
814
+		return new Query($this->mapper, $this->adapter);
815
+	}
816
+
817
+	/**
818
+	 * Get the Mapper instance for this Query Builder
819
+	 *
820
+	 * @return \Analogue\ORM\System\Mapper
821
+	 */
822
+	public function getMapper()
823
+	{
824
+		return $this->mapper;
825
+	}
826
+
827
+	/**
828
+	 * Get the underlying query adapter
829
+	 *
830
+	 * (REFACTOR: this method should move out, we need to provide the client classes
831
+	 * with the adapter instead.)
832
+	 *
833
+	 * @return \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
834
+	 */
835
+	public function getQuery()
836
+	{
837
+		return $this->query;
838
+	}
839
+
840
+	/**
841
+	 * Dynamically handle calls into the query instance.
842
+	 *
843
+	 * @param  string $method
844
+	 * @param  array  $parameters
845
+	 * @throws Exception
846
+	 * @return mixed
847
+	 */
848
+	public function __call($method, $parameters)
849
+	{
850
+		if (isset($this->macros[$method])) {
851
+			array_unshift($parameters, $this);
852
+
853
+			return call_user_func_array($this->macros[$method], $parameters);
854
+		}
855
+
856
+		if (in_array($method, $this->blacklist)) {
857
+			throw new Exception("Method $method doesn't exist");
858
+		}
859
+
860
+		$result = call_user_func_array([$this->query, $method], $parameters);
861
+
862
+		return in_array($method, $this->passthru) ? $result : $this;
863
+	}
864 864
 }
865 865
\ No newline at end of file
Please login to merge, or discard this patch.
src/Exceptions/EntityMapNotFoundException.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -6,5 +6,5 @@
 block discarded – undo
6 6
 
7 7
 class EntityMapNotFoundException extends RuntimeException
8 8
 {
9
-    //
9
+	//
10 10
 }
Please login to merge, or discard this patch.
src/helpers.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -2,7 +2,7 @@  discard block
 block discarded – undo
2 2
 
3 3
 use Analogue\ORM\System\Manager;
4 4
 
5
-if (! function_exists('analogue')) {
5
+if (!function_exists('analogue')) {
6 6
 
7 7
     /**
8 8
      * Return analogue's manager instance
@@ -17,7 +17,7 @@  discard block
 block discarded – undo
17 17
 }
18 18
 
19 19
 
20
-if (! function_exists('mapper')) {
20
+if (!function_exists('mapper')) {
21 21
 
22 22
     /**
23 23
      * Create a mapper for a given entity (static alias)
Please login to merge, or discard this patch.
Indentation   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -4,29 +4,29 @@
 block discarded – undo
4 4
 
5 5
 if (! function_exists('analogue')) {
6 6
 
7
-    /**
8
-     * Return analogue's manager instance
9
-     * 
10
-     * @return \Analogue\ORM\System\Manager
11
-     */
12
-    function analogue()
13
-    {
14
-        return Manager::getInstance();
15
-    }
7
+	/**
8
+	 * Return analogue's manager instance
9
+	 * 
10
+	 * @return \Analogue\ORM\System\Manager
11
+	 */
12
+	function analogue()
13
+	{
14
+		return Manager::getInstance();
15
+	}
16 16
 }
17 17
 
18 18
 
19 19
 if (! function_exists('mapper')) {
20 20
 
21
-    /**
22
-     * Create a mapper for a given entity (static alias)
23
-     * 
24
-     * @param \Analogue\ORM\Mappable|string $entity
25
-     * @param mixed $entityMap 
26
-     * @return Mapper
27
-     */
28
-    function mapper($entity, $entityMap = null)
29
-    {
30
-        return Manager::getMapper($entity, $entityMap);
31
-    }
21
+	/**
22
+	 * Create a mapper for a given entity (static alias)
23
+	 * 
24
+	 * @param \Analogue\ORM\Mappable|string $entity
25
+	 * @param mixed $entityMap 
26
+	 * @return Mapper
27
+	 */
28
+	function mapper($entity, $entityMap = null)
29
+	{
30
+		return Manager::getMapper($entity, $entityMap);
31
+	}
32 32
 }
Please login to merge, or discard this patch.