Skip to content

Commit 412c2d3

Browse files
committed
PHPLIB-45: Collection enumeration methods
1 parent 37edc90 commit 412c2d3

File tree

6 files changed

+244
-2
lines changed

6 files changed

+244
-2
lines changed

src/Database.php

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
use MongoDB\Collection;
66
use MongoDB\Driver\Command;
77
use MongoDB\Driver\Manager;
8+
use MongoDB\Driver\Query;
89
use MongoDB\Driver\ReadPreference;
910
use MongoDB\Driver\Result;
1011
use MongoDB\Driver\WriteConcern;
12+
use MongoDB\Model\CollectionInfoIterator;
13+
use MongoDB\Model\CollectionInfoCommandIterator;
14+
use MongoDB\Model\CollectionInfoLegacyIterator;
15+
use InvalidArgumentException;
1116

1217
class Database
1318
{
@@ -84,11 +89,12 @@ public function dropCollection($collectionName)
8489
*
8590
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
8691
* @param array $options
87-
* @return Result
92+
* @return CollectionInfoIterator
8893
*/
8994
public function listCollections(array $options = array())
9095
{
91-
// TODO
96+
// TODO: Determine if command or legacy method should be used
97+
return $this->listCollectionsCommand($options);
9298
}
9399

94100
/**
@@ -110,4 +116,58 @@ public function selectCollection($collectionName, WriteConcern $writeConcern = n
110116

111117
return new Collection($this->manager, $namespace, $writeConcern, $readPreference);
112118
}
119+
120+
/**
121+
* Returns information for all collections in this database using the
122+
* listCollections command.
123+
*
124+
* @param array $options
125+
* @return CollectionInfoCommandIterator
126+
*/
127+
private function listCollectionsCommand(array $options = array())
128+
{
129+
$command = new Command(array('listCollections' => 1) + $options);
130+
// TODO: Relax RP if connected to a secondary node in standalone mode
131+
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
132+
133+
$result = $this->manager->executeCommand($this->databaseName, $command, $readPreference);
134+
135+
return new CollectionInfoCommandIterator($result);
136+
}
137+
138+
/**
139+
* Returns information for all collections in this database by querying
140+
* the "system.namespaces" collection (MongoDB <2.8).
141+
*
142+
* @param array $options
143+
* @return CollectionInfoLegacyIterator
144+
* @throws InvalidArgumentException if the filter option is neither an array
145+
* nor object, or if filter.name is not a
146+
* string.
147+
*/
148+
private function listCollectionsLegacy(array $options = array())
149+
{
150+
$filter = array_key_exists('filter', $options) ? $options['filter'] : array();
151+
152+
if ( ! is_array($filter) && ! is_object($filter)) {
153+
throw new InvalidArgumentException(sprintf('Expected filter to be array or object, %s given', gettype($filter)));
154+
}
155+
156+
if (array_key_exists('name', $filter)) {
157+
if ( ! is_string($filter['name'])) {
158+
throw new InvalidArgumentException(sprintf('Filter "name" must be a string for MongoDB <2.8, %s given', gettype($filter['name'])));
159+
}
160+
161+
$filter['name'] = $this->databaseName . '.' . $filter['name'];
162+
}
163+
164+
$namespace = $this->databaseName . '.system.namespaces';
165+
$query = new Query($filter);
166+
// TODO: Relax RP if connected to a secondary node in standalone mode
167+
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
168+
169+
$result = $this->manager->executeQuery($namespace, $query, $readPreference);
170+
171+
return new CollectionInfoLegacyIterator($result);
172+
}
113173
}

src/Model/CollectionInfo.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace MongoDB\Model;
4+
5+
class CollectionInfo
6+
{
7+
private $name;
8+
private $options;
9+
10+
/**
11+
* Constructor.
12+
*
13+
* @param array $info Collection info
14+
*/
15+
public function __construct(array $info)
16+
{
17+
$this->name = (string) $info['name'];
18+
$this->options = isset($info['options']) ? (array) $info['options'] : array();
19+
}
20+
21+
/**
22+
* Return the collection name.
23+
*
24+
* @return string
25+
*/
26+
public function getName()
27+
{
28+
return $this->name;
29+
}
30+
31+
/**
32+
* Return the collection options.
33+
*
34+
* @return array
35+
*/
36+
public function getOptions()
37+
{
38+
return $this->options;
39+
}
40+
41+
/**
42+
* Return whether the collection is a capped collection.
43+
*
44+
* @return boolean
45+
*/
46+
public function isCapped()
47+
{
48+
return isset($this->options['capped']) ? (boolean) $this->options['capped'] : false;
49+
}
50+
51+
/**
52+
* Return the maximum number of documents to keep in the capped collection.
53+
*
54+
* @return integer|null
55+
*/
56+
public function getCappedMax()
57+
{
58+
return isset($this->options['max']) ? (integer) $this->options['max'] : null;
59+
}
60+
61+
/**
62+
* Return the maximum size (in bytes) of the capped collection.
63+
*
64+
* @return integer|null
65+
*/
66+
public function getCappedSize()
67+
{
68+
return isset($this->options['size']) ? (integer) $this->options['size'] : null;
69+
}
70+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace MongoDB\Model;
4+
5+
use IteratorIterator;
6+
7+
class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator
8+
{
9+
/**
10+
* Return the current element as a CollectionInfo instance.
11+
*
12+
* @return CollectionInfo
13+
*/
14+
public function current()
15+
{
16+
return new CollectionInfo(parent::current());
17+
}
18+
}

src/Model/CollectionInfoIterator.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace MongoDB\Model;
4+
5+
use Iterator;
6+
7+
interface CollectionInfoIterator extends Iterator
8+
{
9+
/**
10+
* Return the current element as a CollectionInfo instance.
11+
*
12+
* @return CollectionInfo
13+
*/
14+
public function current();
15+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace MongoDB\Model;
4+
5+
use FilterIterator;
6+
7+
class CollectionInfoLegacyIterator extends FilterIterator implements CollectionInfoIterator
8+
{
9+
/**
10+
* Return the current element as a CollectionInfo instance.
11+
*
12+
* @return CollectionInfo
13+
*/
14+
public function current()
15+
{
16+
$info = parent::current();
17+
18+
// Trim the database prefix up to and including the first dot
19+
$firstDot = strpos($info['name'], '.');
20+
21+
if ($firstDot !== false) {
22+
$info['name'] = (string) substr($info['name'], $firstDot + 1);
23+
}
24+
25+
return new CollectionInfo($info);
26+
}
27+
28+
/**
29+
* Filter out internal or invalid collections.
30+
*
31+
* @see http://php.net/manual/en/filteriterator.accept.php
32+
* @return boolean
33+
*/
34+
public function accept()
35+
{
36+
$info = parent::current();
37+
38+
if ( ! isset($info['name']) || ! is_string($info['name'])) {
39+
return false;
40+
}
41+
42+
// Reject names with "$" characters (e.g. indexes, oplog)
43+
if (strpos($info['name'], '$') !== false) {
44+
return false;
45+
}
46+
47+
$firstDot = strpos($info['name'], '.');
48+
49+
/* Legacy collection names are a namespace and should be prefixed with
50+
* the database name and a dot. Reject values that omit this prefix or
51+
* are empty beyond it.
52+
*/
53+
if ($firstDot === false || $firstDot + 1 == strlen($info['name'])) {
54+
return false;
55+
}
56+
57+
return true;
58+
}
59+
}

tests/DatabaseFunctionalTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,24 @@ public function testDropCollection()
3939
$this->assertCommandSucceeded($commandResult);
4040
$this->assertCollectionCount($this->getNamespace(), 0);
4141
}
42+
43+
public function testListCollections()
44+
{
45+
$writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1));
46+
$this->assertEquals(1, $writeResult->getInsertedCount());
47+
48+
$collections = $this->database->listCollections();
49+
$this->assertInstanceOf('MongoDB\Model\CollectionInfoIterator', $collections);
50+
51+
$foundCollection = null;
52+
53+
foreach ($collections as $collection) {
54+
if ($collection->getName() === $this->getCollectionName()) {
55+
$foundCollection = $collection;
56+
break;
57+
}
58+
}
59+
60+
$this->assertNotNull($foundCollection, 'Found test collection in list of collection');
61+
}
4262
}

0 commit comments

Comments
 (0)