Skip to content

Commit 0ec7a24

Browse files
committed
Merge branch 'master' into develop
2 parents f721015 + 040b794 commit 0ec7a24

File tree

8 files changed

+560
-111
lines changed

8 files changed

+560
-111
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ phpunit.phar
44
composer.phar
55
composer.lock
66
*.sublime-project
7-
*.sublime-workspace
7+
*.sublime-workspace
8+
*.project

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,33 @@ Tell your model to use the MongoDB model and set the collection (alias for table
6565

6666
}
6767

68-
*You can also specify the connection name in the model by changing the `connection` property.*
68+
If you are using a different database driver as the default one, you will need to specify the mongodb connection within your model by changing the `connection` property:
69+
70+
use Jenssegers\Mongodb\Model as Eloquent;
71+
72+
class MyModel extends Eloquent {
73+
74+
protected $connection = 'mongodb';
75+
76+
}
6977

7078
Everything else works just like the original Eloquent model. Read more about the Eloquent on http://laravel.com/docs/eloquent
7179

80+
### Optional: Alias
81+
-------------------
82+
83+
You may also register an alias for the MongoDB model by adding the following to the alias array in `app/config/app.php`:
84+
85+
'Moloquent' => 'Jenssegers\Mongodb\Model',
86+
87+
This will allow you to use your registered alias like:
88+
89+
class MyModel extends Moloquent {
90+
91+
protected $collection = 'mycollection';
92+
93+
}
94+
7295
Query Builder
7396
-------------
7497

@@ -135,7 +158,7 @@ Examples
135158

136159
$users = User::whereIn('age', array(16, 18, 20))->get();
137160

138-
When using `whereNotIn` objects will be returned if the field is non existant. Combine with `whereNotNull('age')` to leave out those documents.
161+
When using `whereNotIn` objects will be returned if the field is non existent. Combine with `whereNotNull('age')` to leave out those documents.
139162

140163
**Using Where Between**
141164

@@ -243,6 +266,9 @@ Supported relations are:
243266
- hasOne
244267
- hasMany
245268
- belongsTo
269+
- belongsToMany
270+
271+
*The belongsToMany relation will not use a pivot "table", but will push id's to a **related_ids** attribute instead.*
246272

247273
Example:
248274

src/Jenssegers/Mongodb/Model.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Jenssegers\Mongodb\DatabaseManager as Resolver;
88
use Jenssegers\Mongodb\Builder as QueryBuilder;
99
use Jenssegers\Mongodb\Relations\BelongsTo;
10+
use Jenssegers\Mongodb\Relations\BelongsToMany;
1011

1112
use Carbon\Carbon;
1213
use DateTime;
@@ -199,6 +200,43 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat
199200
return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation);
200201
}
201202

203+
/**
204+
* Define a many-to-many relationship.
205+
*
206+
* @param string $related
207+
* @param string $table
208+
* @param string $foreignKey
209+
* @param string $otherKey
210+
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
211+
*/
212+
public function belongsToMany($related, $collection = null, $foreignKey = null, $otherKey = null)
213+
{
214+
$caller = $this->getBelongsToManyCaller();
215+
216+
// First, we'll need to determine the foreign key and "other key" for the
217+
// relationship. Once we have determined the keys we'll make the query
218+
// instances as well as the relationship instances we need for this.
219+
$foreignKey = $foreignKey ?: $this->getForeignKey() . 's';
220+
221+
$instance = new $related;
222+
223+
$otherKey = $otherKey ?: $instance->getForeignKey() . 's';
224+
// If no table name was provided, we can guess it by concatenating the two
225+
// models using underscores in alphabetical order. The two model names
226+
// are transformed to snake case from their default CamelCase also.
227+
if (is_null($collection))
228+
{
229+
$collection = snake_case(str_plural(class_basename($related)));
230+
}
231+
232+
// Now we're ready to create a new query builder for the related model and
233+
// the relationship instances for the relation. The relations will set
234+
// appropriate query constraint and entirely manages the hydrations.
235+
$query = $instance->newQuery();
236+
237+
return new BelongsToMany($query, $this, $collection, $foreignKey, $otherKey, $caller['function']);
238+
}
239+
202240
/**
203241
* Get a new query builder instance for the connection.
204242
*
@@ -274,4 +312,4 @@ public function __call($method, $parameters)
274312
return parent::__call($method, $parameters);
275313
}
276314

277-
}
315+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
<?php namespace Jenssegers\Mongodb\Relations;
2+
3+
use Illuminate\Database\Eloquent\Collection;
4+
use Jenssegers\Mongodb\Model;
5+
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany;
6+
7+
class BelongsToMany extends EloquentBelongsToMany {
8+
9+
/**
10+
* Hydrate the pivot table relationship on the models.
11+
*
12+
* @param array $models
13+
* @return void
14+
*/
15+
protected function hydratePivotRelation(array $models)
16+
{
17+
// Do nothing
18+
}
19+
20+
/**
21+
* Set the select clause for the relation query.
22+
*
23+
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
24+
*/
25+
protected function getSelectColumns(array $columns = array('*'))
26+
{
27+
return $columns;
28+
}
29+
30+
/**
31+
* Set the base constraints on the relation query.
32+
*
33+
* @return void
34+
*/
35+
public function addConstraints()
36+
{
37+
if (static::$constraints)
38+
{
39+
// Make sure that the primary key of the parent
40+
// is in the relationship array of keys
41+
$this->query->whereIn($this->foreignKey, array($this->parent->getKey()));
42+
}
43+
}
44+
45+
/**
46+
* Sync the intermediate tables with a list of IDs.
47+
*
48+
* @param array $ids
49+
* @param bool $detaching
50+
* @return void
51+
*/
52+
public function sync(array $ids, $detaching = true)
53+
{
54+
// First we need to attach any of the associated models that are not currently
55+
// in this joining table. We'll spin through the given IDs, checking to see
56+
// if they exist in the array of current ones, and if not we will insert.
57+
$current = $this->parent->{$this->otherKey};
58+
59+
// Check if the current array exists or not on the parent model and create it
60+
// if it does not exist
61+
if (is_null($current)) $current = array();
62+
63+
$records = $this->formatSyncList($ids);
64+
65+
$detach = array_diff($current, array_keys($records));
66+
67+
// Next, we will take the differences of the currents and given IDs and detach
68+
// all of the entities that exist in the "current" array but are not in the
69+
// the array of the IDs given to the method which will complete the sync.
70+
if ($detaching and count($detach) > 0)
71+
{
72+
$this->detach($detach);
73+
}
74+
75+
// Now we are finally ready to attach the new records. Note that we'll disable
76+
// touching until after the entire operation is complete so we don't fire a
77+
// ton of touch operations until we are totally done syncing the records.
78+
$this->attachNew($records, $current, false);
79+
80+
$this->touchIfTouching();
81+
}
82+
83+
/**
84+
* Attach all of the IDs that aren't in the current array.
85+
*
86+
* @param array $records
87+
* @param array $current
88+
* @param bool $touch
89+
* @return void
90+
*/
91+
protected function attachNew(array $records, array $current, $touch = true)
92+
{
93+
foreach ($records as $id => $attributes)
94+
{
95+
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
96+
// record, otherwise, we will just update this existing record on this joining
97+
// table, so that the developers will easily update these records pain free.
98+
if ( ! in_array($id, $current))
99+
{
100+
$this->attach($id, $attributes, $touch);
101+
}
102+
}
103+
}
104+
105+
/**
106+
* Attach a model to the parent.
107+
*
108+
* @param mixed $id
109+
* @param array $attributes
110+
* @param bool $touch
111+
* @return void
112+
*/
113+
public function attach($id, array $attributes = array(), $touch = true)
114+
{
115+
if ($id instanceof Model) $id = $id->getKey();
116+
117+
// Generate a new parent query instance
118+
$parent = $this->newParentQuery();
119+
120+
// Generate a new related query instance
121+
$related = $this->related->newInstance();
122+
123+
// Set contraints on the related query
124+
$related = $related->where($this->related->getKeyName(), $id);
125+
126+
$records = $this->createAttachRecords((array) $id, $attributes);
127+
128+
// Get the ID's to attach to the two documents
129+
$otherIds = array_pluck($records, $this->otherKey);
130+
$foreignIds = array_pluck($records, $this->foreignKey);
131+
132+
// Attach to the parent model
133+
$parent->push($this->otherKey, $otherIds[0])->update(array());
134+
135+
// Attach to the related model
136+
$related->push($this->foreignKey, $foreignIds[0])->update(array());
137+
}
138+
139+
/**
140+
* Create an array of records to insert into the pivot table.
141+
*
142+
* @param array $ids
143+
* @return void
144+
*/
145+
protected function createAttachRecords($ids, array $attributes)
146+
{
147+
$records = array();;
148+
149+
// To create the attachment records, we will simply spin through the IDs given
150+
// and create a new record to insert for each ID. Each ID may actually be a
151+
// key in the array, with extra attributes to be placed in other columns.
152+
foreach ($ids as $key => $value)
153+
{
154+
$records[] = $this->attacher($key, $value, $attributes, false);
155+
}
156+
157+
return $records;
158+
}
159+
160+
/**
161+
* Detach models from the relationship.
162+
*
163+
* @param int|array $ids
164+
* @param bool $touch
165+
* @return int
166+
*/
167+
public function detach($ids = array(), $touch = true)
168+
{
169+
if ($ids instanceof Model) $ids = (array) $ids->getKey();
170+
171+
$query = $this->newParentQuery();
172+
173+
// If associated IDs were passed to the method we will only delete those
174+
// associations, otherwise all of the association ties will be broken.
175+
// We'll return the numbers of affected rows when we do the deletes.
176+
$ids = (array) $ids;
177+
178+
if (count($ids) > 0)
179+
{
180+
$query->whereIn($this->otherKey, $ids);
181+
}
182+
183+
if ($touch) $this->touchIfTouching();
184+
185+
// Once we have all of the conditions set on the statement, we are ready
186+
// to run the delete on the pivot table. Then, if the touch parameter
187+
// is true, we will go ahead and touch all related models to sync.
188+
foreach($ids as $id)
189+
{
190+
$query->pull($this->otherKey, $id);
191+
}
192+
193+
return count($ids);
194+
}
195+
196+
/**
197+
* Create a new query builder for the parent
198+
*
199+
* @return Jenssegers\Mongodb\Builder
200+
*/
201+
protected function newParentQuery()
202+
{
203+
$query = $this->parent->newQuery();
204+
205+
return $query->where($this->parent->getKeyName(), '=', $this->parent->getKey());
206+
}
207+
208+
/**
209+
* Build model dictionary keyed by the relation's foreign key.
210+
*
211+
* @param \Illuminate\Database\Eloquent\Collection $results
212+
* @return array
213+
*/
214+
protected function buildDictionary(Collection $results)
215+
{
216+
$foreign = $this->foreignKey;
217+
218+
// First we will build a dictionary of child models keyed by the foreign key
219+
// of the relation so that we will easily and quickly match them to their
220+
// parents without having a possibly slow inner loops for every models.
221+
$dictionary = array();
222+
223+
foreach ($results as $result)
224+
{
225+
foreach ($result->$foreign as $single)
226+
{
227+
$dictionary[$single][] = $result;
228+
}
229+
}
230+
231+
return $dictionary;
232+
}
233+
234+
/**
235+
* Get the fully qualified foreign key for the relation.
236+
*
237+
* @return string
238+
*/
239+
public function getForeignKey()
240+
{
241+
return $this->foreignKey;
242+
}
243+
244+
/**
245+
* Get the fully qualified "other key" for the relation.
246+
*
247+
* @return string
248+
*/
249+
public function getOtherKey()
250+
{
251+
return $this->otherKey;
252+
}
253+
}

tests/ModelTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,14 @@ public function testUnset()
312312
public function testDates()
313313
{
314314
$user = User::create(array('name' => 'John Doe', 'birthday' => new DateTime('1980/1/1')));
315+
315316
$this->assertInstanceOf('Carbon\Carbon', $user->birthday);
316317

317318
$check = User::find($user->_id);
319+
318320
$this->assertInstanceOf('Carbon\Carbon', $check->birthday);
319321
$this->assertEquals($user->birthday, $check->birthday);
322+
320323

321324
$user = User::where('birthday', '>', new DateTime('1975/1/1'))->first();
322325
$this->assertEquals('John Doe', $user->name);

0 commit comments

Comments
 (0)