Skip to content

Instantly share code, notes, and snippets.

@alcaeus
Created May 25, 2015 15:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alcaeus/1e1dfe7c52904c9cf581 to your computer and use it in GitHub Desktop.
Save alcaeus/1e1dfe7c52904c9cf581 to your computer and use it in GitHub Desktop.
Hydrated aggregation results with Doctrine ODM
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/
namespace Alcaeus\DataObject\Content;
use Alcaeus\Document;
/**
* @author alcaeus <alcaeus@alcaeus.org>
*/
class Author
{
/**
* @var Document\User
*/
protected $user;
/**
* @var int
*/
protected $numPosts = 0;
/**
* @param Document\User $user
*/
public function __construct(Document\User $user)
{
$this->setUser($user);
}
/**
* @return Document\User
*/
public function getUser()
{
return $this->user;
}
/**
* @param Document\User $user
*
* @return $this
*/
public function setUser(Document\User $user)
{
$this->user = $user;
return $this;
}
/**
* @return int
*/
public function getNumPosts()
{
return $this->numPosts;
}
/**
* @param int $numPosts
*
* @return $this
*/
public function setNumPosts($numPosts)
{
$this->numPosts = $numPosts;
return $this;
}
}
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/
namespace Alcaeus\Hydrator;
/**
* @author alcaeus <alcaeus@alcaeus.org>
*/
interface HydratorInterface
{
/**
* Hydrate array of MongoDB document data into a document object.
*
* @param array $data The array of document data.
*
* @return object The hydrated document
*/
public function hydrate($data);
}
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/
namespace Alcaeus\Hydrator;
use Alcaeus\DataObject;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Hydrator\HydratorException;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\UnitOfWork;
/**
* @author alcaeus <alcaeus@alcaeus.org>
*/
class PostCount implements HydratorInterface
{
/**
* @var DocumentManager
*/
protected $dm;
/**
* @var UnitOfWork
*/
protected $uow;
/**
* @var ClassMetadata
*/
protected $class;
/**
* @param DocumentManager $dm
* @param UnitOfWork $uow
* @param ClassMetadata $class
*/
public function __construct(DocumentManager $dm, UnitOfWork $uow, ClassMetadata $class)
{
$this->dm = $dm;
$this->uow = $uow;
$this->class = $class;
}
/**
* {@inheritdoc}
*/
public function hydrate($data)
{
if (! isset($data['_id']['$id'])) {
throw new HydratorException('Invalid document received - must have a user ID');
}
$className = $this->uow->getClassNameForAssociation($this->class->fieldMappings['user'], $data['_id']);
$user = $this->dm->getReference($className, $data['_id']['$id']);
$dataObject = new DataObject\PostCount($user);
if (isset($data['numPosts'])) {
$dataObject->setNumPosts((int) $data['numPosts']);
}
return $dataObject;
}
}
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/
namespace Alcaeus\MongoDB;
use Doctrine\MongoDB\CommandCursor;
use Alcaeus\Hydrator\HydratorInterface;
/**
* @author alcaeus <alcaeus@alcaeus.org>
*/
class HydratedCommandCursor extends CommandCursor
{
/**
* @var CommandCursor
*/
protected $baseCursor;
/**
* @var HydratorInterface
*/
protected $hydrator;
/**
* @param CommandCursor $baseCursor
* @param HydratorInterface $hydrator
*/
public function __construct(CommandCursor $baseCursor, HydratorInterface $hydrator)
{
$this->baseCursor = $baseCursor;
$this->hydrator = $hydrator;
}
/**
* @return mixed
*/
public function current()
{
$current = $this->baseCursor->current();
if ($current !== null) {
return $this->hydrator->hydrate($current);
}
return $current;
}
/**
* {@inheritdoc}
*/
public function batchSize($num)
{
return $this->baseCursor->batchSize($num);
}
/**
* {@inheritdoc}
*/
public function count()
{
return $this->baseCursor->count();
}
/**
* {@inheritdoc}
*/
public function dead()
{
return $this->baseCursor->dead();
}
/**
* {@inheritdoc}
*/
public function getSingleResult()
{
return $this->baseCursor->getSingleResult();
}
/**
* {@inheritdoc}
*/
public function info()
{
return $this->baseCursor->info();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->baseCursor->key();
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->baseCursor->next();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
return $this->baseCursor->rewind();
}
/**
* {@inheritdoc}
*/
public function timeout($ms)
{
return $this->baseCursor->timeout($ms);
}
/**
* {@inheritdoc}
*/
public function toArray()
{
$cursor = $this;
return $this->retry(function () use ($cursor) {
return iterator_to_array($cursor);
});
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->baseCursor->valid();
}
}
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/
namespace Alcaeus\Repository;
use Alcaeus\DataObject;
use Alcaeus\Hydrator;
use Alcaeus\MongoDB;
use Doctrine\ODM\MongoDB\DocumentRepository;
/**
* @author alcaeus <alcaeus@alcaeus.org>
*/
class Post extends DocumentRepository
{
/**
* @param int $days
* @param null $limit
*
* @return DataObject\PostCount[]
*/
public function getUsersWithMostPosts($days = 7, $limit = null)
{
$to = new \DateTime();
$from = clone $to;
$from->sub(\DateInterval::createFromDateString($days . ' days'));
$query = $this->createQueryBuilder();
$query
->field('createDate')
->gte($from)
->lt($to)
->field('user.type')
->exists(true);
$pipeline = [
[
'$match' => $query->getQueryArray()
],
[
'$group' => [
'_id' => '$user',
'numPosts' => ['$sum' => 1]
]
],
[
'$sort' => ['numPosts' => -1]
]
];
if ($limit !== null) {
$pipeline[] = ['$limit' => $limit];
}
$cursor = $this
->getDocumentManager()
->getDocumentCollection($this->getDocumentName())
->aggregate($pipeline, ['cursor' => true]);
$hydrator = new Hydrator\PostCount(
$this->getDocumentManager(),
$this->getDocumentManager()->getUnitOfWork(),
$this->getClassMetadata()
);
return new MongoDB\HydratedCommandCursor($cursor, $hydrator);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment