Completed
Branch master (5737c6)
by Rémi
02:51
created
src/Relationships/MorphTo.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -163,7 +163,7 @@
 block discarded – undo
163 163
     {
164 164
         $foreign = $this->foreignKey;
165 165
 
166
-        return BaseCollection::make($this->dictionary[$type])->map(function ($entities) use ($foreign) {
166
+        return BaseCollection::make($this->dictionary[$type])->map(function($entities) use ($foreign) {
167 167
             return head($entities)->{$foreign};
168 168
 
169 169
         })->unique();
Please login to merge, or discard this patch.
Indentation   +222 added lines, -222 removed lines patch added patch discarded remove patch
@@ -8,226 +8,226 @@
 block discarded – undo
8 8
 
9 9
 class MorphTo extends BelongsTo
10 10
 {
11
-    /**
12
-     * The type of the polymorphic relation.
13
-     *
14
-     * @var string
15
-     */
16
-    protected $morphType;
17
-
18
-    /**
19
-     * The entities whose relations are being eager loaded.
20
-     *
21
-     * @var EntityCollection
22
-     */
23
-    protected $entities;
24
-
25
-    /**
26
-     * All of the result sets keyed by ID.
27
-     *
28
-     * @var array
29
-     */
30
-    protected $dictionary = [];
31
-
32
-    /**
33
-     * Indicates if soft-deleted model instances should be fetched.
34
-     *
35
-     * @var bool
36
-     */
37
-    protected $withTrashed = false;
38
-
39
-    /**
40
-     * Indicate if the parent entity hold the key for the relation.
41
-     *
42
-     * @var bool
43
-     */
44
-    protected static $ownForeignKey = true;
45
-
46
-    /**
47
-     * Create a new belongs to relationship instance.
48
-     *
49
-     * @param Mapper                 $mapper
50
-     * @param \Analogue\ORM\Mappable $parent
51
-     * @param string                 $foreignKey
52
-     * @param string                 $otherKey
53
-     * @param string                 $type
54
-     * @param string                 $relation
55
-     */
56
-    public function __construct(Mapper $mapper, $parent, $foreignKey, $otherKey, $type, $relation)
57
-    {
58
-        $this->morphType = $type;
59
-
60
-        parent::__construct($mapper, $parent, $foreignKey, $otherKey, $relation);
61
-    }
62
-
63
-    /**
64
-     * Set the constraints for an eager load of the relation.
65
-     *
66
-     * @param array $results
67
-     *
68
-     * @return void
69
-     */
70
-    public function addEagerConstraints(array $results)
71
-    {
72
-        $this->buildDictionary($results);
73
-    }
74
-
75
-    /**
76
-     * Build a dictionary with the entities.
77
-     *
78
-     * @param array $results
79
-     *
80
-     * @return void
81
-     */
82
-    protected function buildDictionary($results)
83
-    {
84
-        foreach ($results as $result) {
85
-            if ($result[$this->morphType]) {
86
-                $this->dictionary[$result[$this->morphType]][$result[$this->foreignKey]] = $result;
87
-            }
88
-        }
89
-    }
90
-
91
-    /**
92
-     * Match the eagerly loaded results to their parents.
93
-     *
94
-     * @param array  $results
95
-     * @param string $relation
96
-     *
97
-     * @return array
98
-     */
99
-    public function match(array $results, $relation)
100
-    {
101
-        return $entities;
102
-    }
103
-
104
-    /**
105
-     * Get the results of the relationship.
106
-     *
107
-     * @throws \Analogue\ORM\Exceptions\MappingException
108
-     *
109
-     * @return EntityCollection
110
-     */
111
-    public function getEager()
112
-    {
113
-        foreach (array_keys($this->dictionary) as $type) {
114
-            $this->matchToMorphParents($type, $this->getResultsByType($type));
115
-        }
116
-
117
-        return $this->entities;
118
-    }
119
-
120
-    /**
121
-     * Match the results for a given type to their parents.
122
-     *
123
-     * @param string           $type
124
-     * @param EntityCollection $results
125
-     *
126
-     * @return void
127
-     */
128
-    protected function matchToMorphParents($type, EntityCollection $results)
129
-    {
130
-        $mapper = $this->relatedMapper->getManager()->mapper($type);
131
-        $keyName = $mapper->getEntityMap()->getKeyName();
132
-
133
-        foreach ($results as $result) {
134
-            $key = $result[$keyName];
135
-
136
-            if (isset($this->dictionary[$type][$key])) {
137
-                foreach ($this->dictionary[$type][$key] as $result) {
138
-                    $result[$this->relation] = $result;
139
-                }
140
-            }
141
-        }
142
-    }
143
-
144
-    /**
145
-     * Get all of the relation results for a type.
146
-     *
147
-     * @param string $type
148
-     *
149
-     * @throws \Analogue\ORM\Exceptions\MappingException
150
-     *
151
-     * @return EntityCollection
152
-     */
153
-    protected function getResultsByType($type)
154
-    {
155
-        $mapper = $this->relatedMapper->getManager()->mapper($type);
156
-
157
-        $key = $mapper->getEntityMap()->getKeyName();
158
-
159
-        $query = $mapper->getQuery();
160
-
161
-        return $query->whereIn($key, $this->gatherKeysByType($type)->all())->get();
162
-    }
163
-
164
-    /**
165
-     * Gather all of the foreign keys for a given type.
166
-     *
167
-     * @param string $type
168
-     *
169
-     * @return BaseCollection
170
-     */
171
-    protected function gatherKeysByType($type)
172
-    {
173
-        $foreign = $this->foreignKey;
174
-
175
-        return BaseCollection::make($this->dictionary[$type])->map(function ($entities) use ($foreign) {
176
-            return head($entities)->{$foreign};
177
-        })->unique();
178
-    }
179
-
180
-    /**
181
-     * Associate the model instance to the given parent.
182
-     *
183
-     * @param mixed $entity
184
-     *
185
-     * @return void
186
-     */
187
-    public function associate($entity)
188
-    {
189
-        // The Mapper will retrieve this association within the object model, we won't be using
190
-        // the foreign key attribute inside the parent Entity.
191
-        //
192
-        //$this->parent->setEntityAttribute($this->foreignKey, $entity->getEntityAttribute($this->otherKey));
193
-        //
194
-        // Instead, we'll just add the object to the Entity's attribute
195
-
196
-        $this->parent->setEntityAttribute($this->relation, $entity->getEntityObject());
197
-    }
198
-
199
-    /**
200
-     * Get the foreign key value pair for a related object.
201
-     *
202
-     * @var mixed
203
-     *
204
-     * @return array
205
-     */
206
-    public function getForeignKeyValuePair($related)
207
-    {
208
-        $foreignKey = $this->getForeignKey();
209
-
210
-        if ($related) {
211
-            $wrapper = $this->factory->make($related);
212
-
213
-            $relatedKey = $this->relatedMap->getKeyName();
214
-
215
-            return [
216
-                $foreignKey      => $wrapper->getEntityAttribute($relatedKey),
217
-                $this->morphType => $wrapper->getMap()->getMorphClass(),
218
-            ];
219
-        } else {
220
-            return [$foreignKey => null];
221
-        }
222
-    }
223
-
224
-    /**
225
-     * Get the dictionary used by the relationship.
226
-     *
227
-     * @return array
228
-     */
229
-    public function getDictionary()
230
-    {
231
-        return $this->dictionary;
232
-    }
11
+	/**
12
+	 * The type of the polymorphic relation.
13
+	 *
14
+	 * @var string
15
+	 */
16
+	protected $morphType;
17
+
18
+	/**
19
+	 * The entities whose relations are being eager loaded.
20
+	 *
21
+	 * @var EntityCollection
22
+	 */
23
+	protected $entities;
24
+
25
+	/**
26
+	 * All of the result sets keyed by ID.
27
+	 *
28
+	 * @var array
29
+	 */
30
+	protected $dictionary = [];
31
+
32
+	/**
33
+	 * Indicates if soft-deleted model instances should be fetched.
34
+	 *
35
+	 * @var bool
36
+	 */
37
+	protected $withTrashed = false;
38
+
39
+	/**
40
+	 * Indicate if the parent entity hold the key for the relation.
41
+	 *
42
+	 * @var bool
43
+	 */
44
+	protected static $ownForeignKey = true;
45
+
46
+	/**
47
+	 * Create a new belongs to relationship instance.
48
+	 *
49
+	 * @param Mapper                 $mapper
50
+	 * @param \Analogue\ORM\Mappable $parent
51
+	 * @param string                 $foreignKey
52
+	 * @param string                 $otherKey
53
+	 * @param string                 $type
54
+	 * @param string                 $relation
55
+	 */
56
+	public function __construct(Mapper $mapper, $parent, $foreignKey, $otherKey, $type, $relation)
57
+	{
58
+		$this->morphType = $type;
59
+
60
+		parent::__construct($mapper, $parent, $foreignKey, $otherKey, $relation);
61
+	}
62
+
63
+	/**
64
+	 * Set the constraints for an eager load of the relation.
65
+	 *
66
+	 * @param array $results
67
+	 *
68
+	 * @return void
69
+	 */
70
+	public function addEagerConstraints(array $results)
71
+	{
72
+		$this->buildDictionary($results);
73
+	}
74
+
75
+	/**
76
+	 * Build a dictionary with the entities.
77
+	 *
78
+	 * @param array $results
79
+	 *
80
+	 * @return void
81
+	 */
82
+	protected function buildDictionary($results)
83
+	{
84
+		foreach ($results as $result) {
85
+			if ($result[$this->morphType]) {
86
+				$this->dictionary[$result[$this->morphType]][$result[$this->foreignKey]] = $result;
87
+			}
88
+		}
89
+	}
90
+
91
+	/**
92
+	 * Match the eagerly loaded results to their parents.
93
+	 *
94
+	 * @param array  $results
95
+	 * @param string $relation
96
+	 *
97
+	 * @return array
98
+	 */
99
+	public function match(array $results, $relation)
100
+	{
101
+		return $entities;
102
+	}
103
+
104
+	/**
105
+	 * Get the results of the relationship.
106
+	 *
107
+	 * @throws \Analogue\ORM\Exceptions\MappingException
108
+	 *
109
+	 * @return EntityCollection
110
+	 */
111
+	public function getEager()
112
+	{
113
+		foreach (array_keys($this->dictionary) as $type) {
114
+			$this->matchToMorphParents($type, $this->getResultsByType($type));
115
+		}
116
+
117
+		return $this->entities;
118
+	}
119
+
120
+	/**
121
+	 * Match the results for a given type to their parents.
122
+	 *
123
+	 * @param string           $type
124
+	 * @param EntityCollection $results
125
+	 *
126
+	 * @return void
127
+	 */
128
+	protected function matchToMorphParents($type, EntityCollection $results)
129
+	{
130
+		$mapper = $this->relatedMapper->getManager()->mapper($type);
131
+		$keyName = $mapper->getEntityMap()->getKeyName();
132
+
133
+		foreach ($results as $result) {
134
+			$key = $result[$keyName];
135
+
136
+			if (isset($this->dictionary[$type][$key])) {
137
+				foreach ($this->dictionary[$type][$key] as $result) {
138
+					$result[$this->relation] = $result;
139
+				}
140
+			}
141
+		}
142
+	}
143
+
144
+	/**
145
+	 * Get all of the relation results for a type.
146
+	 *
147
+	 * @param string $type
148
+	 *
149
+	 * @throws \Analogue\ORM\Exceptions\MappingException
150
+	 *
151
+	 * @return EntityCollection
152
+	 */
153
+	protected function getResultsByType($type)
154
+	{
155
+		$mapper = $this->relatedMapper->getManager()->mapper($type);
156
+
157
+		$key = $mapper->getEntityMap()->getKeyName();
158
+
159
+		$query = $mapper->getQuery();
160
+
161
+		return $query->whereIn($key, $this->gatherKeysByType($type)->all())->get();
162
+	}
163
+
164
+	/**
165
+	 * Gather all of the foreign keys for a given type.
166
+	 *
167
+	 * @param string $type
168
+	 *
169
+	 * @return BaseCollection
170
+	 */
171
+	protected function gatherKeysByType($type)
172
+	{
173
+		$foreign = $this->foreignKey;
174
+
175
+		return BaseCollection::make($this->dictionary[$type])->map(function ($entities) use ($foreign) {
176
+			return head($entities)->{$foreign};
177
+		})->unique();
178
+	}
179
+
180
+	/**
181
+	 * Associate the model instance to the given parent.
182
+	 *
183
+	 * @param mixed $entity
184
+	 *
185
+	 * @return void
186
+	 */
187
+	public function associate($entity)
188
+	{
189
+		// The Mapper will retrieve this association within the object model, we won't be using
190
+		// the foreign key attribute inside the parent Entity.
191
+		//
192
+		//$this->parent->setEntityAttribute($this->foreignKey, $entity->getEntityAttribute($this->otherKey));
193
+		//
194
+		// Instead, we'll just add the object to the Entity's attribute
195
+
196
+		$this->parent->setEntityAttribute($this->relation, $entity->getEntityObject());
197
+	}
198
+
199
+	/**
200
+	 * Get the foreign key value pair for a related object.
201
+	 *
202
+	 * @var mixed
203
+	 *
204
+	 * @return array
205
+	 */
206
+	public function getForeignKeyValuePair($related)
207
+	{
208
+		$foreignKey = $this->getForeignKey();
209
+
210
+		if ($related) {
211
+			$wrapper = $this->factory->make($related);
212
+
213
+			$relatedKey = $this->relatedMap->getKeyName();
214
+
215
+			return [
216
+				$foreignKey      => $wrapper->getEntityAttribute($relatedKey),
217
+				$this->morphType => $wrapper->getMap()->getMorphClass(),
218
+			];
219
+		} else {
220
+			return [$foreignKey => null];
221
+		}
222
+	}
223
+
224
+	/**
225
+	 * Get the dictionary used by the relationship.
226
+	 *
227
+	 * @return array
228
+	 */
229
+	public function getDictionary()
230
+	{
231
+		return $this->dictionary;
232
+	}
233 233
 }
Please login to merge, or discard this patch.
src/AnalogueFacade.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -8,13 +8,13 @@
 block discarded – undo
8 8
 class AnalogueFacade extends Facade
9 9
 {
10 10
 
11
-    /**
12
-     * Get the registered name of the component.
13
-     *
14
-     * @return string
15
-     */
16
-    protected static function getFacadeAccessor()
17
-    {
18
-        return 'analogue';
19
-    }
11
+	/**
12
+	 * Get the registered name of the component.
13
+	 *
14
+	 * @return string
15
+	 */
16
+	protected static function getFacadeAccessor()
17
+	{
18
+		return 'analogue';
19
+	}
20 20
 }
Please login to merge, or discard this patch.
src/Plugins/SoftDeletes/SoftDeletingScope.php 3 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -73,7 +73,7 @@  discard block
 block discarded – undo
73 73
      */
74 74
     protected function addWithTrashed(Query $query)
75 75
     {
76
-        $query->macro('withTrashed', function (Query $query) {
76
+        $query->macro('withTrashed', function(Query $query) {
77 77
             $this->remove($query);
78 78
 
79 79
             return $query;
@@ -88,7 +88,7 @@  discard block
 block discarded – undo
88 88
      */
89 89
     protected function addOnlyTrashed(Query $query)
90 90
     {
91
-        $query->macro('onlyTrashed', function (Query $query) {
91
+        $query->macro('onlyTrashed', function(Query $query) {
92 92
             $this->remove($query);
93 93
 
94 94
             $query->getQuery()->whereNotNull($query->getMapper()->getEntityMap()->getQualifiedDeletedAtColumn());
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -33,7 +33,7 @@
 block discarded – undo
33 33
     /**
34 34
      * Remove the scope from the given Analogue query builder.
35 35
      *
36
-     * @param mixed $query
36
+     * @param Query $query
37 37
      *
38 38
      * @return void
39 39
      */
Please login to merge, or discard this patch.
Indentation   +108 added lines, -108 removed lines patch added patch discarded remove patch
@@ -7,112 +7,112 @@
 block discarded – undo
7 7
 
8 8
 class SoftDeletingScope implements ScopeInterface
9 9
 {
10
-    /**
11
-     * All of the extensions to be added to the builder.
12
-     *
13
-     * @var array
14
-     */
15
-    protected $extensions = ['WithTrashed', 'OnlyTrashed'];
16
-
17
-    /**
18
-     * Apply the scope to a given Analogue query builder.
19
-     *
20
-     * @param \Analogue\ORM\System\Query $query
21
-     *
22
-     * @return void
23
-     */
24
-    public function apply(Query $query)
25
-    {
26
-        $entityMap = $query->getMapper()->getEntityMap();
27
-
28
-        $query->whereNull($entityMap->getQualifiedDeletedAtColumn());
29
-
30
-        $this->extend($query);
31
-    }
32
-
33
-    /**
34
-     * Remove the scope from the given Analogue query builder.
35
-     *
36
-     * @param mixed $query
37
-     *
38
-     * @return void
39
-     */
40
-    public function remove(Query $query)
41
-    {
42
-        $column = $query->getMapper()->getEntityMap()->getQualifiedDeletedAtColumn();
43
-
44
-        $query = $query->getQuery();
45
-
46
-        foreach ((array) $query->wheres as $key => $where) {
47
-            // If the where clause is a soft delete date constraint, we will remove it from
48
-            // the query and reset the keys on the wheres. This allows this developer to
49
-            // include deleted model in a relationship result set that is lazy loaded.
50
-            if ($this->isSoftDeleteConstraint($where, $column)) {
51
-                unset($query->wheres[$key]);
52
-
53
-                $query->wheres = array_values($query->wheres);
54
-            }
55
-        }
56
-    }
57
-
58
-    /**
59
-     * Extend the query builder with the needed functions.
60
-     *
61
-     * @param \Analogue\ORM\System\Query $query
62
-     *
63
-     * @return void
64
-     */
65
-    public function extend(Query $query)
66
-    {
67
-        foreach ($this->extensions as $extension) {
68
-            $this->{"add{$extension}"}($query);
69
-        }
70
-    }
71
-
72
-    /**
73
-     * Add the with-trashed extension to the builder.
74
-     *
75
-     * @param \Analogue\ORM\System\Query $query
76
-     *
77
-     * @return void
78
-     */
79
-    protected function addWithTrashed(Query $query)
80
-    {
81
-        $query->macro('withTrashed', function (Query $query) {
82
-            $this->remove($query);
83
-
84
-            return $query;
85
-        });
86
-    }
87
-
88
-    /**
89
-     * Add the only-trashed extension to the builder.
90
-     *
91
-     * @param \Analogue\ORM\System\Query $query
92
-     *
93
-     * @return void
94
-     */
95
-    protected function addOnlyTrashed(Query $query)
96
-    {
97
-        $query->macro('onlyTrashed', function (Query $query) {
98
-            $this->remove($query);
99
-
100
-            $query->getQuery()->whereNotNull($query->getMapper()->getEntityMap()->getQualifiedDeletedAtColumn());
101
-
102
-            return $query;
103
-        });
104
-    }
105
-
106
-    /**
107
-     * Determine if the given where clause is a soft delete constraint.
108
-     *
109
-     * @param array  $where
110
-     * @param string $column
111
-     *
112
-     * @return bool
113
-     */
114
-    protected function isSoftDeleteConstraint(array $where, $column)
115
-    {
116
-        return $where['type'] == 'Null' && $where['column'] == $column;
117
-    }
10
+	/**
11
+	 * All of the extensions to be added to the builder.
12
+	 *
13
+	 * @var array
14
+	 */
15
+	protected $extensions = ['WithTrashed', 'OnlyTrashed'];
16
+
17
+	/**
18
+	 * Apply the scope to a given Analogue query builder.
19
+	 *
20
+	 * @param \Analogue\ORM\System\Query $query
21
+	 *
22
+	 * @return void
23
+	 */
24
+	public function apply(Query $query)
25
+	{
26
+		$entityMap = $query->getMapper()->getEntityMap();
27
+
28
+		$query->whereNull($entityMap->getQualifiedDeletedAtColumn());
29
+
30
+		$this->extend($query);
31
+	}
32
+
33
+	/**
34
+	 * Remove the scope from the given Analogue query builder.
35
+	 *
36
+	 * @param mixed $query
37
+	 *
38
+	 * @return void
39
+	 */
40
+	public function remove(Query $query)
41
+	{
42
+		$column = $query->getMapper()->getEntityMap()->getQualifiedDeletedAtColumn();
43
+
44
+		$query = $query->getQuery();
45
+
46
+		foreach ((array) $query->wheres as $key => $where) {
47
+			// If the where clause is a soft delete date constraint, we will remove it from
48
+			// the query and reset the keys on the wheres. This allows this developer to
49
+			// include deleted model in a relationship result set that is lazy loaded.
50
+			if ($this->isSoftDeleteConstraint($where, $column)) {
51
+				unset($query->wheres[$key]);
52
+
53
+				$query->wheres = array_values($query->wheres);
54
+			}
55
+		}
56
+	}
57
+
58
+	/**
59
+	 * Extend the query builder with the needed functions.
60
+	 *
61
+	 * @param \Analogue\ORM\System\Query $query
62
+	 *
63
+	 * @return void
64
+	 */
65
+	public function extend(Query $query)
66
+	{
67
+		foreach ($this->extensions as $extension) {
68
+			$this->{"add{$extension}"}($query);
69
+		}
70
+	}
71
+
72
+	/**
73
+	 * Add the with-trashed extension to the builder.
74
+	 *
75
+	 * @param \Analogue\ORM\System\Query $query
76
+	 *
77
+	 * @return void
78
+	 */
79
+	protected function addWithTrashed(Query $query)
80
+	{
81
+		$query->macro('withTrashed', function (Query $query) {
82
+			$this->remove($query);
83
+
84
+			return $query;
85
+		});
86
+	}
87
+
88
+	/**
89
+	 * Add the only-trashed extension to the builder.
90
+	 *
91
+	 * @param \Analogue\ORM\System\Query $query
92
+	 *
93
+	 * @return void
94
+	 */
95
+	protected function addOnlyTrashed(Query $query)
96
+	{
97
+		$query->macro('onlyTrashed', function (Query $query) {
98
+			$this->remove($query);
99
+
100
+			$query->getQuery()->whereNotNull($query->getMapper()->getEntityMap()->getQualifiedDeletedAtColumn());
101
+
102
+			return $query;
103
+		});
104
+	}
105
+
106
+	/**
107
+	 * Determine if the given where clause is a soft delete constraint.
108
+	 *
109
+	 * @param array  $where
110
+	 * @param string $column
111
+	 *
112
+	 * @return bool
113
+	 */
114
+	protected function isSoftDeleteConstraint(array $where, $column)
115
+	{
116
+		return $where['type'] == 'Null' && $where['column'] == $column;
117
+	}
118 118
 }
Please login to merge, or discard this patch.
src/Relationships/BelongsToMany.php 3 patches
Doc Comments   +1 added lines patch added patch discarded remove patch
@@ -383,6 +383,7 @@
 block discarded – undo
383 383
      * Set the join clause for the relation query.
384 384
      *
385 385
      * @param  \Analogue\ORM\Query|null
386
+     * @param Query $query
386 387
      * @return $this
387 388
      */
388 389
     protected function setJoin($query = null)
Please login to merge, or discard this patch.
Indentation   +925 added lines, -925 removed lines patch added patch discarded remove patch
@@ -12,929 +12,929 @@
 block discarded – undo
12 12
 
13 13
 class BelongsToMany extends Relationship
14 14
 {
15
-    /**
16
-     * The intermediate table for the relation.
17
-     *
18
-     * @var string
19
-     */
20
-    protected $table;
21
-
22
-    /**
23
-     * The foreign key of the parent model.
24
-     *
25
-     * @var string
26
-     */
27
-    protected $foreignKey;
28
-
29
-    /**
30
-     * The associated key of the relation.
31
-     *
32
-     * @var string
33
-     */
34
-    protected $otherKey;
35
-
36
-    /**
37
-     * The "name" of the relationship.
38
-     *
39
-     * @var string
40
-     */
41
-    protected $relationName;
42
-
43
-    /**
44
-     * The pivot table columns to retrieve.
45
-     *
46
-     * @var array
47
-     */
48
-    protected $pivotColumns = [];
49
-
50
-    /**
51
-     * This relationship has pivot attributes.
52
-     *
53
-     * @var bool
54
-     */
55
-    protected static $hasPivot = true;
56
-
57
-    /**
58
-     * Create a new has many relationship instance.
59
-     *
60
-     * @param Mapper   $mapper
61
-     * @param Mappable $parent
62
-     * @param string   $table
63
-     * @param string   $foreignKey
64
-     * @param string   $otherKey
65
-     * @param string   $relationName
66
-     */
67
-    public function __construct(Mapper $mapper, $parent, $table, $foreignKey, $otherKey, $relationName = null)
68
-    {
69
-        $this->table = $table;
70
-        $this->otherKey = $otherKey;
71
-        $this->foreignKey = $foreignKey;
72
-        $this->relationName = $relationName;
73
-
74
-        parent::__construct($mapper, $parent);
75
-    }
76
-
77
-    /**
78
-     * @param $related
79
-     */
80
-    public function detachMany($related)
81
-    {
82
-        $ids = $this->getIdsFromHashes($related);
83
-
84
-        $this->detach($ids);
85
-    }
86
-
87
-    /**
88
-     * @param array $hashes
89
-     *
90
-     * @return array
91
-     */
92
-    protected function getIdsFromHashes(array $hashes)
93
-    {
94
-        $ids = [];
95
-
96
-        foreach ($hashes as $hash) {
97
-            $split = explode('.', $hash);
98
-            $ids[] = $split[1];
99
-        }
100
-
101
-        return $ids;
102
-    }
103
-
104
-    /**
105
-     * Get the results of the relationship.
106
-     *
107
-     * @param $relation
108
-     *
109
-     * @return EntityCollection
110
-     */
111
-    public function getResults($relation)
112
-    {
113
-        $results = $this->get();
114
-
115
-        $this->cacheRelation($results, $relation);
116
-
117
-        return $results;
118
-    }
119
-
120
-    /**
121
-     * Set a where clause for a pivot table column.
122
-     *
123
-     * @param string $column
124
-     * @param string $operator
125
-     * @param mixed  $value
126
-     * @param string $boolean
127
-     *
128
-     * @return self
129
-     */
130
-    public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
131
-    {
132
-        return $this->where($this->table.'.'.$column, $operator, $value, $boolean);
133
-    }
134
-
135
-    /**
136
-     * Set an or where clause for a pivot table column.
137
-     *
138
-     * @param string $column
139
-     * @param string $operator
140
-     * @param mixed  $value
141
-     *
142
-     * @return self
143
-     */
144
-    public function orWherePivot($column, $operator = null, $value = null)
145
-    {
146
-        return $this->wherePivot($column, $operator, $value, 'or');
147
-    }
148
-
149
-    /**
150
-     * Return Pivot attributes when available on a relationship.
151
-     *
152
-     * @return array
153
-     */
154
-    public function getPivotAttributes()
155
-    {
156
-        return $this->pivotColumns;
157
-    }
158
-
159
-    /**
160
-     * Execute the query and get the first result.
161
-     *
162
-     * @param array $columns
163
-     *
164
-     * @return mixed
165
-     */
166
-    public function first($columns = ['*'])
167
-    {
168
-        $results = $this->take(1)->get($columns);
169
-
170
-        return count($results) > 0 ? $results->first() : null;
171
-    }
172
-
173
-    /**
174
-     * Execute the query and get the first result or throw an exception.
175
-     *
176
-     * @param array $columns
177
-     *
178
-     * @throws EntityNotFoundException
179
-     *
180
-     * @return Mappable|self
181
-     */
182
-    public function firstOrFail($columns = ['*'])
183
-    {
184
-        if (!is_null($entity = $this->first($columns))) {
185
-            return $entity;
186
-        }
187
-
188
-        throw new EntityNotFoundException();
189
-    }
190
-
191
-    /**
192
-     * Execute the query as a "select" statement.
193
-     *
194
-     * @param array $columns
195
-     *
196
-     * @return \Analogue\ORM\EntityCollection
197
-     */
198
-    public function get($columns = ['*'])
199
-    {
200
-        // First we'll add the proper select columns onto the query so it is run with
201
-        // the proper columns. Then, we will get the results and hydrate out pivot
202
-        // models with the result of those columns as a separate model relation.
203
-        $columns = $this->query->getQuery()->columns ? [] : $columns;
204
-
205
-        $select = $this->getSelectColumns($columns);
206
-
207
-        $entities = $this->query->addSelect($select)->getEntities();
208
-
209
-        $entities = $this->hydratePivotRelation($entities);
210
-
211
-        return $this->relatedMap->newCollection($entities);
212
-    }
213
-
214
-    /**
215
-     * Hydrate the pivot table relationship on the models.
216
-     *
217
-     * @param array $entities
218
-     *
219
-     * @return void
220
-     */
221
-    protected function hydratePivotRelation(array $entities)
222
-    {
223
-        // TODO (note) We should definitely get rid of the pivot in a next
224
-        // release, as this is not quite relevant in a datamapper context.
225
-        $host = $this;
226
-
227
-        return array_map(function ($entity) use ($host) {
228
-            $entityWrapper = $this->factory->make($entity);
229
-
230
-            $pivot = $this->newExistingPivot($this->cleanPivotAttributes($entityWrapper));
231
-            $entityWrapper->setEntityAttribute('pivot', $pivot);
232
-
233
-            return $entityWrapper->getObject();
234
-        }, $entities);
235
-    }
236
-
237
-    /**
238
-     * Get the pivot attributes from a model.
239
-     *
240
-     * @param  $entity
241
-     *
242
-     * @return array
243
-     */
244
-    protected function cleanPivotAttributes(InternallyMappable $entity)
245
-    {
246
-        $values = [];
247
-
248
-        $attributes = $entity->getEntityAttributes();
249
-
250
-        foreach ($attributes as $key => $value) {
251
-            // To get the pivots attributes we will just take any of the attributes which
252
-            // begin with "pivot_" and add those to this arrays, as well as unsetting
253
-            // them from the parent's models since they exist in a different table.
254
-            if (strpos($key, 'pivot_') === 0) {
255
-                $values[substr($key, 6)] = $value;
256
-
257
-                unset($attributes[$key]);
258
-            }
259
-        }
260
-
261
-        // Rehydrate Entity with cleaned array.
262
-        $entity->setEntityAttributes($attributes);
263
-
264
-        return $values;
265
-    }
266
-
267
-    /**
268
-     * Set the base constraints on the relation query.
269
-     *
270
-     * @return void
271
-     */
272
-    public function addConstraints()
273
-    {
274
-        $this->setJoin();
275
-
276
-        if (static::$constraints) {
277
-            $this->setWhere();
278
-        }
279
-    }
280
-
281
-    /**
282
-     * Add the constraints for a relationship count query.
283
-     *
284
-     * @param Query $query
285
-     * @param Query $parent
286
-     *
287
-     * @return Query
288
-     */
289
-    public function getRelationCountQuery(Query $query, Query $parent)
290
-    {
291
-        if ($parent->getQuery()->from == $query->getQuery()->from) {
292
-            return $this->getRelationCountQueryForSelfJoin($query, $parent);
293
-        }
294
-
295
-        $this->setJoin($query);
296
-
297
-        return parent::getRelationCountQuery($query, $parent);
298
-    }
299
-
300
-    /**
301
-     * Add the constraints for a relationship count query on the same table.
302
-     *
303
-     * @param Query $query
304
-     * @param Query $parent
305
-     *
306
-     * @return Query
307
-     */
308
-    public function getRelationCountQueryForSelfJoin(Query $query, Query $parent)
309
-    {
310
-        $query->select(new Expression('count(*)'));
311
-
312
-        $tablePrefix = $this->query->getQuery()->getConnection()->getTablePrefix();
313
-
314
-        $query->from($this->table.' as '.$tablePrefix.$hash = $this->getRelationCountHash());
315
-
316
-        $key = $this->wrap($this->getQualifiedParentKeyName());
317
-
318
-        return $query->where($hash.'.'.$this->foreignKey, '=', new Expression($key));
319
-    }
320
-
321
-    /**
322
-     * Get a relationship join table hash.
323
-     *
324
-     * @return string
325
-     */
326
-    public function getRelationCountHash()
327
-    {
328
-        return 'self_'.md5(microtime(true));
329
-    }
330
-
331
-    /**
332
-     * Set the select clause for the relation query.
333
-     *
334
-     * @param array $columns
335
-     *
336
-     * @return \Analogue\ORM\Relationships\BelongsToMany
337
-     */
338
-    protected function getSelectColumns(array $columns = ['*'])
339
-    {
340
-        if ($columns == ['*']) {
341
-            $columns = [$this->relatedMap->getTable().'.*'];
342
-        }
343
-
344
-        return array_merge($columns, $this->getAliasedPivotColumns());
345
-    }
346
-
347
-    /**
348
-     * Get the pivot columns for the relation.
349
-     *
350
-     * @return array
351
-     */
352
-    protected function getAliasedPivotColumns()
353
-    {
354
-        $defaults = [$this->foreignKey, $this->otherKey];
355
-
356
-        // We need to alias all of the pivot columns with the "pivot_" prefix so we
357
-        // can easily extract them out of the models and put them into the pivot
358
-        // relationships when they are retrieved and hydrated into the models.
359
-        $columns = [];
360
-
361
-        foreach (array_merge($defaults, $this->pivotColumns) as $column) {
362
-            $columns[] = $this->table.'.'.$column.' as pivot_'.$column;
363
-        }
364
-
365
-        return array_unique($columns);
366
-    }
367
-
368
-    /**
369
-     * Set the join clause for the relation query.
370
-     *
371
-     * @param  \Analogue\ORM\Query|null
372
-     *
373
-     * @return $this
374
-     */
375
-    protected function setJoin($query = null)
376
-    {
377
-        $query = $query ?: $this->query;
378
-
379
-        // We need to join to the intermediate table on the related model's primary
380
-        // key column with the intermediate table's foreign key for the related
381
-        // model instance. Then we can set the "where" for the parent models.
382
-        $baseTable = $this->relatedMap->getTable();
383
-
384
-        $key = $baseTable.'.'.$this->relatedMap->getKeyName();
385
-
386
-        $query->join($this->table, $key, '=', $this->getOtherKey());
387
-
388
-        return $this;
389
-    }
390
-
391
-    /**
392
-     * Set the where clause for the relation query.
393
-     *
394
-     * @return $this
395
-     */
396
-    protected function setWhere()
397
-    {
398
-        $foreign = $this->getForeignKey();
399
-
400
-        $parentKey = $this->parentMap->getKeyName();
401
-
402
-        $this->query->where($foreign, '=', $this->parent->getEntityAttribute($parentKey));
403
-
404
-        return $this;
405
-    }
406
-
407
-    /**
408
-     * Set the constraints for an eager load of the relation.
409
-     *
410
-     * @param array $results
411
-     *
412
-     * @return void
413
-     */
414
-    public function addEagerConstraints(array $results)
415
-    {
416
-        $this->query->whereIn($this->getForeignKey(), $this->getKeysFromResults($results));
417
-    }
418
-
419
-    /**
420
-     * Match Eagerly loaded relation to result.
421
-     *
422
-     * @param array  $results
423
-     * @param string $relation
424
-     *
425
-     * @return array
426
-     */
427
-    public function match(array $results, $relation)
428
-    {
429
-        $entities = $this->getEager();
430
-
431
-        // TODO; optimize this operation
432
-        $dictionary = $this->buildDictionary($entities);
433
-
434
-        $keyName = $this->relatedMap->getKeyName();
435
-
436
-        $cache = $this->parentMapper->getEntityCache();
437
-
438
-        $host = $this;
439
-
440
-        // Once we have an array dictionary of child objects we can easily match the
441
-        // children back to their parent using the dictionary and the keys on the
442
-        // the parent models. Then we will return the hydrated models back out.
443
-        return array_map(function ($result) use ($dictionary, $keyName, $cache, $relation, $host) {
444
-            if (isset($dictionary[$key = $result[$keyName]])) {
445
-                $collection = $host->relatedMap->newCollection($dictionary[$key]);
446
-
447
-                $result[$relation] = $collection;
448
-
449
-                // TODO Refactor this
450
-                $cache->cacheLoadedRelationResult($key, $relation, $collection, $this);
451
-            } else {
452
-                $result[$relation] = $host->relatedMap->newCollection();
453
-            }
454
-
455
-            return $result;
456
-        }, $results);
457
-    }
458
-
459
-    /**
460
-     * Build model dictionary keyed by the relation's foreign key.
461
-     *
462
-     * @param EntityCollection $results
463
-     *
464
-     * @return array
465
-     */
466
-    protected function buildDictionary(EntityCollection $results)
467
-    {
468
-        $foreign = $this->foreignKey;
469
-
470
-        // First we will build a dictionary of child models keyed by the foreign key
471
-        // of the relation so that we will easily and quickly match them to their
472
-        // parents without having a possibly slow inner loops for every models.
473
-        $dictionary = [];
474
-
475
-        foreach ($results as $entity) {
476
-            $wrapper = $this->factory->make($entity);
477
-            $dictionary[$wrapper->getEntityAttribute('pivot')->$foreign][] = $entity;
478
-        }
479
-
480
-        return $dictionary;
481
-    }
482
-
483
-    /**
484
-     * Get all of the IDs for the related models.
485
-     *
486
-     * @return array
487
-     */
488
-    public function getRelatedIds()
489
-    {
490
-        $fullKey = $this->relatedMap->getQualifiedKeyName();
491
-
492
-        return $this->getQuery()->select($fullKey)->lists($this->relatedMap->getKeyName());
493
-    }
494
-
495
-    /**
496
-     * Update Pivot.
497
-     *
498
-     * @param \Analogue\ORM\Entity $entity
499
-     *
500
-     * @return void
501
-     */
502
-    public function updatePivot($entity)
503
-    {
504
-        $keyName = $this->relatedMap->getKeyName();
505
-
506
-        $this->updateExistingPivot(
507
-            $entity->getEntityAttribute($keyName),
508
-            $entity->getEntityAttribute('pivot')->getEntityAttributes()
509
-        );
510
-    }
511
-
512
-    /**
513
-     * Update Multiple pivot.
514
-     *
515
-     * @param  $relatedEntities
516
-     *
517
-     * @return void
518
-     */
519
-    public function updatePivots($relatedEntities)
520
-    {
521
-        foreach ($relatedEntities as $entity) {
522
-            $this->updatePivot($entity);
523
-        }
524
-    }
525
-
526
-    /**
527
-     * Create Pivot Records.
528
-     *
529
-     * @param \Analogue\ORM\Entity[] $relatedEntities
530
-     *
531
-     * @return void
532
-     */
533
-    public function createPivots($relatedEntities)
534
-    {
535
-        $keys = [];
536
-        $attributes = [];
537
-
538
-        $keyName = $this->relatedMap->getKeyName();
539
-
540
-        foreach ($relatedEntities as $entity) {
541
-            $keys[] = $entity->getEntityAttribute($keyName);
542
-        }
543
-
544
-        $records = $this->createAttachRecords($keys, $attributes);
545
-
546
-        $this->query->getQuery()->from($this->table)->insert($records);
547
-    }
548
-
549
-    /**
550
-     * Update an existing pivot record on the table.
551
-     *
552
-     * @param mixed $id
553
-     * @param array $attributes
554
-     *
555
-     * @throws \InvalidArgumentException
556
-     *
557
-     * @return int
558
-     */
559
-    public function updateExistingPivot($id, array $attributes)
560
-    {
561
-        if (in_array($this->updatedAt(), $this->pivotColumns)) {
562
-            $attributes = $this->setTimestampsOnAttach($attributes, true);
563
-        }
564
-
565
-        return $this->newPivotStatementForId($id)->update($attributes);
566
-    }
567
-
568
-    /**
569
-     * Attach a model to the parent.
570
-     *
571
-     * @param mixed $id
572
-     * @param array $attributes
573
-     *
574
-     * @return void
575
-     */
576
-    public function attach($id, array $attributes = [])
577
-    {
578
-        $query = $this->newPivotStatement();
579
-
580
-        $query->insert($this->createAttachRecords((array) $id, $attributes));
581
-    }
582
-
583
-    /**
584
-     * @param array $entities
585
-     *
586
-     * @throws \InvalidArgumentException
587
-     */
588
-    public function sync(array $entities)
589
-    {
590
-        $this->detachExcept($entities);
591
-    }
592
-
593
-    /**
594
-     * Detach related entities that are not in $id.
595
-     *
596
-     * @param array $entities
597
-     *
598
-     * @throws \InvalidArgumentException
599
-     *
600
-     * @return void
601
-     */
602
-    protected function detachExcept(array $entities = [])
603
-    {
604
-        $query = $this->newPivotQuery();
605
-
606
-        if (count($entities) > 0) {
607
-            $keys = $this->getKeys($entities);
608
-
609
-            $query->whereNotIn($this->otherKey, $keys);
610
-        }
611
-        $parentKey = $this->parentMap->getKeyName();
612
-
613
-        $query->where($this->foreignKey, '=', $this->parent->getEntityAttribute($parentKey));
614
-
615
-        $query->delete();
616
-
617
-        $query = $this->newPivotQuery();
618
-    }
619
-
620
-    /**
621
-     * Create an array of records to insert into the pivot table.
622
-     *
623
-     * @param array $ids
624
-     * @param array $attributes
625
-     *
626
-     * @return array
627
-     */
628
-    protected function createAttachRecords($ids, array $attributes)
629
-    {
630
-        $records = [];
631
-
632
-        $timed = in_array($this->createdAt(), $this->pivotColumns);
633
-
634
-        // To create the attachment records, we will simply spin through the IDs given
635
-        // and create a new record to insert for each ID. Each ID may actually be a
636
-        // key in the array, with extra attributes to be placed in other columns.
637
-        foreach ($ids as $key => $value) {
638
-            $records[] = $this->attacher($key, $value, $attributes, $timed);
639
-        }
640
-
641
-        return $records;
642
-    }
643
-
644
-    /**
645
-     * Create a full attachment record payload.
646
-     *
647
-     * @param int   $key
648
-     * @param mixed $value
649
-     * @param array $attributes
650
-     * @param bool  $timed
651
-     *
652
-     * @return array
653
-     */
654
-    protected function attacher($key, $value, $attributes, $timed)
655
-    {
656
-        list($id, $extra) = $this->getAttachId($key, $value, $attributes);
657
-
658
-        // To create the attachment records, we will simply spin through the IDs given
659
-        // and create a new record to insert for each ID. Each ID may actually be a
660
-        // key in the array, with extra attributes to be placed in other columns.
661
-        $record = $this->createAttachRecord($id, $timed);
662
-
663
-        return array_merge($record, $extra);
664
-    }
665
-
666
-    /**
667
-     * Get the attach record ID and extra attributes.
668
-     *
669
-     * @param int   $key
670
-     * @param mixed $value
671
-     * @param array $attributes
672
-     *
673
-     * @return array
674
-     */
675
-    protected function getAttachId($key, $value, array $attributes)
676
-    {
677
-        if (is_array($value)) {
678
-            return [$key, array_merge($value, $attributes)];
679
-        }
680
-
681
-        return [$value, $attributes];
682
-    }
683
-
684
-    /**
685
-     * Create a new pivot attachment record.
686
-     *
687
-     * @param int  $id
688
-     * @param bool $timed
689
-     *
690
-     * @return array
691
-     */
692
-    protected function createAttachRecord($id, $timed)
693
-    {
694
-        $parentKey = $this->parentMap->getKeyName();
695
-
696
-        $record = [];
697
-
698
-        $record[$this->foreignKey] = $this->parent->getEntityAttribute($parentKey);
699
-
700
-        $record[$this->otherKey] = $id;
701
-
702
-        // If the record needs to have creation and update timestamps, we will make
703
-        // them by calling the parent model's "freshTimestamp" method which will
704
-        // provide us with a fresh timestamp in this model's preferred format.
705
-        if ($timed) {
706
-            $record = $this->setTimestampsOnAttach($record);
707
-        }
708
-
709
-        return $record;
710
-    }
711
-
712
-    /**
713
-     * Set the creation and update timestamps on an attach record.
714
-     *
715
-     * @param array $record
716
-     * @param bool  $exists
717
-     *
718
-     * @return array
719
-     */
720
-    protected function setTimestampsOnAttach(array $record, $exists = false)
721
-    {
722
-        $fresh = $this->freshTimestamp();
723
-
724
-        if (!$exists) {
725
-            $record[$this->createdAt()] = $fresh;
726
-        }
727
-
728
-        $record[$this->updatedAt()] = $fresh;
729
-
730
-        return $record;
731
-    }
732
-
733
-    /**
734
-     * @param EntityCollection $entities
735
-     *
736
-     * @return array
737
-     */
738
-    protected function getModelKeysFromCollection(EntityCollection $entities)
739
-    {
740
-        $keyName = $this->relatedMap->getKeyName();
741
-
742
-        return array_map(function ($m) use ($keyName) {
743
-            return $m->$keyName;
744
-        }, $entities);
745
-    }
746
-
747
-    /**
748
-     * Detach models from the relationship.
749
-     *
750
-     * @param int|array $ids
751
-     *
752
-     * @throws \InvalidArgumentException
753
-     *
754
-     * @return int
755
-     */
756
-    public function detach($ids = [])
757
-    {
758
-        if ($ids instanceof EntityCollection) {
759
-            $ids = (array) $ids->modelKeys();
760
-        }
761
-
762
-        $query = $this->newPivotQuery();
763
-
764
-        // If associated IDs were passed to the method we will only delete those
765
-        // associations, otherwise all of the association ties will be broken.
766
-        // We'll return the numbers of affected rows when we do the deletes.
767
-        $ids = (array) $ids;
768
-
769
-        if (count($ids) > 0) {
770
-            $query->whereIn($this->otherKey, (array) $ids);
771
-        }
772
-
773
-        // Once we have all of the conditions set on the statement, we are ready
774
-        // to run the delete on the pivot table. Then, if the touch parameter
775
-        // is true, we will go ahead and touch all related models to sync.
776
-        return $query->delete();
777
-    }
778
-
779
-    /**
780
-     * Create a new query builder for the pivot table.
781
-     *
782
-     * @throws \InvalidArgumentException
783
-     *
784
-     * @return \Illuminate\Database\Query\Builder
785
-     */
786
-    protected function newPivotQuery()
787
-    {
788
-        $query = $this->newPivotStatement();
789
-
790
-        $parentKey = $this->parentMap->getKeyName();
791
-
792
-        return $query->where($this->foreignKey, $this->parent->getEntityAttribute($parentKey));
793
-    }
794
-
795
-    /**
796
-     * Get a new plain query builder for the pivot table.
797
-     *
798
-     * @return \Illuminate\Database\Query\Builder
799
-     */
800
-    public function newPivotStatement()
801
-    {
802
-        return $this->query->getQuery()->newQuery()->from($this->table);
803
-    }
804
-
805
-    /**
806
-     * Get a new pivot statement for a given "other" ID.
807
-     *
808
-     * @param mixed $id
809
-     *
810
-     * @throws \InvalidArgumentException
811
-     *
812
-     * @return \Illuminate\Database\Query\Builder
813
-     */
814
-    public function newPivotStatementForId($id)
815
-    {
816
-        $pivot = $this->newPivotStatement();
817
-
818
-        $parentKeyName = $this->parentMap->getKeyName();
819
-
820
-        $key = $this->parent->getEntityAttribute($parentKeyName);
821
-
822
-        return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id);
823
-    }
824
-
825
-    /**
826
-     * Create a new pivot model instance.
827
-     *
828
-     * @param array $attributes
829
-     * @param bool  $exists
830
-     *
831
-     * @return \Analogue\ORM\Relationships\Pivot
832
-     */
833
-    public function newPivot(array $attributes = [], $exists = false)
834
-    {
835
-        $pivot = new Pivot($this->parent, $this->parentMap, $attributes, $this->table, $exists);
836
-
837
-        return $pivot->setPivotKeys($this->foreignKey, $this->otherKey);
838
-    }
839
-
840
-    /**
841
-     * Create a new existing pivot model instance.
842
-     *
843
-     * @param array $attributes
844
-     *
845
-     * @return \Analogue\ORM\Relationships\Pivot
846
-     */
847
-    public function newExistingPivot(array $attributes = [])
848
-    {
849
-        return $this->newPivot($attributes, true);
850
-    }
851
-
852
-    /**
853
-     * Set the columns on the pivot table to retrieve.
854
-     *
855
-     * @param array $columns
856
-     *
857
-     * @return $this
858
-     */
859
-    public function withPivot($columns)
860
-    {
861
-        $columns = is_array($columns) ? $columns : func_get_args();
862
-
863
-        $this->pivotColumns = array_merge($this->pivotColumns, $columns);
864
-
865
-        return $this;
866
-    }
867
-
868
-    /**
869
-     * Specify that the pivot table has creation and update timestamps.
870
-     *
871
-     * @param mixed $createdAt
872
-     * @param mixed $updatedAt
873
-     *
874
-     * @return \Analogue\ORM\Relationships\BelongsToMany
875
-     */
876
-    public function withTimestamps($createdAt = null, $updatedAt = null)
877
-    {
878
-        return $this->withPivot($createdAt ?: $this->createdAt(), $updatedAt ?: $this->updatedAt());
879
-    }
880
-
881
-    /**
882
-     * Get the key for comparing against the parent key in "has" query.
883
-     *
884
-     * @return string
885
-     */
886
-    public function getHasCompareKey()
887
-    {
888
-        return $this->getForeignKey();
889
-    }
890
-
891
-    /**
892
-     * Get the fully qualified foreign key for the relation.
893
-     *
894
-     * @return string
895
-     */
896
-    public function getForeignKey()
897
-    {
898
-        return $this->table.'.'.$this->foreignKey;
899
-    }
900
-
901
-    /**
902
-     * Get the fully qualified "other key" for the relation.
903
-     *
904
-     * @return string
905
-     */
906
-    public function getOtherKey()
907
-    {
908
-        return $this->table.'.'.$this->otherKey;
909
-    }
910
-
911
-    /**
912
-     * Get the fully qualified parent key name.
913
-     *
914
-     * @return string
915
-     */
916
-    protected function getQualifiedParentKeyName()
917
-    {
918
-        return $this->parentMap->getQualifiedKeyName();
919
-    }
920
-
921
-    /**
922
-     * Get the intermediate table for the relationship.
923
-     *
924
-     * @return string
925
-     */
926
-    public function getTable()
927
-    {
928
-        return $this->table;
929
-    }
930
-
931
-    /**
932
-     * Get the relationship name for the relationship.
933
-     *
934
-     * @return string
935
-     */
936
-    public function getRelationName()
937
-    {
938
-        return $this->relationName;
939
-    }
15
+	/**
16
+	 * The intermediate table for the relation.
17
+	 *
18
+	 * @var string
19
+	 */
20
+	protected $table;
21
+
22
+	/**
23
+	 * The foreign key of the parent model.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	protected $foreignKey;
28
+
29
+	/**
30
+	 * The associated key of the relation.
31
+	 *
32
+	 * @var string
33
+	 */
34
+	protected $otherKey;
35
+
36
+	/**
37
+	 * The "name" of the relationship.
38
+	 *
39
+	 * @var string
40
+	 */
41
+	protected $relationName;
42
+
43
+	/**
44
+	 * The pivot table columns to retrieve.
45
+	 *
46
+	 * @var array
47
+	 */
48
+	protected $pivotColumns = [];
49
+
50
+	/**
51
+	 * This relationship has pivot attributes.
52
+	 *
53
+	 * @var bool
54
+	 */
55
+	protected static $hasPivot = true;
56
+
57
+	/**
58
+	 * Create a new has many relationship instance.
59
+	 *
60
+	 * @param Mapper   $mapper
61
+	 * @param Mappable $parent
62
+	 * @param string   $table
63
+	 * @param string   $foreignKey
64
+	 * @param string   $otherKey
65
+	 * @param string   $relationName
66
+	 */
67
+	public function __construct(Mapper $mapper, $parent, $table, $foreignKey, $otherKey, $relationName = null)
68
+	{
69
+		$this->table = $table;
70
+		$this->otherKey = $otherKey;
71
+		$this->foreignKey = $foreignKey;
72
+		$this->relationName = $relationName;
73
+
74
+		parent::__construct($mapper, $parent);
75
+	}
76
+
77
+	/**
78
+	 * @param $related
79
+	 */
80
+	public function detachMany($related)
81
+	{
82
+		$ids = $this->getIdsFromHashes($related);
83
+
84
+		$this->detach($ids);
85
+	}
86
+
87
+	/**
88
+	 * @param array $hashes
89
+	 *
90
+	 * @return array
91
+	 */
92
+	protected function getIdsFromHashes(array $hashes)
93
+	{
94
+		$ids = [];
95
+
96
+		foreach ($hashes as $hash) {
97
+			$split = explode('.', $hash);
98
+			$ids[] = $split[1];
99
+		}
100
+
101
+		return $ids;
102
+	}
103
+
104
+	/**
105
+	 * Get the results of the relationship.
106
+	 *
107
+	 * @param $relation
108
+	 *
109
+	 * @return EntityCollection
110
+	 */
111
+	public function getResults($relation)
112
+	{
113
+		$results = $this->get();
114
+
115
+		$this->cacheRelation($results, $relation);
116
+
117
+		return $results;
118
+	}
119
+
120
+	/**
121
+	 * Set a where clause for a pivot table column.
122
+	 *
123
+	 * @param string $column
124
+	 * @param string $operator
125
+	 * @param mixed  $value
126
+	 * @param string $boolean
127
+	 *
128
+	 * @return self
129
+	 */
130
+	public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
131
+	{
132
+		return $this->where($this->table.'.'.$column, $operator, $value, $boolean);
133
+	}
134
+
135
+	/**
136
+	 * Set an or where clause for a pivot table column.
137
+	 *
138
+	 * @param string $column
139
+	 * @param string $operator
140
+	 * @param mixed  $value
141
+	 *
142
+	 * @return self
143
+	 */
144
+	public function orWherePivot($column, $operator = null, $value = null)
145
+	{
146
+		return $this->wherePivot($column, $operator, $value, 'or');
147
+	}
148
+
149
+	/**
150
+	 * Return Pivot attributes when available on a relationship.
151
+	 *
152
+	 * @return array
153
+	 */
154
+	public function getPivotAttributes()
155
+	{
156
+		return $this->pivotColumns;
157
+	}
158
+
159
+	/**
160
+	 * Execute the query and get the first result.
161
+	 *
162
+	 * @param array $columns
163
+	 *
164
+	 * @return mixed
165
+	 */
166
+	public function first($columns = ['*'])
167
+	{
168
+		$results = $this->take(1)->get($columns);
169
+
170
+		return count($results) > 0 ? $results->first() : null;
171
+	}
172
+
173
+	/**
174
+	 * Execute the query and get the first result or throw an exception.
175
+	 *
176
+	 * @param array $columns
177
+	 *
178
+	 * @throws EntityNotFoundException
179
+	 *
180
+	 * @return Mappable|self
181
+	 */
182
+	public function firstOrFail($columns = ['*'])
183
+	{
184
+		if (!is_null($entity = $this->first($columns))) {
185
+			return $entity;
186
+		}
187
+
188
+		throw new EntityNotFoundException();
189
+	}
190
+
191
+	/**
192
+	 * Execute the query as a "select" statement.
193
+	 *
194
+	 * @param array $columns
195
+	 *
196
+	 * @return \Analogue\ORM\EntityCollection
197
+	 */
198
+	public function get($columns = ['*'])
199
+	{
200
+		// First we'll add the proper select columns onto the query so it is run with
201
+		// the proper columns. Then, we will get the results and hydrate out pivot
202
+		// models with the result of those columns as a separate model relation.
203
+		$columns = $this->query->getQuery()->columns ? [] : $columns;
204
+
205
+		$select = $this->getSelectColumns($columns);
206
+
207
+		$entities = $this->query->addSelect($select)->getEntities();
208
+
209
+		$entities = $this->hydratePivotRelation($entities);
210
+
211
+		return $this->relatedMap->newCollection($entities);
212
+	}
213
+
214
+	/**
215
+	 * Hydrate the pivot table relationship on the models.
216
+	 *
217
+	 * @param array $entities
218
+	 *
219
+	 * @return void
220
+	 */
221
+	protected function hydratePivotRelation(array $entities)
222
+	{
223
+		// TODO (note) We should definitely get rid of the pivot in a next
224
+		// release, as this is not quite relevant in a datamapper context.
225
+		$host = $this;
226
+
227
+		return array_map(function ($entity) use ($host) {
228
+			$entityWrapper = $this->factory->make($entity);
229
+
230
+			$pivot = $this->newExistingPivot($this->cleanPivotAttributes($entityWrapper));
231
+			$entityWrapper->setEntityAttribute('pivot', $pivot);
232
+
233
+			return $entityWrapper->getObject();
234
+		}, $entities);
235
+	}
236
+
237
+	/**
238
+	 * Get the pivot attributes from a model.
239
+	 *
240
+	 * @param  $entity
241
+	 *
242
+	 * @return array
243
+	 */
244
+	protected function cleanPivotAttributes(InternallyMappable $entity)
245
+	{
246
+		$values = [];
247
+
248
+		$attributes = $entity->getEntityAttributes();
249
+
250
+		foreach ($attributes as $key => $value) {
251
+			// To get the pivots attributes we will just take any of the attributes which
252
+			// begin with "pivot_" and add those to this arrays, as well as unsetting
253
+			// them from the parent's models since they exist in a different table.
254
+			if (strpos($key, 'pivot_') === 0) {
255
+				$values[substr($key, 6)] = $value;
256
+
257
+				unset($attributes[$key]);
258
+			}
259
+		}
260
+
261
+		// Rehydrate Entity with cleaned array.
262
+		$entity->setEntityAttributes($attributes);
263
+
264
+		return $values;
265
+	}
266
+
267
+	/**
268
+	 * Set the base constraints on the relation query.
269
+	 *
270
+	 * @return void
271
+	 */
272
+	public function addConstraints()
273
+	{
274
+		$this->setJoin();
275
+
276
+		if (static::$constraints) {
277
+			$this->setWhere();
278
+		}
279
+	}
280
+
281
+	/**
282
+	 * Add the constraints for a relationship count query.
283
+	 *
284
+	 * @param Query $query
285
+	 * @param Query $parent
286
+	 *
287
+	 * @return Query
288
+	 */
289
+	public function getRelationCountQuery(Query $query, Query $parent)
290
+	{
291
+		if ($parent->getQuery()->from == $query->getQuery()->from) {
292
+			return $this->getRelationCountQueryForSelfJoin($query, $parent);
293
+		}
294
+
295
+		$this->setJoin($query);
296
+
297
+		return parent::getRelationCountQuery($query, $parent);
298
+	}
299
+
300
+	/**
301
+	 * Add the constraints for a relationship count query on the same table.
302
+	 *
303
+	 * @param Query $query
304
+	 * @param Query $parent
305
+	 *
306
+	 * @return Query
307
+	 */
308
+	public function getRelationCountQueryForSelfJoin(Query $query, Query $parent)
309
+	{
310
+		$query->select(new Expression('count(*)'));
311
+
312
+		$tablePrefix = $this->query->getQuery()->getConnection()->getTablePrefix();
313
+
314
+		$query->from($this->table.' as '.$tablePrefix.$hash = $this->getRelationCountHash());
315
+
316
+		$key = $this->wrap($this->getQualifiedParentKeyName());
317
+
318
+		return $query->where($hash.'.'.$this->foreignKey, '=', new Expression($key));
319
+	}
320
+
321
+	/**
322
+	 * Get a relationship join table hash.
323
+	 *
324
+	 * @return string
325
+	 */
326
+	public function getRelationCountHash()
327
+	{
328
+		return 'self_'.md5(microtime(true));
329
+	}
330
+
331
+	/**
332
+	 * Set the select clause for the relation query.
333
+	 *
334
+	 * @param array $columns
335
+	 *
336
+	 * @return \Analogue\ORM\Relationships\BelongsToMany
337
+	 */
338
+	protected function getSelectColumns(array $columns = ['*'])
339
+	{
340
+		if ($columns == ['*']) {
341
+			$columns = [$this->relatedMap->getTable().'.*'];
342
+		}
343
+
344
+		return array_merge($columns, $this->getAliasedPivotColumns());
345
+	}
346
+
347
+	/**
348
+	 * Get the pivot columns for the relation.
349
+	 *
350
+	 * @return array
351
+	 */
352
+	protected function getAliasedPivotColumns()
353
+	{
354
+		$defaults = [$this->foreignKey, $this->otherKey];
355
+
356
+		// We need to alias all of the pivot columns with the "pivot_" prefix so we
357
+		// can easily extract them out of the models and put them into the pivot
358
+		// relationships when they are retrieved and hydrated into the models.
359
+		$columns = [];
360
+
361
+		foreach (array_merge($defaults, $this->pivotColumns) as $column) {
362
+			$columns[] = $this->table.'.'.$column.' as pivot_'.$column;
363
+		}
364
+
365
+		return array_unique($columns);
366
+	}
367
+
368
+	/**
369
+	 * Set the join clause for the relation query.
370
+	 *
371
+	 * @param  \Analogue\ORM\Query|null
372
+	 *
373
+	 * @return $this
374
+	 */
375
+	protected function setJoin($query = null)
376
+	{
377
+		$query = $query ?: $this->query;
378
+
379
+		// We need to join to the intermediate table on the related model's primary
380
+		// key column with the intermediate table's foreign key for the related
381
+		// model instance. Then we can set the "where" for the parent models.
382
+		$baseTable = $this->relatedMap->getTable();
383
+
384
+		$key = $baseTable.'.'.$this->relatedMap->getKeyName();
385
+
386
+		$query->join($this->table, $key, '=', $this->getOtherKey());
387
+
388
+		return $this;
389
+	}
390
+
391
+	/**
392
+	 * Set the where clause for the relation query.
393
+	 *
394
+	 * @return $this
395
+	 */
396
+	protected function setWhere()
397
+	{
398
+		$foreign = $this->getForeignKey();
399
+
400
+		$parentKey = $this->parentMap->getKeyName();
401
+
402
+		$this->query->where($foreign, '=', $this->parent->getEntityAttribute($parentKey));
403
+
404
+		return $this;
405
+	}
406
+
407
+	/**
408
+	 * Set the constraints for an eager load of the relation.
409
+	 *
410
+	 * @param array $results
411
+	 *
412
+	 * @return void
413
+	 */
414
+	public function addEagerConstraints(array $results)
415
+	{
416
+		$this->query->whereIn($this->getForeignKey(), $this->getKeysFromResults($results));
417
+	}
418
+
419
+	/**
420
+	 * Match Eagerly loaded relation to result.
421
+	 *
422
+	 * @param array  $results
423
+	 * @param string $relation
424
+	 *
425
+	 * @return array
426
+	 */
427
+	public function match(array $results, $relation)
428
+	{
429
+		$entities = $this->getEager();
430
+
431
+		// TODO; optimize this operation
432
+		$dictionary = $this->buildDictionary($entities);
433
+
434
+		$keyName = $this->relatedMap->getKeyName();
435
+
436
+		$cache = $this->parentMapper->getEntityCache();
437
+
438
+		$host = $this;
439
+
440
+		// Once we have an array dictionary of child objects we can easily match the
441
+		// children back to their parent using the dictionary and the keys on the
442
+		// the parent models. Then we will return the hydrated models back out.
443
+		return array_map(function ($result) use ($dictionary, $keyName, $cache, $relation, $host) {
444
+			if (isset($dictionary[$key = $result[$keyName]])) {
445
+				$collection = $host->relatedMap->newCollection($dictionary[$key]);
446
+
447
+				$result[$relation] = $collection;
448
+
449
+				// TODO Refactor this
450
+				$cache->cacheLoadedRelationResult($key, $relation, $collection, $this);
451
+			} else {
452
+				$result[$relation] = $host->relatedMap->newCollection();
453
+			}
454
+
455
+			return $result;
456
+		}, $results);
457
+	}
458
+
459
+	/**
460
+	 * Build model dictionary keyed by the relation's foreign key.
461
+	 *
462
+	 * @param EntityCollection $results
463
+	 *
464
+	 * @return array
465
+	 */
466
+	protected function buildDictionary(EntityCollection $results)
467
+	{
468
+		$foreign = $this->foreignKey;
469
+
470
+		// First we will build a dictionary of child models keyed by the foreign key
471
+		// of the relation so that we will easily and quickly match them to their
472
+		// parents without having a possibly slow inner loops for every models.
473
+		$dictionary = [];
474
+
475
+		foreach ($results as $entity) {
476
+			$wrapper = $this->factory->make($entity);
477
+			$dictionary[$wrapper->getEntityAttribute('pivot')->$foreign][] = $entity;
478
+		}
479
+
480
+		return $dictionary;
481
+	}
482
+
483
+	/**
484
+	 * Get all of the IDs for the related models.
485
+	 *
486
+	 * @return array
487
+	 */
488
+	public function getRelatedIds()
489
+	{
490
+		$fullKey = $this->relatedMap->getQualifiedKeyName();
491
+
492
+		return $this->getQuery()->select($fullKey)->lists($this->relatedMap->getKeyName());
493
+	}
494
+
495
+	/**
496
+	 * Update Pivot.
497
+	 *
498
+	 * @param \Analogue\ORM\Entity $entity
499
+	 *
500
+	 * @return void
501
+	 */
502
+	public function updatePivot($entity)
503
+	{
504
+		$keyName = $this->relatedMap->getKeyName();
505
+
506
+		$this->updateExistingPivot(
507
+			$entity->getEntityAttribute($keyName),
508
+			$entity->getEntityAttribute('pivot')->getEntityAttributes()
509
+		);
510
+	}
511
+
512
+	/**
513
+	 * Update Multiple pivot.
514
+	 *
515
+	 * @param  $relatedEntities
516
+	 *
517
+	 * @return void
518
+	 */
519
+	public function updatePivots($relatedEntities)
520
+	{
521
+		foreach ($relatedEntities as $entity) {
522
+			$this->updatePivot($entity);
523
+		}
524
+	}
525
+
526
+	/**
527
+	 * Create Pivot Records.
528
+	 *
529
+	 * @param \Analogue\ORM\Entity[] $relatedEntities
530
+	 *
531
+	 * @return void
532
+	 */
533
+	public function createPivots($relatedEntities)
534
+	{
535
+		$keys = [];
536
+		$attributes = [];
537
+
538
+		$keyName = $this->relatedMap->getKeyName();
539
+
540
+		foreach ($relatedEntities as $entity) {
541
+			$keys[] = $entity->getEntityAttribute($keyName);
542
+		}
543
+
544
+		$records = $this->createAttachRecords($keys, $attributes);
545
+
546
+		$this->query->getQuery()->from($this->table)->insert($records);
547
+	}
548
+
549
+	/**
550
+	 * Update an existing pivot record on the table.
551
+	 *
552
+	 * @param mixed $id
553
+	 * @param array $attributes
554
+	 *
555
+	 * @throws \InvalidArgumentException
556
+	 *
557
+	 * @return int
558
+	 */
559
+	public function updateExistingPivot($id, array $attributes)
560
+	{
561
+		if (in_array($this->updatedAt(), $this->pivotColumns)) {
562
+			$attributes = $this->setTimestampsOnAttach($attributes, true);
563
+		}
564
+
565
+		return $this->newPivotStatementForId($id)->update($attributes);
566
+	}
567
+
568
+	/**
569
+	 * Attach a model to the parent.
570
+	 *
571
+	 * @param mixed $id
572
+	 * @param array $attributes
573
+	 *
574
+	 * @return void
575
+	 */
576
+	public function attach($id, array $attributes = [])
577
+	{
578
+		$query = $this->newPivotStatement();
579
+
580
+		$query->insert($this->createAttachRecords((array) $id, $attributes));
581
+	}
582
+
583
+	/**
584
+	 * @param array $entities
585
+	 *
586
+	 * @throws \InvalidArgumentException
587
+	 */
588
+	public function sync(array $entities)
589
+	{
590
+		$this->detachExcept($entities);
591
+	}
592
+
593
+	/**
594
+	 * Detach related entities that are not in $id.
595
+	 *
596
+	 * @param array $entities
597
+	 *
598
+	 * @throws \InvalidArgumentException
599
+	 *
600
+	 * @return void
601
+	 */
602
+	protected function detachExcept(array $entities = [])
603
+	{
604
+		$query = $this->newPivotQuery();
605
+
606
+		if (count($entities) > 0) {
607
+			$keys = $this->getKeys($entities);
608
+
609
+			$query->whereNotIn($this->otherKey, $keys);
610
+		}
611
+		$parentKey = $this->parentMap->getKeyName();
612
+
613
+		$query->where($this->foreignKey, '=', $this->parent->getEntityAttribute($parentKey));
614
+
615
+		$query->delete();
616
+
617
+		$query = $this->newPivotQuery();
618
+	}
619
+
620
+	/**
621
+	 * Create an array of records to insert into the pivot table.
622
+	 *
623
+	 * @param array $ids
624
+	 * @param array $attributes
625
+	 *
626
+	 * @return array
627
+	 */
628
+	protected function createAttachRecords($ids, array $attributes)
629
+	{
630
+		$records = [];
631
+
632
+		$timed = in_array($this->createdAt(), $this->pivotColumns);
633
+
634
+		// To create the attachment records, we will simply spin through the IDs given
635
+		// and create a new record to insert for each ID. Each ID may actually be a
636
+		// key in the array, with extra attributes to be placed in other columns.
637
+		foreach ($ids as $key => $value) {
638
+			$records[] = $this->attacher($key, $value, $attributes, $timed);
639
+		}
640
+
641
+		return $records;
642
+	}
643
+
644
+	/**
645
+	 * Create a full attachment record payload.
646
+	 *
647
+	 * @param int   $key
648
+	 * @param mixed $value
649
+	 * @param array $attributes
650
+	 * @param bool  $timed
651
+	 *
652
+	 * @return array
653
+	 */
654
+	protected function attacher($key, $value, $attributes, $timed)
655
+	{
656
+		list($id, $extra) = $this->getAttachId($key, $value, $attributes);
657
+
658
+		// To create the attachment records, we will simply spin through the IDs given
659
+		// and create a new record to insert for each ID. Each ID may actually be a
660
+		// key in the array, with extra attributes to be placed in other columns.
661
+		$record = $this->createAttachRecord($id, $timed);
662
+
663
+		return array_merge($record, $extra);
664
+	}
665
+
666
+	/**
667
+	 * Get the attach record ID and extra attributes.
668
+	 *
669
+	 * @param int   $key
670
+	 * @param mixed $value
671
+	 * @param array $attributes
672
+	 *
673
+	 * @return array
674
+	 */
675
+	protected function getAttachId($key, $value, array $attributes)
676
+	{
677
+		if (is_array($value)) {
678
+			return [$key, array_merge($value, $attributes)];
679
+		}
680
+
681
+		return [$value, $attributes];
682
+	}
683
+
684
+	/**
685
+	 * Create a new pivot attachment record.
686
+	 *
687
+	 * @param int  $id
688
+	 * @param bool $timed
689
+	 *
690
+	 * @return array
691
+	 */
692
+	protected function createAttachRecord($id, $timed)
693
+	{
694
+		$parentKey = $this->parentMap->getKeyName();
695
+
696
+		$record = [];
697
+
698
+		$record[$this->foreignKey] = $this->parent->getEntityAttribute($parentKey);
699
+
700
+		$record[$this->otherKey] = $id;
701
+
702
+		// If the record needs to have creation and update timestamps, we will make
703
+		// them by calling the parent model's "freshTimestamp" method which will
704
+		// provide us with a fresh timestamp in this model's preferred format.
705
+		if ($timed) {
706
+			$record = $this->setTimestampsOnAttach($record);
707
+		}
708
+
709
+		return $record;
710
+	}
711
+
712
+	/**
713
+	 * Set the creation and update timestamps on an attach record.
714
+	 *
715
+	 * @param array $record
716
+	 * @param bool  $exists
717
+	 *
718
+	 * @return array
719
+	 */
720
+	protected function setTimestampsOnAttach(array $record, $exists = false)
721
+	{
722
+		$fresh = $this->freshTimestamp();
723
+
724
+		if (!$exists) {
725
+			$record[$this->createdAt()] = $fresh;
726
+		}
727
+
728
+		$record[$this->updatedAt()] = $fresh;
729
+
730
+		return $record;
731
+	}
732
+
733
+	/**
734
+	 * @param EntityCollection $entities
735
+	 *
736
+	 * @return array
737
+	 */
738
+	protected function getModelKeysFromCollection(EntityCollection $entities)
739
+	{
740
+		$keyName = $this->relatedMap->getKeyName();
741
+
742
+		return array_map(function ($m) use ($keyName) {
743
+			return $m->$keyName;
744
+		}, $entities);
745
+	}
746
+
747
+	/**
748
+	 * Detach models from the relationship.
749
+	 *
750
+	 * @param int|array $ids
751
+	 *
752
+	 * @throws \InvalidArgumentException
753
+	 *
754
+	 * @return int
755
+	 */
756
+	public function detach($ids = [])
757
+	{
758
+		if ($ids instanceof EntityCollection) {
759
+			$ids = (array) $ids->modelKeys();
760
+		}
761
+
762
+		$query = $this->newPivotQuery();
763
+
764
+		// If associated IDs were passed to the method we will only delete those
765
+		// associations, otherwise all of the association ties will be broken.
766
+		// We'll return the numbers of affected rows when we do the deletes.
767
+		$ids = (array) $ids;
768
+
769
+		if (count($ids) > 0) {
770
+			$query->whereIn($this->otherKey, (array) $ids);
771
+		}
772
+
773
+		// Once we have all of the conditions set on the statement, we are ready
774
+		// to run the delete on the pivot table. Then, if the touch parameter
775
+		// is true, we will go ahead and touch all related models to sync.
776
+		return $query->delete();
777
+	}
778
+
779
+	/**
780
+	 * Create a new query builder for the pivot table.
781
+	 *
782
+	 * @throws \InvalidArgumentException
783
+	 *
784
+	 * @return \Illuminate\Database\Query\Builder
785
+	 */
786
+	protected function newPivotQuery()
787
+	{
788
+		$query = $this->newPivotStatement();
789
+
790
+		$parentKey = $this->parentMap->getKeyName();
791
+
792
+		return $query->where($this->foreignKey, $this->parent->getEntityAttribute($parentKey));
793
+	}
794
+
795
+	/**
796
+	 * Get a new plain query builder for the pivot table.
797
+	 *
798
+	 * @return \Illuminate\Database\Query\Builder
799
+	 */
800
+	public function newPivotStatement()
801
+	{
802
+		return $this->query->getQuery()->newQuery()->from($this->table);
803
+	}
804
+
805
+	/**
806
+	 * Get a new pivot statement for a given "other" ID.
807
+	 *
808
+	 * @param mixed $id
809
+	 *
810
+	 * @throws \InvalidArgumentException
811
+	 *
812
+	 * @return \Illuminate\Database\Query\Builder
813
+	 */
814
+	public function newPivotStatementForId($id)
815
+	{
816
+		$pivot = $this->newPivotStatement();
817
+
818
+		$parentKeyName = $this->parentMap->getKeyName();
819
+
820
+		$key = $this->parent->getEntityAttribute($parentKeyName);
821
+
822
+		return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id);
823
+	}
824
+
825
+	/**
826
+	 * Create a new pivot model instance.
827
+	 *
828
+	 * @param array $attributes
829
+	 * @param bool  $exists
830
+	 *
831
+	 * @return \Analogue\ORM\Relationships\Pivot
832
+	 */
833
+	public function newPivot(array $attributes = [], $exists = false)
834
+	{
835
+		$pivot = new Pivot($this->parent, $this->parentMap, $attributes, $this->table, $exists);
836
+
837
+		return $pivot->setPivotKeys($this->foreignKey, $this->otherKey);
838
+	}
839
+
840
+	/**
841
+	 * Create a new existing pivot model instance.
842
+	 *
843
+	 * @param array $attributes
844
+	 *
845
+	 * @return \Analogue\ORM\Relationships\Pivot
846
+	 */
847
+	public function newExistingPivot(array $attributes = [])
848
+	{
849
+		return $this->newPivot($attributes, true);
850
+	}
851
+
852
+	/**
853
+	 * Set the columns on the pivot table to retrieve.
854
+	 *
855
+	 * @param array $columns
856
+	 *
857
+	 * @return $this
858
+	 */
859
+	public function withPivot($columns)
860
+	{
861
+		$columns = is_array($columns) ? $columns : func_get_args();
862
+
863
+		$this->pivotColumns = array_merge($this->pivotColumns, $columns);
864
+
865
+		return $this;
866
+	}
867
+
868
+	/**
869
+	 * Specify that the pivot table has creation and update timestamps.
870
+	 *
871
+	 * @param mixed $createdAt
872
+	 * @param mixed $updatedAt
873
+	 *
874
+	 * @return \Analogue\ORM\Relationships\BelongsToMany
875
+	 */
876
+	public function withTimestamps($createdAt = null, $updatedAt = null)
877
+	{
878
+		return $this->withPivot($createdAt ?: $this->createdAt(), $updatedAt ?: $this->updatedAt());
879
+	}
880
+
881
+	/**
882
+	 * Get the key for comparing against the parent key in "has" query.
883
+	 *
884
+	 * @return string
885
+	 */
886
+	public function getHasCompareKey()
887
+	{
888
+		return $this->getForeignKey();
889
+	}
890
+
891
+	/**
892
+	 * Get the fully qualified foreign key for the relation.
893
+	 *
894
+	 * @return string
895
+	 */
896
+	public function getForeignKey()
897
+	{
898
+		return $this->table.'.'.$this->foreignKey;
899
+	}
900
+
901
+	/**
902
+	 * Get the fully qualified "other key" for the relation.
903
+	 *
904
+	 * @return string
905
+	 */
906
+	public function getOtherKey()
907
+	{
908
+		return $this->table.'.'.$this->otherKey;
909
+	}
910
+
911
+	/**
912
+	 * Get the fully qualified parent key name.
913
+	 *
914
+	 * @return string
915
+	 */
916
+	protected function getQualifiedParentKeyName()
917
+	{
918
+		return $this->parentMap->getQualifiedKeyName();
919
+	}
920
+
921
+	/**
922
+	 * Get the intermediate table for the relationship.
923
+	 *
924
+	 * @return string
925
+	 */
926
+	public function getTable()
927
+	{
928
+		return $this->table;
929
+	}
930
+
931
+	/**
932
+	 * Get the relationship name for the relationship.
933
+	 *
934
+	 * @return string
935
+	 */
936
+	public function getRelationName()
937
+	{
938
+		return $this->relationName;
939
+	}
940 940
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -224,7 +224,7 @@  discard block
 block discarded – undo
224 224
         // release, as this is not quite relevant in a datamapper context.
225 225
         $host = $this;
226 226
 
227
-        return array_map(function ($entity) use ($host) {
227
+        return array_map(function($entity) use ($host) {
228 228
             $entityWrapper = $this->factory->make($entity);
229 229
 
230 230
             $pivot = $this->newExistingPivot($this->cleanPivotAttributes($entityWrapper));
@@ -440,7 +440,7 @@  discard block
 block discarded – undo
440 440
         // Once we have an array dictionary of child objects we can easily match the
441 441
         // children back to their parent using the dictionary and the keys on the
442 442
         // the parent models. Then we will return the hydrated models back out.
443
-        return array_map(function ($result) use ($dictionary, $keyName, $cache, $relation, $host) {
443
+        return array_map(function($result) use ($dictionary, $keyName, $cache, $relation, $host) {
444 444
             if (isset($dictionary[$key = $result[$keyName]])) {
445 445
                 $collection = $host->relatedMap->newCollection($dictionary[$key]);
446 446
 
@@ -739,7 +739,7 @@  discard block
 block discarded – undo
739 739
     {
740 740
         $keyName = $this->relatedMap->getKeyName();
741 741
 
742
-        return array_map(function ($m) use ($keyName) {
742
+        return array_map(function($m) use ($keyName) {
743 743
             return $m->$keyName;
744 744
         }, $entities);
745 745
     }
Please login to merge, or discard this patch.
src/Plugins/SoftDeletes/SoftDeletesPlugin.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -24,7 +24,7 @@  discard block
 block discarded – undo
24 24
         $host = $this;
25 25
 
26 26
         // Hook any mapper init and check the mapping include soft deletes.
27
-        $this->manager->registerGlobalEvent('initialized', function (Mapper $mapper) use ($host) {
27
+        $this->manager->registerGlobalEvent('initialized', function(Mapper $mapper) use ($host) {
28 28
             $entityMap = $mapper->getEntityMap();
29 29
 
30 30
             if ($entityMap->usesSoftDeletes()) {
@@ -52,7 +52,7 @@  discard block
 block discarded – undo
52 52
         $host = $this;
53 53
 
54 54
         // Register 'deleting' events
55
-        $mapper->registerEvent('deleting', function ($entity) use ($entityMap, $host) {
55
+        $mapper->registerEvent('deleting', function($entity) use ($entityMap, $host) {
56 56
             
57 57
             // Convert Entity into an EntityWrapper
58 58
             $factory = new Factory;
Please login to merge, or discard this patch.
Indentation   +77 added lines, -77 removed lines patch added patch discarded remove patch
@@ -13,81 +13,81 @@
 block discarded – undo
13 13
  */
14 14
 class SoftDeletesPlugin extends AnaloguePlugin
15 15
 {
16
-    /**
17
-     * Register the plugin.
18
-     *
19
-     * @throws \Exception
20
-     *
21
-     * @return void
22
-     */
23
-    public function register()
24
-    {
25
-        $host = $this;
26
-
27
-        // Hook any mapper init and check the mapping include soft deletes.
28
-        $this->manager->registerGlobalEvent('initialized', function (Mapper $mapper) use ($host) {
29
-            $entityMap = $mapper->getEntityMap();
30
-
31
-            if ($entityMap->usesSoftDeletes()) {
32
-                $host->registerSoftDelete($mapper);
33
-            }
34
-        });
35
-    }
36
-
37
-    /**
38
-     * By hooking to the mapper initialization event, we can extend it
39
-     * with the softDelete capacity.
40
-     *
41
-     * @param \Analogue\ORM\System\Mapper $mapper
42
-     *
43
-     * @throws \Analogue\ORM\Exceptions\MappingException
44
-     *
45
-     * @return bool|void
46
-     */
47
-    protected function registerSoftDelete(Mapper $mapper)
48
-    {
49
-        $entityMap = $mapper->getEntityMap();
50
-
51
-        // Add Scopes
52
-        $mapper->addGlobalScope(new SoftDeletingScope());
53
-
54
-        $host = $this;
55
-
56
-        // Register 'deleting' events
57
-        $mapper->registerEvent('deleting', function ($entity) use ($entityMap, $host) {
58
-
59
-            // Convert Entity into an EntityWrapper
60
-            $factory = new Factory();
61
-
62
-            $wrappedEntity = $factory->make($entity);
63
-
64
-            $deletedAtField = $entityMap->getQualifiedDeletedAtColumn();
65
-
66
-            if (!is_null($wrappedEntity->getEntityAttribute($deletedAtField))) {
67
-                return true;
68
-            } else {
69
-                $time = new Carbon();
70
-
71
-                $wrappedEntity->setEntityAttribute($deletedAtField, $time);
72
-
73
-                $plainObject = $wrappedEntity->getObject();
74
-                $host->manager->mapper(get_class($plainObject))->store($plainObject);
75
-
76
-                return false;
77
-            }
78
-        });
79
-
80
-        // Register RestoreCommand
81
-        $mapper->addCustomCommand('Analogue\ORM\Plugins\SoftDeletes\Restore');
82
-    }
83
-
84
-    /**
85
-     * Get custom events provided by the plugin.
86
-     *
87
-     * @return string[]
88
-     */
89
-    public function getCustomEvents()
90
-    {
91
-        return ['restoring', 'restored'];
92
-    }
16
+	/**
17
+	 * Register the plugin.
18
+	 *
19
+	 * @throws \Exception
20
+	 *
21
+	 * @return void
22
+	 */
23
+	public function register()
24
+	{
25
+		$host = $this;
26
+
27
+		// Hook any mapper init and check the mapping include soft deletes.
28
+		$this->manager->registerGlobalEvent('initialized', function (Mapper $mapper) use ($host) {
29
+			$entityMap = $mapper->getEntityMap();
30
+
31
+			if ($entityMap->usesSoftDeletes()) {
32
+				$host->registerSoftDelete($mapper);
33
+			}
34
+		});
35
+	}
36
+
37
+	/**
38
+	 * By hooking to the mapper initialization event, we can extend it
39
+	 * with the softDelete capacity.
40
+	 *
41
+	 * @param \Analogue\ORM\System\Mapper $mapper
42
+	 *
43
+	 * @throws \Analogue\ORM\Exceptions\MappingException
44
+	 *
45
+	 * @return bool|void
46
+	 */
47
+	protected function registerSoftDelete(Mapper $mapper)
48
+	{
49
+		$entityMap = $mapper->getEntityMap();
50
+
51
+		// Add Scopes
52
+		$mapper->addGlobalScope(new SoftDeletingScope());
53
+
54
+		$host = $this;
55
+
56
+		// Register 'deleting' events
57
+		$mapper->registerEvent('deleting', function ($entity) use ($entityMap, $host) {
58
+
59
+			// Convert Entity into an EntityWrapper
60
+			$factory = new Factory();
61
+
62
+			$wrappedEntity = $factory->make($entity);
63
+
64
+			$deletedAtField = $entityMap->getQualifiedDeletedAtColumn();
65
+
66
+			if (!is_null($wrappedEntity->getEntityAttribute($deletedAtField))) {
67
+				return true;
68
+			} else {
69
+				$time = new Carbon();
70
+
71
+				$wrappedEntity->setEntityAttribute($deletedAtField, $time);
72
+
73
+				$plainObject = $wrappedEntity->getObject();
74
+				$host->manager->mapper(get_class($plainObject))->store($plainObject);
75
+
76
+				return false;
77
+			}
78
+		});
79
+
80
+		// Register RestoreCommand
81
+		$mapper->addCustomCommand('Analogue\ORM\Plugins\SoftDeletes\Restore');
82
+	}
83
+
84
+	/**
85
+	 * Get custom events provided by the plugin.
86
+	 *
87
+	 * @return string[]
88
+	 */
89
+	public function getCustomEvents()
90
+	{
91
+		return ['restoring', 'restored'];
92
+	}
93 93
 }
Please login to merge, or discard this patch.
src/Plugins/Timestamps/TimestampsPlugin.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -20,11 +20,11 @@  discard block
 block discarded – undo
20 20
      */
21 21
     public function register()
22 22
     {
23
-        $this->manager->registerGlobalEvent('initialized', function (Mapper $mapper) {
23
+        $this->manager->registerGlobalEvent('initialized', function(Mapper $mapper) {
24 24
             $entityMap = $mapper->getEntityMap();
25 25
 
26 26
             if ($entityMap->usesTimestamps()) {
27
-                $mapper->registerEvent('creating', function ($entity) use ($entityMap) {
27
+                $mapper->registerEvent('creating', function($entity) use ($entityMap) {
28 28
 
29 29
                     $factory = new Factory;
30 30
                     $wrappedEntity = $factory->make($entity);
@@ -39,7 +39,7 @@  discard block
 block discarded – undo
39 39
 
40 40
                 });
41 41
 
42
-                $mapper->registerEvent('updating', function ($entity) use ($entityMap) {
42
+                $mapper->registerEvent('updating', function($entity) use ($entityMap) {
43 43
 
44 44
                     $factory = new Factory;
45 45
                     $wrappedEntity = $factory->make($entity);
Please login to merge, or discard this patch.
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -12,53 +12,53 @@
 block discarded – undo
12 12
  */
13 13
 class TimestampsPlugin extends AnaloguePlugin
14 14
 {
15
-    /**
16
-     * Register the plugin.
17
-     *
18
-     * @throws \Exception
19
-     *
20
-     * @return void
21
-     */
22
-    public function register()
23
-    {
24
-        $this->manager->registerGlobalEvent('initialized', function (Mapper $mapper) {
25
-            $entityMap = $mapper->getEntityMap();
15
+	/**
16
+	 * Register the plugin.
17
+	 *
18
+	 * @throws \Exception
19
+	 *
20
+	 * @return void
21
+	 */
22
+	public function register()
23
+	{
24
+		$this->manager->registerGlobalEvent('initialized', function (Mapper $mapper) {
25
+			$entityMap = $mapper->getEntityMap();
26 26
 
27
-            if ($entityMap->usesTimestamps()) {
28
-                $mapper->registerEvent('creating', function ($entity) use ($entityMap) {
29
-                    $factory = new Factory();
30
-                    $wrappedEntity = $factory->make($entity);
27
+			if ($entityMap->usesTimestamps()) {
28
+				$mapper->registerEvent('creating', function ($entity) use ($entityMap) {
29
+					$factory = new Factory();
30
+					$wrappedEntity = $factory->make($entity);
31 31
 
32
-                    $createdAtField = $entityMap->getCreatedAtColumn();
33
-                    $updatedAtField = $entityMap->getUpdatedAtColumn();
32
+					$createdAtField = $entityMap->getCreatedAtColumn();
33
+					$updatedAtField = $entityMap->getUpdatedAtColumn();
34 34
 
35
-                    $time = new Carbon();
35
+					$time = new Carbon();
36 36
 
37
-                    $wrappedEntity->setEntityAttribute($createdAtField, $time);
38
-                    $wrappedEntity->setEntityAttribute($updatedAtField, $time);
39
-                });
37
+					$wrappedEntity->setEntityAttribute($createdAtField, $time);
38
+					$wrappedEntity->setEntityAttribute($updatedAtField, $time);
39
+				});
40 40
 
41
-                $mapper->registerEvent('updating', function ($entity) use ($entityMap) {
42
-                    $factory = new Factory();
43
-                    $wrappedEntity = $factory->make($entity);
41
+				$mapper->registerEvent('updating', function ($entity) use ($entityMap) {
42
+					$factory = new Factory();
43
+					$wrappedEntity = $factory->make($entity);
44 44
 
45
-                    $updatedAtField = $entityMap->getUpdatedAtColumn();
45
+					$updatedAtField = $entityMap->getUpdatedAtColumn();
46 46
 
47
-                    $time = new Carbon();
47
+					$time = new Carbon();
48 48
 
49
-                    $wrappedEntity->setEntityAttribute($updatedAtField, $time);
50
-                });
51
-            }
52
-        });
53
-    }
49
+					$wrappedEntity->setEntityAttribute($updatedAtField, $time);
50
+				});
51
+			}
52
+		});
53
+	}
54 54
 
55
-    /**
56
-     * Get custom events provided by the plugin.
57
-     *
58
-     * @return array
59
-     */
60
-    public function getCustomEvents()
61
-    {
62
-        return [];
63
-    }
55
+	/**
56
+	 * Get custom events provided by the plugin.
57
+	 *
58
+	 * @return array
59
+	 */
60
+	public function getCustomEvents()
61
+	{
62
+		return [];
63
+	}
64 64
 }
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/Drivers/IlluminateDBAdapter.php 2 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -27,7 +27,7 @@
 block discarded – undo
27 27
     /**
28 28
      * Return a new Query instance for this driver
29 29
      *
30
-     * @return QueryAdapter
30
+     * @return IlluminateQueryBuilder
31 31
      */
32 32
     public function getQuery()
33 33
     {
Please login to merge, or discard this patch.
Indentation   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -10,72 +10,72 @@
 block discarded – undo
10 10
  */
11 11
 class IlluminateDBAdapter implements DBAdapter
12 12
 {
13
-    /**
14
-     * @var Connection
15
-     */
16
-    protected $connection;
13
+	/**
14
+	 * @var Connection
15
+	 */
16
+	protected $connection;
17 17
 
18
-    /**
19
-     * IlluminateDBAdapter constructor.
20
-     *
21
-     * @param Connection $connection
22
-     */
23
-    public function __construct(Connection $connection)
24
-    {
25
-        $this->connection = $connection;
26
-    }
18
+	/**
19
+	 * IlluminateDBAdapter constructor.
20
+	 *
21
+	 * @param Connection $connection
22
+	 */
23
+	public function __construct(Connection $connection)
24
+	{
25
+		$this->connection = $connection;
26
+	}
27 27
 
28
-    /**
29
-     * Return a new Query instance for this driver.
30
-     *
31
-     * @return QueryAdapter
32
-     */
33
-    public function getQuery()
34
-    {
35
-        $connection = $this->connection;
28
+	/**
29
+	 * Return a new Query instance for this driver.
30
+	 *
31
+	 * @return QueryAdapter
32
+	 */
33
+	public function getQuery()
34
+	{
35
+		$connection = $this->connection;
36 36
 
37
-        $grammar = $connection->getQueryGrammar();
37
+		$grammar = $connection->getQueryGrammar();
38 38
 
39
-        return new IlluminateQueryBuilder($connection, $grammar, $connection->getPostProcessor());
40
-    }
39
+		return new IlluminateQueryBuilder($connection, $grammar, $connection->getPostProcessor());
40
+	}
41 41
 
42
-    /**
43
-     * Get the date format supported by the current connection.
44
-     *
45
-     * @return string
46
-     */
47
-    public function getDateFormat()
48
-    {
49
-        return $this->connection->getQueryGrammar()->getDateFormat();
50
-    }
42
+	/**
43
+	 * Get the date format supported by the current connection.
44
+	 *
45
+	 * @return string
46
+	 */
47
+	public function getDateFormat()
48
+	{
49
+		return $this->connection->getQueryGrammar()->getDateFormat();
50
+	}
51 51
 
52
-    /**
53
-     * Start a DB transaction on driver that supports it.
54
-     *
55
-     * @return void
56
-     */
57
-    public function beginTransaction()
58
-    {
59
-        $this->connection->beginTransaction();
60
-    }
52
+	/**
53
+	 * Start a DB transaction on driver that supports it.
54
+	 *
55
+	 * @return void
56
+	 */
57
+	public function beginTransaction()
58
+	{
59
+		$this->connection->beginTransaction();
60
+	}
61 61
 
62
-    /**
63
-     * Commit a DB transaction on driver that supports it.
64
-     *
65
-     * @return void
66
-     */
67
-    public function commit()
68
-    {
69
-        $this->connection->commit();
70
-    }
62
+	/**
63
+	 * Commit a DB transaction on driver that supports it.
64
+	 *
65
+	 * @return void
66
+	 */
67
+	public function commit()
68
+	{
69
+		$this->connection->commit();
70
+	}
71 71
 
72
-    /**
73
-     * Rollback a DB transaction.
74
-     *
75
-     * @return void
76
-     */
77
-    public function rollback()
78
-    {
79
-        $this->connection->rollBack();
80
-    }
72
+	/**
73
+	 * Rollback a DB transaction.
74
+	 *
75
+	 * @return void
76
+	 */
77
+	public function rollback()
78
+	{
79
+		$this->connection->rollBack();
80
+	}
81 81
 }
Please login to merge, or discard this patch.
src/System/Mapper.php 3 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -177,7 +177,7 @@
 block discarded – undo
177 177
     /**
178 178
      * Get a the Underlying QueryAdapter.
179 179
      *
180
-     * @return \Analogue\ORM\Drivers\QueryAdapter
180
+     * @return \Illuminate\Database\Query\Builder
181 181
      */
182 182
     public function newQueryBuilder()
183 183
     {
Please login to merge, or discard this patch.
Indentation   +608 added lines, -608 removed lines patch added patch discarded remove patch
@@ -25,612 +25,612 @@
 block discarded – undo
25 25
  */
26 26
 class Mapper
27 27
 {
28
-    /**
29
-     * The Manager instance.
30
-     *
31
-     * @var \Analogue\ORM\System\Manager
32
-     */
33
-    protected $manager;
34
-
35
-    /**
36
-     * Instance of EntityMapper Object.
37
-     *
38
-     * @var \Analogue\ORM\EntityMap
39
-     */
40
-    protected $entityMap;
41
-
42
-    /**
43
-     * The instance of db adapter.
44
-     *
45
-     * @var \Analogue\ORM\Drivers\DBAdapter
46
-     */
47
-    protected $adapter;
48
-
49
-    /**
50
-     * Event dispatcher instance.
51
-     *
52
-     * @var \Illuminate\Contracts\Events\Dispatcher
53
-     */
54
-    protected $dispatcher;
55
-
56
-    /**
57
-     * Entity Cache.
58
-     *
59
-     * @var \Analogue\ORM\System\EntityCache
60
-     */
61
-    protected $cache;
62
-
63
-    /**
64
-     * Global scopes.
65
-     *
66
-     * @var array
67
-     */
68
-    protected $globalScopes = [];
69
-
70
-    /**
71
-     * Custom Commands.
72
-     *
73
-     * @var array
74
-     */
75
-    protected $customCommands = [];
76
-
77
-    /**
78
-     * @param EntityMap  $entityMap
79
-     * @param DBAdapter  $adapter
80
-     * @param Dispatcher $dispatcher
81
-     * @param Manager    $manager
82
-     */
83
-    public function __construct(EntityMap $entityMap, DBAdapter $adapter, Dispatcher $dispatcher, Manager $manager)
84
-    {
85
-        $this->entityMap = $entityMap;
86
-
87
-        $this->adapter = $adapter;
88
-
89
-        $this->dispatcher = $dispatcher;
90
-
91
-        $this->manager = $manager;
92
-
93
-        $this->cache = new EntityCache($entityMap);
94
-    }
95
-
96
-    /**
97
-     * Map results to a Collection.
98
-     *
99
-     * @param Collection $results
100
-     *
101
-     * @return Collection
102
-     */
103
-    public function map($results)
104
-    {
105
-        // To implement, will allow decoupling result
106
-        // instantiation from Query class
107
-    }
108
-
109
-    /**
110
-     * Return all records for a mapped object.
111
-     *
112
-     * @return EntityCollection
113
-     */
114
-    public function all()
115
-    {
116
-        return $this->query()->get();
117
-    }
118
-
119
-    /**
120
-     * Persist an entity or an entity collection into the database.
121
-     *
122
-     * @param Mappable|\Traversable|array $entity
123
-     *
124
-     * @throws \InvalidArgumentException
125
-     * @throws MappingException
126
-     *
127
-     * @return Mappable|\Traversable|array
128
-     */
129
-    public function store($entity)
130
-    {
131
-        if ($this->manager->isTraversable($entity)) {
132
-            return $this->storeCollection($entity);
133
-        } else {
134
-            return $this->storeEntity($entity);
135
-        }
136
-    }
137
-
138
-    /**
139
-     * Store an entity collection inside a single DB Transaction.
140
-     *
141
-     * @param \Traversable|array $entities
142
-     *
143
-     * @throws \InvalidArgumentException
144
-     * @throws MappingException
145
-     *
146
-     * @return \Traversable|array
147
-     */
148
-    protected function storeCollection($entities)
149
-    {
150
-        $this->adapter->beginTransaction();
151
-
152
-        foreach ($entities as $entity) {
153
-            $this->storeEntity($entity);
154
-        }
155
-
156
-        $this->adapter->commit();
157
-
158
-        return $entities;
159
-    }
160
-
161
-    /**
162
-     * Store a single entity into the database.
163
-     *
164
-     * @param Mappable $entity
165
-     *
166
-     * @throws \InvalidArgumentException
167
-     * @throws MappingException
168
-     *
169
-     * @return \Analogue\ORM\Entity
170
-     */
171
-    protected function storeEntity($entity)
172
-    {
173
-        $this->checkEntityType($entity);
174
-
175
-        $store = new Store($this->aggregate($entity), $this->newQueryBuilder());
176
-
177
-        return $store->execute();
178
-    }
179
-
180
-    /**
181
-     * Check that the entity correspond to the current mapper.
182
-     *
183
-     * @param mixed $entity
184
-     *
185
-     * @throws InvalidArgumentException
186
-     *
187
-     * @return void
188
-     */
189
-    protected function checkEntityType($entity)
190
-    {
191
-        if (get_class($entity) != $this->entityMap->getClass() && !is_subclass_of($entity, $this->entityMap->getClass())) {
192
-            $expected = $this->entityMap->getClass();
193
-            $actual = get_class($entity);
194
-            throw new InvalidArgumentException("Expected : $expected, got $actual.");
195
-        }
196
-    }
197
-
198
-    /**
199
-     * Convert an entity into an aggregate root.
200
-     *
201
-     * @param mixed $entity
202
-     *
203
-     * @throws MappingException
204
-     *
205
-     * @return \Analogue\ORM\System\Aggregate
206
-     */
207
-    protected function aggregate($entity)
208
-    {
209
-        return new Aggregate($entity);
210
-    }
211
-
212
-    /**
213
-     * Get a the Underlying QueryAdapter.
214
-     *
215
-     * @return \Analogue\ORM\Drivers\QueryAdapter
216
-     */
217
-    public function newQueryBuilder()
218
-    {
219
-        return $this->adapter->getQuery();
220
-    }
221
-
222
-    /**
223
-     * Delete an entity or an entity collection from the database.
224
-     *
225
-     * @param  Mappable|\Traversable|array
226
-     *
227
-     * @throws MappingException
228
-     * @throws \InvalidArgumentException
229
-     *
230
-     * @return \Traversable|array
231
-     */
232
-    public function delete($entity)
233
-    {
234
-        if ($this->manager->isTraversable($entity)) {
235
-            return $this->deleteCollection($entity);
236
-        } else {
237
-            $this->deleteEntity($entity);
238
-        }
239
-    }
240
-
241
-    /**
242
-     * Delete an Entity Collection inside a single db transaction.
243
-     *
244
-     * @param \Traversable|array $entities
245
-     *
246
-     * @throws \InvalidArgumentException
247
-     * @throws MappingException
248
-     *
249
-     * @return \Traversable|array
250
-     */
251
-    protected function deleteCollection($entities)
252
-    {
253
-        $this->adapter->beginTransaction();
254
-
255
-        foreach ($entities as $entity) {
256
-            $this->deleteEntity($entity);
257
-        }
258
-
259
-        $this->adapter->commit();
260
-
261
-        return $entities;
262
-    }
263
-
264
-    /**
265
-     * Delete a single entity from the database.
266
-     *
267
-     * @param Mappable $entity
268
-     *
269
-     * @throws \InvalidArgumentException
270
-     * @throws MappingException
271
-     *
272
-     * @return void
273
-     */
274
-    protected function deleteEntity($entity)
275
-    {
276
-        $this->checkEntityType($entity);
277
-
278
-        $delete = new Delete($this->aggregate($entity), $this->newQueryBuilder());
279
-
280
-        $delete->execute();
281
-    }
282
-
283
-    /**
284
-     * Return the entity map for this mapper.
285
-     *
286
-     * @return EntityMap
287
-     */
288
-    public function getEntityMap()
289
-    {
290
-        return $this->entityMap;
291
-    }
292
-
293
-    /**
294
-     * Get the entity cache for the current mapper.
295
-     *
296
-     * @return EntityCache $entityCache
297
-     */
298
-    public function getEntityCache()
299
-    {
300
-        return $this->cache;
301
-    }
302
-
303
-    /**
304
-     * Fire the given event for the entity.
305
-     *
306
-     * @param string               $event
307
-     * @param \Analogue\ORM\Entity $entity
308
-     * @param bool                 $halt
309
-     *
310
-     * @throws InvalidArgumentException
311
-     *
312
-     * @return mixed
313
-     */
314
-    public function fireEvent($event, $entity, $halt = true)
315
-    {
316
-        if ($entity instanceof Wrapper) {
317
-            throw new InvalidArgumentException('Fired Event with invalid Entity Object');
318
-        }
319
-
320
-        $event = "analogue.{$event}.".$this->entityMap->getClass();
321
-
322
-        $method = $halt ? 'until' : 'fire';
323
-
324
-        return $this->dispatcher->$method($event, $entity);
325
-    }
326
-
327
-    /**
328
-     * Register an entity event with the dispatcher.
329
-     *
330
-     * @param string   $event
331
-     * @param \Closure $callback
332
-     *
333
-     * @return void
334
-     */
335
-    public function registerEvent($event, $callback)
336
-    {
337
-        $name = $this->entityMap->getClass();
338
-
339
-        $this->dispatcher->listen("analogue.{$event}.{$name}", $callback);
340
-    }
341
-
342
-    /**
343
-     * Add a global scope to this mapper query builder.
344
-     *
345
-     * @param ScopeInterface $scope
346
-     *
347
-     * @return void
348
-     */
349
-    public function addGlobalScope(ScopeInterface $scope)
350
-    {
351
-        $this->globalScopes[get_class($scope)] = $scope;
352
-    }
353
-
354
-    /**
355
-     * Determine if the mapper has a global scope.
356
-     *
357
-     * @param \Analogue\ORM\System\ScopeInterface $scope
358
-     *
359
-     * @return bool
360
-     */
361
-    public function hasGlobalScope($scope)
362
-    {
363
-        return !is_null($this->getGlobalScope($scope));
364
-    }
365
-
366
-    /**
367
-     * Get a global scope registered with the modal.
368
-     *
369
-     * @param \Analogue\ORM\System\ScopeInterface $scope
370
-     *
371
-     * @return \Analogue\ORM\System\ScopeInterface|null
372
-     */
373
-    public function getGlobalScope($scope)
374
-    {
375
-        return array_first($this->globalScopes, function ($key, $value) use ($scope) {
376
-            return $scope instanceof $value;
377
-        });
378
-    }
379
-
380
-    /**
381
-     * Get a new query instance without a given scope.
382
-     *
383
-     * @param \Analogue\ORM\System\ScopeInterface $scope
384
-     *
385
-     * @return \Analogue\ORM\System\Query
386
-     */
387
-    public function newQueryWithoutScope($scope)
388
-    {
389
-        $this->getGlobalScope($scope)->remove($query = $this->getQuery(), $this);
390
-
391
-        return $query;
392
-    }
393
-
394
-    /**
395
-     * Get the Analogue Query Builder for this instance.
396
-     *
397
-     * @return \Analogue\ORM\System\Query
398
-     */
399
-    public function getQuery()
400
-    {
401
-        $query = new Query($this, $this->adapter);
402
-
403
-        return $this->applyGlobalScopes($query);
404
-    }
405
-
406
-    /**
407
-     * Apply all of the global scopes to an Analogue Query builder.
408
-     *
409
-     * @param Query $query
410
-     *
411
-     * @return \Analogue\ORM\System\Query
412
-     */
413
-    public function applyGlobalScopes($query)
414
-    {
415
-        foreach ($this->getGlobalScopes() as $scope) {
416
-            $scope->apply($query, $this);
417
-        }
418
-
419
-        return $query;
420
-    }
421
-
422
-    /**
423
-     * Get the global scopes for this class instance.
424
-     *
425
-     * @return \Analogue\ORM\System\ScopeInterface
426
-     */
427
-    public function getGlobalScopes()
428
-    {
429
-        return $this->globalScopes;
430
-    }
431
-
432
-    /**
433
-     * Add a dynamic method that extends the mapper/repository.
434
-     *
435
-     * @param string $command
436
-     */
437
-    public function addCustomCommand($command)
438
-    {
439
-        $name = lcfirst(class_basename($command));
440
-
441
-        $this->customCommands[$name] = $command;
442
-    }
443
-
444
-    /**
445
-     * Create a new instance of the mapped entity class.
446
-     *
447
-     * @return mixed
448
-     */
449
-    public function newInstance()
450
-    {
451
-        $class = $this->entityMap->getClass();
452
-
453
-        if ($this->entityMap->useDependencyInjection()) {
454
-            return $this->newInstanceUsingDependencyInjection($class);
455
-        }
456
-
457
-        return $this->newInstanceUsingInstantiator($class);
458
-    }
459
-
460
-    /**
461
-     * Return a new object instance using dependency injection.
462
-     *
463
-     * @param string $class
464
-     *
465
-     * @return mixed
466
-     */
467
-    protected function newInstanceUsingDependencyInjection($class)
468
-    {
469
-        if (!class_exists(Container::class)) {
470
-            throw new ErrorException("Illuminate\Container\Container is required to use Dependency Injection");
471
-        }
472
-
473
-        return Container::getInstance()->make($class);
474
-    }
475
-
476
-    /**
477
-     * Return a new object instance using doctrine's instantiator.
478
-     *
479
-     * @param string $class
480
-     *
481
-     * @return mixed
482
-     */
483
-    protected function newInstanceUsingInstantiator($class)
484
-    {
485
-        $instantiator = new \Doctrine\Instantiator\Instantiator();
486
-
487
-        return $instantiator->instantiate($class);
488
-    }
489
-
490
-    /**
491
-     * Get an unscoped Analogue Query Builder for this instance.
492
-     *
493
-     * @return \Analogue\ORM\System\Query
494
-     */
495
-    public function globalQuery()
496
-    {
497
-        return $this->newQueryWithoutScopes();
498
-    }
499
-
500
-    /**
501
-     * Get a new query builder that doesn't have any global scopes.
502
-     *
503
-     * @return Query
504
-     */
505
-    public function newQueryWithoutScopes()
506
-    {
507
-        return $this->removeGlobalScopes($this->getQuery());
508
-    }
509
-
510
-    /**
511
-     * Remove all of the global scopes from an Analogue Query builder.
512
-     *
513
-     * @param Query $query
514
-     *
515
-     * @return \Analogue\ORM\System\Query
516
-     */
517
-    public function removeGlobalScopes($query)
518
-    {
519
-        foreach ($this->getGlobalScopes() as $scope) {
520
-            $scope->remove($query, $this);
521
-        }
522
-
523
-        return $query;
524
-    }
525
-
526
-    /**
527
-     * Return the manager instance.
528
-     *
529
-     * @return \Analogue\ORM\System\Manager
530
-     */
531
-    public function getManager()
532
-    {
533
-        return $this->manager;
534
-    }
535
-
536
-    /**
537
-     * Dynamically handle calls to custom commands, or Redirects to query().
538
-     *
539
-     * @param string $method
540
-     * @param array  $parameters
541
-     *
542
-     * @throws \Exception
543
-     *
544
-     * @return mixed
545
-     */
546
-    public function __call($method, $parameters)
547
-    {
548
-        // Check if method is a custom command on the mapper
549
-        if ($this->hasCustomCommand($method)) {
550
-            if (count($parameters) == 0) {
551
-                throw new \Exception("$method must at least have 1 argument");
552
-            }
553
-
554
-            return $this->executeCustomCommand($method, $parameters[0]);
555
-        }
556
-
557
-        // Redirect call on a new query instance
558
-        return call_user_func_array([$this->query(), $method], $parameters);
559
-    }
560
-
561
-    /**
562
-     * Check if this mapper supports this command.
563
-     *
564
-     * @param string $command
565
-     *
566
-     * @return bool
567
-     */
568
-    public function hasCustomCommand($command)
569
-    {
570
-        return in_array($command, $this->getCustomCommands());
571
-    }
572
-
573
-    /**
574
-     * Get all the custom commands registered on this mapper.
575
-     *
576
-     * @return array
577
-     */
578
-    public function getCustomCommands()
579
-    {
580
-        return array_keys($this->customCommands);
581
-    }
582
-
583
-    /**
584
-     * Execute a custom command on an Entity.
585
-     *
586
-     * @param string                 $command
587
-     * @param mixed|Collection|array $entity
588
-     *
589
-     * @throws \InvalidArgumentException
590
-     * @throws MappingException
591
-     *
592
-     * @return mixed
593
-     */
594
-    public function executeCustomCommand($command, $entity)
595
-    {
596
-        $commandClass = $this->customCommands[$command];
597
-
598
-        if ($this->manager->isTraversable($entity)) {
599
-            foreach ($entity as $instance) {
600
-                $this->executeSingleCustomCommand($commandClass, $instance);
601
-            }
602
-        } else {
603
-            return $this->executeSingleCustomCommand($commandClass, $entity);
604
-        }
605
-    }
606
-
607
-    /**
608
-     * Execute a single command instance.
609
-     *
610
-     * @param string $commandClass
611
-     * @param mixed  $entity
612
-     *
613
-     * @throws \InvalidArgumentException
614
-     * @throws MappingException
615
-     *
616
-     * @return mixed
617
-     */
618
-    protected function executeSingleCustomCommand($commandClass, $entity)
619
-    {
620
-        $this->checkEntityType($entity);
621
-
622
-        $instance = new $commandClass($this->aggregate($entity), $this->newQueryBuilder());
623
-
624
-        return $instance->execute();
625
-    }
626
-
627
-    /**
628
-     * Get the Analogue Query Builder for this instance.
629
-     *
630
-     * @return \Analogue\ORM\System\Query
631
-     */
632
-    public function query()
633
-    {
634
-        return $this->getQuery();
635
-    }
28
+	/**
29
+	 * The Manager instance.
30
+	 *
31
+	 * @var \Analogue\ORM\System\Manager
32
+	 */
33
+	protected $manager;
34
+
35
+	/**
36
+	 * Instance of EntityMapper Object.
37
+	 *
38
+	 * @var \Analogue\ORM\EntityMap
39
+	 */
40
+	protected $entityMap;
41
+
42
+	/**
43
+	 * The instance of db adapter.
44
+	 *
45
+	 * @var \Analogue\ORM\Drivers\DBAdapter
46
+	 */
47
+	protected $adapter;
48
+
49
+	/**
50
+	 * Event dispatcher instance.
51
+	 *
52
+	 * @var \Illuminate\Contracts\Events\Dispatcher
53
+	 */
54
+	protected $dispatcher;
55
+
56
+	/**
57
+	 * Entity Cache.
58
+	 *
59
+	 * @var \Analogue\ORM\System\EntityCache
60
+	 */
61
+	protected $cache;
62
+
63
+	/**
64
+	 * Global scopes.
65
+	 *
66
+	 * @var array
67
+	 */
68
+	protected $globalScopes = [];
69
+
70
+	/**
71
+	 * Custom Commands.
72
+	 *
73
+	 * @var array
74
+	 */
75
+	protected $customCommands = [];
76
+
77
+	/**
78
+	 * @param EntityMap  $entityMap
79
+	 * @param DBAdapter  $adapter
80
+	 * @param Dispatcher $dispatcher
81
+	 * @param Manager    $manager
82
+	 */
83
+	public function __construct(EntityMap $entityMap, DBAdapter $adapter, Dispatcher $dispatcher, Manager $manager)
84
+	{
85
+		$this->entityMap = $entityMap;
86
+
87
+		$this->adapter = $adapter;
88
+
89
+		$this->dispatcher = $dispatcher;
90
+
91
+		$this->manager = $manager;
92
+
93
+		$this->cache = new EntityCache($entityMap);
94
+	}
95
+
96
+	/**
97
+	 * Map results to a Collection.
98
+	 *
99
+	 * @param Collection $results
100
+	 *
101
+	 * @return Collection
102
+	 */
103
+	public function map($results)
104
+	{
105
+		// To implement, will allow decoupling result
106
+		// instantiation from Query class
107
+	}
108
+
109
+	/**
110
+	 * Return all records for a mapped object.
111
+	 *
112
+	 * @return EntityCollection
113
+	 */
114
+	public function all()
115
+	{
116
+		return $this->query()->get();
117
+	}
118
+
119
+	/**
120
+	 * Persist an entity or an entity collection into the database.
121
+	 *
122
+	 * @param Mappable|\Traversable|array $entity
123
+	 *
124
+	 * @throws \InvalidArgumentException
125
+	 * @throws MappingException
126
+	 *
127
+	 * @return Mappable|\Traversable|array
128
+	 */
129
+	public function store($entity)
130
+	{
131
+		if ($this->manager->isTraversable($entity)) {
132
+			return $this->storeCollection($entity);
133
+		} else {
134
+			return $this->storeEntity($entity);
135
+		}
136
+	}
137
+
138
+	/**
139
+	 * Store an entity collection inside a single DB Transaction.
140
+	 *
141
+	 * @param \Traversable|array $entities
142
+	 *
143
+	 * @throws \InvalidArgumentException
144
+	 * @throws MappingException
145
+	 *
146
+	 * @return \Traversable|array
147
+	 */
148
+	protected function storeCollection($entities)
149
+	{
150
+		$this->adapter->beginTransaction();
151
+
152
+		foreach ($entities as $entity) {
153
+			$this->storeEntity($entity);
154
+		}
155
+
156
+		$this->adapter->commit();
157
+
158
+		return $entities;
159
+	}
160
+
161
+	/**
162
+	 * Store a single entity into the database.
163
+	 *
164
+	 * @param Mappable $entity
165
+	 *
166
+	 * @throws \InvalidArgumentException
167
+	 * @throws MappingException
168
+	 *
169
+	 * @return \Analogue\ORM\Entity
170
+	 */
171
+	protected function storeEntity($entity)
172
+	{
173
+		$this->checkEntityType($entity);
174
+
175
+		$store = new Store($this->aggregate($entity), $this->newQueryBuilder());
176
+
177
+		return $store->execute();
178
+	}
179
+
180
+	/**
181
+	 * Check that the entity correspond to the current mapper.
182
+	 *
183
+	 * @param mixed $entity
184
+	 *
185
+	 * @throws InvalidArgumentException
186
+	 *
187
+	 * @return void
188
+	 */
189
+	protected function checkEntityType($entity)
190
+	{
191
+		if (get_class($entity) != $this->entityMap->getClass() && !is_subclass_of($entity, $this->entityMap->getClass())) {
192
+			$expected = $this->entityMap->getClass();
193
+			$actual = get_class($entity);
194
+			throw new InvalidArgumentException("Expected : $expected, got $actual.");
195
+		}
196
+	}
197
+
198
+	/**
199
+	 * Convert an entity into an aggregate root.
200
+	 *
201
+	 * @param mixed $entity
202
+	 *
203
+	 * @throws MappingException
204
+	 *
205
+	 * @return \Analogue\ORM\System\Aggregate
206
+	 */
207
+	protected function aggregate($entity)
208
+	{
209
+		return new Aggregate($entity);
210
+	}
211
+
212
+	/**
213
+	 * Get a the Underlying QueryAdapter.
214
+	 *
215
+	 * @return \Analogue\ORM\Drivers\QueryAdapter
216
+	 */
217
+	public function newQueryBuilder()
218
+	{
219
+		return $this->adapter->getQuery();
220
+	}
221
+
222
+	/**
223
+	 * Delete an entity or an entity collection from the database.
224
+	 *
225
+	 * @param  Mappable|\Traversable|array
226
+	 *
227
+	 * @throws MappingException
228
+	 * @throws \InvalidArgumentException
229
+	 *
230
+	 * @return \Traversable|array
231
+	 */
232
+	public function delete($entity)
233
+	{
234
+		if ($this->manager->isTraversable($entity)) {
235
+			return $this->deleteCollection($entity);
236
+		} else {
237
+			$this->deleteEntity($entity);
238
+		}
239
+	}
240
+
241
+	/**
242
+	 * Delete an Entity Collection inside a single db transaction.
243
+	 *
244
+	 * @param \Traversable|array $entities
245
+	 *
246
+	 * @throws \InvalidArgumentException
247
+	 * @throws MappingException
248
+	 *
249
+	 * @return \Traversable|array
250
+	 */
251
+	protected function deleteCollection($entities)
252
+	{
253
+		$this->adapter->beginTransaction();
254
+
255
+		foreach ($entities as $entity) {
256
+			$this->deleteEntity($entity);
257
+		}
258
+
259
+		$this->adapter->commit();
260
+
261
+		return $entities;
262
+	}
263
+
264
+	/**
265
+	 * Delete a single entity from the database.
266
+	 *
267
+	 * @param Mappable $entity
268
+	 *
269
+	 * @throws \InvalidArgumentException
270
+	 * @throws MappingException
271
+	 *
272
+	 * @return void
273
+	 */
274
+	protected function deleteEntity($entity)
275
+	{
276
+		$this->checkEntityType($entity);
277
+
278
+		$delete = new Delete($this->aggregate($entity), $this->newQueryBuilder());
279
+
280
+		$delete->execute();
281
+	}
282
+
283
+	/**
284
+	 * Return the entity map for this mapper.
285
+	 *
286
+	 * @return EntityMap
287
+	 */
288
+	public function getEntityMap()
289
+	{
290
+		return $this->entityMap;
291
+	}
292
+
293
+	/**
294
+	 * Get the entity cache for the current mapper.
295
+	 *
296
+	 * @return EntityCache $entityCache
297
+	 */
298
+	public function getEntityCache()
299
+	{
300
+		return $this->cache;
301
+	}
302
+
303
+	/**
304
+	 * Fire the given event for the entity.
305
+	 *
306
+	 * @param string               $event
307
+	 * @param \Analogue\ORM\Entity $entity
308
+	 * @param bool                 $halt
309
+	 *
310
+	 * @throws InvalidArgumentException
311
+	 *
312
+	 * @return mixed
313
+	 */
314
+	public function fireEvent($event, $entity, $halt = true)
315
+	{
316
+		if ($entity instanceof Wrapper) {
317
+			throw new InvalidArgumentException('Fired Event with invalid Entity Object');
318
+		}
319
+
320
+		$event = "analogue.{$event}.".$this->entityMap->getClass();
321
+
322
+		$method = $halt ? 'until' : 'fire';
323
+
324
+		return $this->dispatcher->$method($event, $entity);
325
+	}
326
+
327
+	/**
328
+	 * Register an entity event with the dispatcher.
329
+	 *
330
+	 * @param string   $event
331
+	 * @param \Closure $callback
332
+	 *
333
+	 * @return void
334
+	 */
335
+	public function registerEvent($event, $callback)
336
+	{
337
+		$name = $this->entityMap->getClass();
338
+
339
+		$this->dispatcher->listen("analogue.{$event}.{$name}", $callback);
340
+	}
341
+
342
+	/**
343
+	 * Add a global scope to this mapper query builder.
344
+	 *
345
+	 * @param ScopeInterface $scope
346
+	 *
347
+	 * @return void
348
+	 */
349
+	public function addGlobalScope(ScopeInterface $scope)
350
+	{
351
+		$this->globalScopes[get_class($scope)] = $scope;
352
+	}
353
+
354
+	/**
355
+	 * Determine if the mapper has a global scope.
356
+	 *
357
+	 * @param \Analogue\ORM\System\ScopeInterface $scope
358
+	 *
359
+	 * @return bool
360
+	 */
361
+	public function hasGlobalScope($scope)
362
+	{
363
+		return !is_null($this->getGlobalScope($scope));
364
+	}
365
+
366
+	/**
367
+	 * Get a global scope registered with the modal.
368
+	 *
369
+	 * @param \Analogue\ORM\System\ScopeInterface $scope
370
+	 *
371
+	 * @return \Analogue\ORM\System\ScopeInterface|null
372
+	 */
373
+	public function getGlobalScope($scope)
374
+	{
375
+		return array_first($this->globalScopes, function ($key, $value) use ($scope) {
376
+			return $scope instanceof $value;
377
+		});
378
+	}
379
+
380
+	/**
381
+	 * Get a new query instance without a given scope.
382
+	 *
383
+	 * @param \Analogue\ORM\System\ScopeInterface $scope
384
+	 *
385
+	 * @return \Analogue\ORM\System\Query
386
+	 */
387
+	public function newQueryWithoutScope($scope)
388
+	{
389
+		$this->getGlobalScope($scope)->remove($query = $this->getQuery(), $this);
390
+
391
+		return $query;
392
+	}
393
+
394
+	/**
395
+	 * Get the Analogue Query Builder for this instance.
396
+	 *
397
+	 * @return \Analogue\ORM\System\Query
398
+	 */
399
+	public function getQuery()
400
+	{
401
+		$query = new Query($this, $this->adapter);
402
+
403
+		return $this->applyGlobalScopes($query);
404
+	}
405
+
406
+	/**
407
+	 * Apply all of the global scopes to an Analogue Query builder.
408
+	 *
409
+	 * @param Query $query
410
+	 *
411
+	 * @return \Analogue\ORM\System\Query
412
+	 */
413
+	public function applyGlobalScopes($query)
414
+	{
415
+		foreach ($this->getGlobalScopes() as $scope) {
416
+			$scope->apply($query, $this);
417
+		}
418
+
419
+		return $query;
420
+	}
421
+
422
+	/**
423
+	 * Get the global scopes for this class instance.
424
+	 *
425
+	 * @return \Analogue\ORM\System\ScopeInterface
426
+	 */
427
+	public function getGlobalScopes()
428
+	{
429
+		return $this->globalScopes;
430
+	}
431
+
432
+	/**
433
+	 * Add a dynamic method that extends the mapper/repository.
434
+	 *
435
+	 * @param string $command
436
+	 */
437
+	public function addCustomCommand($command)
438
+	{
439
+		$name = lcfirst(class_basename($command));
440
+
441
+		$this->customCommands[$name] = $command;
442
+	}
443
+
444
+	/**
445
+	 * Create a new instance of the mapped entity class.
446
+	 *
447
+	 * @return mixed
448
+	 */
449
+	public function newInstance()
450
+	{
451
+		$class = $this->entityMap->getClass();
452
+
453
+		if ($this->entityMap->useDependencyInjection()) {
454
+			return $this->newInstanceUsingDependencyInjection($class);
455
+		}
456
+
457
+		return $this->newInstanceUsingInstantiator($class);
458
+	}
459
+
460
+	/**
461
+	 * Return a new object instance using dependency injection.
462
+	 *
463
+	 * @param string $class
464
+	 *
465
+	 * @return mixed
466
+	 */
467
+	protected function newInstanceUsingDependencyInjection($class)
468
+	{
469
+		if (!class_exists(Container::class)) {
470
+			throw new ErrorException("Illuminate\Container\Container is required to use Dependency Injection");
471
+		}
472
+
473
+		return Container::getInstance()->make($class);
474
+	}
475
+
476
+	/**
477
+	 * Return a new object instance using doctrine's instantiator.
478
+	 *
479
+	 * @param string $class
480
+	 *
481
+	 * @return mixed
482
+	 */
483
+	protected function newInstanceUsingInstantiator($class)
484
+	{
485
+		$instantiator = new \Doctrine\Instantiator\Instantiator();
486
+
487
+		return $instantiator->instantiate($class);
488
+	}
489
+
490
+	/**
491
+	 * Get an unscoped Analogue Query Builder for this instance.
492
+	 *
493
+	 * @return \Analogue\ORM\System\Query
494
+	 */
495
+	public function globalQuery()
496
+	{
497
+		return $this->newQueryWithoutScopes();
498
+	}
499
+
500
+	/**
501
+	 * Get a new query builder that doesn't have any global scopes.
502
+	 *
503
+	 * @return Query
504
+	 */
505
+	public function newQueryWithoutScopes()
506
+	{
507
+		return $this->removeGlobalScopes($this->getQuery());
508
+	}
509
+
510
+	/**
511
+	 * Remove all of the global scopes from an Analogue Query builder.
512
+	 *
513
+	 * @param Query $query
514
+	 *
515
+	 * @return \Analogue\ORM\System\Query
516
+	 */
517
+	public function removeGlobalScopes($query)
518
+	{
519
+		foreach ($this->getGlobalScopes() as $scope) {
520
+			$scope->remove($query, $this);
521
+		}
522
+
523
+		return $query;
524
+	}
525
+
526
+	/**
527
+	 * Return the manager instance.
528
+	 *
529
+	 * @return \Analogue\ORM\System\Manager
530
+	 */
531
+	public function getManager()
532
+	{
533
+		return $this->manager;
534
+	}
535
+
536
+	/**
537
+	 * Dynamically handle calls to custom commands, or Redirects to query().
538
+	 *
539
+	 * @param string $method
540
+	 * @param array  $parameters
541
+	 *
542
+	 * @throws \Exception
543
+	 *
544
+	 * @return mixed
545
+	 */
546
+	public function __call($method, $parameters)
547
+	{
548
+		// Check if method is a custom command on the mapper
549
+		if ($this->hasCustomCommand($method)) {
550
+			if (count($parameters) == 0) {
551
+				throw new \Exception("$method must at least have 1 argument");
552
+			}
553
+
554
+			return $this->executeCustomCommand($method, $parameters[0]);
555
+		}
556
+
557
+		// Redirect call on a new query instance
558
+		return call_user_func_array([$this->query(), $method], $parameters);
559
+	}
560
+
561
+	/**
562
+	 * Check if this mapper supports this command.
563
+	 *
564
+	 * @param string $command
565
+	 *
566
+	 * @return bool
567
+	 */
568
+	public function hasCustomCommand($command)
569
+	{
570
+		return in_array($command, $this->getCustomCommands());
571
+	}
572
+
573
+	/**
574
+	 * Get all the custom commands registered on this mapper.
575
+	 *
576
+	 * @return array
577
+	 */
578
+	public function getCustomCommands()
579
+	{
580
+		return array_keys($this->customCommands);
581
+	}
582
+
583
+	/**
584
+	 * Execute a custom command on an Entity.
585
+	 *
586
+	 * @param string                 $command
587
+	 * @param mixed|Collection|array $entity
588
+	 *
589
+	 * @throws \InvalidArgumentException
590
+	 * @throws MappingException
591
+	 *
592
+	 * @return mixed
593
+	 */
594
+	public function executeCustomCommand($command, $entity)
595
+	{
596
+		$commandClass = $this->customCommands[$command];
597
+
598
+		if ($this->manager->isTraversable($entity)) {
599
+			foreach ($entity as $instance) {
600
+				$this->executeSingleCustomCommand($commandClass, $instance);
601
+			}
602
+		} else {
603
+			return $this->executeSingleCustomCommand($commandClass, $entity);
604
+		}
605
+	}
606
+
607
+	/**
608
+	 * Execute a single command instance.
609
+	 *
610
+	 * @param string $commandClass
611
+	 * @param mixed  $entity
612
+	 *
613
+	 * @throws \InvalidArgumentException
614
+	 * @throws MappingException
615
+	 *
616
+	 * @return mixed
617
+	 */
618
+	protected function executeSingleCustomCommand($commandClass, $entity)
619
+	{
620
+		$this->checkEntityType($entity);
621
+
622
+		$instance = new $commandClass($this->aggregate($entity), $this->newQueryBuilder());
623
+
624
+		return $instance->execute();
625
+	}
626
+
627
+	/**
628
+	 * Get the Analogue Query Builder for this instance.
629
+	 *
630
+	 * @return \Analogue\ORM\System\Query
631
+	 */
632
+	public function query()
633
+	{
634
+		return $this->getQuery();
635
+	}
636 636
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -372,7 +372,7 @@
 block discarded – undo
372 372
      */
373 373
     public function getGlobalScope($scope)
374 374
     {
375
-        return array_first($this->globalScopes, function ($key, $value) use ($scope) {
375
+        return array_first($this->globalScopes, function($key, $value) use ($scope) {
376 376
             return $scope instanceof $value;
377 377
         });
378 378
     }
Please login to merge, or discard this patch.