Commit e80273b4 authored by Jens Segers's avatar Jens Segers

Refactor to original Laravel classes

parent 4009dbc7
Laravel Eloquent MongoDB [![Build Status](https://travis-ci.org/jenssegers/Laravel-MongoDB.png?branch=master)](https://travis-ci.org/jenssegers/Laravel-MongoDB) Laravel Eloquent MongoDB [![Build Status](https://travis-ci.org/jenssegers/Laravel-MongoDB.png?branch=master)](https://travis-ci.org/jenssegers/Laravel-MongoDB)
======================== ========================
An Eloquent model that supports MongoDB. An Eloquent model that supports MongoDB, inspired by LMongo but using original Eloquent methods.
*This model extends the original Eloquent model so it uses exactly the same methods. Please note that some advanced Eloquent features may not be working, but feel free to issue a pull request!* *This model extends the original Eloquent model so it uses exactly the same methods. Please note that some advanced Eloquent features may not be working, but feel free to issue a pull request!*
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0",
"illuminate/support": "4.0.x", "illuminate/support": "4.0.x",
"illuminate/database": "4.0.x" "illuminate/database": "4.0.x",
"illuminate/events": "4.0.x"
}, },
"autoload": { "autoload": {
"psr-0": { "psr-0": {
......
<?php namespace Jenssegers\Mongodb;
use MongoID;
use MongoRegex;
class Builder extends \Illuminate\Database\Query\Builder {
/**
* The database collection
*
* @var string
*/
public $collection;
/**
* All of the available operators.
*
* @var array
*/
protected $conversion = array(
'=' => '=',
'!=' => '$ne',
'<' => '$lt',
'<=' => '$lte',
'>' => '$gt',
'>=' => '$gte',
);
/**
* Create a new query builder instance.
*
* @param Connection $connection
* @return void
*/
public function __construct(Connection $connection, $collection = null)
{
$this->connection = $connection;
// Set optional collection
if (!is_null($collection))
{
$this->from($collection);
}
}
/**
* Execute a query for a single record by ID.
*
* @param int $id
* @param array $columns
* @return mixed
*/
public function find($id, $columns = array('*'))
{
return $this->where('_id', '=', new MongoID((string) $id))->first($columns);
}
/**
* Execute the query as a "select" statement.
*
* @param array $columns
* @return array
*/
public function get($columns = array('*'))
{
// If no columns have been specified for the select statement, we will set them
// here to either the passed columns, or the standard default of retrieving
// all of the columns on the table using the "wildcard" column character.
if (is_null($this->columns))
{
$this->columns = $columns;
}
// Drop all columns if * is present
if (in_array('*', $this->columns))
{
$this->columns = array();
}
// Compile wheres
$wheres = $this->compileWheres();
// Use aggregation framework if needed
if ($this->groups || $this->aggregate)
{
$pipeline = array();
$group = array();
// Grouping
if ($this->groups)
{
foreach ($this->groups as $column)
{
$group['_id'][$column] = '$' . $column;
$group[$column] = array('$last' => '$' . $column);
}
}
else
{
$group['_id'] = 0;
}
// Columns
foreach ($this->columns as $column)
{
$group[$column] = array('$last' => '$' . $column);
}
// Apply aggregation functions
if ($this->aggregate)
{
$function = $this->aggregate['function'];
foreach ($this->aggregate['columns'] as $column)
{
if ($function == 'count')
{
$group[$column] = array('$sum' => 1);
}
else {
$group[$column] = array('$' . $function => '$' . $column);
}
}
}
if ($wheres) $pipeline[] = array('$match' => $wheres);
$pipeline[] = array('$group' => $group);
// Apply order and limit
if ($this->orders) $pipeline[] = array('$sort' => $this->orders);
if ($this->limit) $pipeline[] = array('$limit' => $this->limit);
$results = $this->collection->aggregate($pipeline);
// Return results
return $results['result'];
}
else
{
// Execute distinct
if ($this->distinct)
{
$column = isset($this->columns[0]) ? $this->columns[0] : '_id';
return $this->collection->distinct($column, $wheres);
}
// Columns
$columns = array();
foreach ($this->columns as $column)
{
$columns[$column] = true;
}
// Get the MongoCursor
$cursor = $this->collection->find($wheres, $columns);
// Apply order, offset and limit
if ($this->orders) $cursor->sort($this->orders);
if ($this->offset) $cursor->skip($this->offset);
if ($this->limit) $cursor->limit($this->limit);
// Return results
return iterator_to_array($cursor);
}
}
/**
* Execute an aggregate function on the database.
*
* @param string $function
* @param array $columns
* @return mixed
*/
public function aggregate($function, $columns = array('*'))
{
$this->aggregate = compact('function', 'columns');
$results = $this->get($columns);
if (isset($results[0]))
{
return $results[0][$columns[0]];
}
}
/**
* Force the query to only return distinct results.
*
* @return Builder
*/
public function distinct($column = false)
{
$this->distinct = true;
if ($column)
{
$this->columns = array($column);
}
return $this;
}
/**
* Add an "order by" clause to the query.
*
* @param string $column
* @param string $direction
* @return Builder
*/
public function orderBy($column, $direction = 'asc')
{
$this->orders[$column] = ($direction == 'asc' ? 1 : -1);
return $this;
}
/**
* Add a where between statement to the query.
*
* @param string $column
* @param array $values
* @param string $boolean
* @return \Illuminate\Database\Query\Builder
*/
public function whereBetween($column, array $values, $boolean = 'and')
{
$type = 'between';
$this->wheres[] = compact('column', 'type', 'boolean', 'values');
$this->bindings = array_merge($this->bindings, $values);
return $this;
}
/**
* Insert a new record into the database.
*
* @param array $values
* @return bool
*/
public function insert(array $values)
{
$result = $this->collection->insert($values);
return (1 == (int) $result['ok']);
}
/**
* Insert a new record and get the value of the primary key.
*
* @param array $values
* @param string $sequence
* @return int
*/
public function insertGetId(array $values, $sequence = null)
{
$result = $this->collection->insert($values);
if (1 == (int) $result['ok'])
{
return $values['_id'];
}
}
/**
* Update a record in the database.
*
* @param array $values
* @return int
*/
public function update(array $values)
{
$update = array('$set' => $values);
$result = $this->collection->update($this->compileWheres(), $update, array('multiple' => true));
if (1 == (int) $result['ok'])
{
return $result['n'];
}
return 0;
}
/**
* Delete a record from the database.
*
* @param mixed $id
* @return int
*/
public function delete($id = null)
{
$query = $this->compileWheres($this);
$result = $this->collection->remove($query);
if (1 == (int) $result['ok'])
{
return $result['n'];
}
return 0;
}
/**
* Set the collection which the query is targeting.
*
* @param string $collection
* @return Builder
*/
public function from($collection)
{
if ($collection)
{
$this->collection = $this->connection->getCollection($collection);
}
return $this;
}
/**
* Run a truncate statement on the table.
*
* @return void
*/
public function truncate()
{
$result = $this->collection->drop();
return (1 == (int) $result['ok']);
}
/**
* Compile the where array
*
* @return array
*/
private function compileWheres()
{
if (!$this->wheres) return array();
$wheres = array();
foreach ($this->wheres as $i=>&$where)
{
// Convert id's
if (isset($where['column']) && $where['column'] == '_id')
{
if (isset($where['values']))
{
foreach ($where['values'] as &$value)
{
$value = ($value instanceof MongoID) ? $value : new MongoID($value);
}
}
else
{
$where['value'] = ($where['value'] instanceof MongoID) ? $where['value'] : new MongoID($where['value']);
}
}
// First item of chain
if ($i == 0 && count($this->wheres) > 1 && $where['boolean'] == 'and')
{
// Copy over boolean value of next item in chain
$where['boolean'] = $this->wheres[$i+1]['boolean'];
}
// Delegate
$method = "compileWhere{$where['type']}";
$compiled = $this->{$method}($where);
// Check for or
if ($where['boolean'] == 'or')
{
$compiled = array('$or' => array($compiled));
}
// Merge compiled where
$wheres = array_merge_recursive($wheres, $compiled);
}
return $wheres;
}
private function compileWhereBasic($where)
{
extract($where);
// Replace like with MongoRegex
if ($operator == 'like')
{
$operator = '=';
$regex = str_replace('%', '', $value);
// Prepare regex
if (substr($value, 0, 1) != '%') $regex = '^' . $regex;
if (substr($value, -1) != '%') $regex = $regex . '$';
$value = new MongoRegex("/$regex/i");
}
if (!isset($operator) || $operator == '=')
{
$query = array($column => $value);
}
else
{
$query = array($column => array($this->conversion[$operator] => $value));
}
return $query;
}
private function compileWhereNested($where)
{
extract($where);
return $query->compileWheres();
}
private function compileWhereIn($where)
{
extract($where);
return array($column => array('$in' => $values));
}
private function compileWhereNull($where)
{
$where['operator'] = '=';
$where['value'] = null;
return $this->compileWhereBasic($where);
}
private function compileWhereNotNull($where)
{
$where['operator'] = '!=';
$where['value'] = null;
return $this->compileWhereBasic($where);
}
private function compileWherebetween($where)
{
extract($where);
return array(
$column => array(
'$gte' => $values[0],
'$lte' => $values[1])
);
}
/**
* Get a new instance of the query builder.
*
* @return Builder
*/
public function newQuery()
{
$connection = $this->getConnection();
return new Builder($connection);
}
}
<?php namespace Jenssegers\Mongodb; <?php namespace Jenssegers\Mongodb;
class Connection { use MongoClient;
class Connection extends \Illuminate\Database\Connection {
/** /**
* The MongoDB database handler. * The MongoDB database handler.
...@@ -16,13 +18,6 @@ class Connection { ...@@ -16,13 +18,6 @@ class Connection {
*/ */
protected $connection; protected $connection;
/**
* The database connection configuration options.
*
* @var string
*/
protected $config = array();
/** /**
* Create a new database connection instance. * Create a new database connection instance.
* *
...@@ -31,8 +26,6 @@ class Connection { ...@@ -31,8 +26,6 @@ class Connection {
*/ */
public function __construct(array $config) public function __construct(array $config)
{ {
if (!is_null($this->connection)) return;
// Store configuration // Store configuration
$this->config = $config; $this->config = $config;
...@@ -40,16 +33,14 @@ class Connection { ...@@ -40,16 +33,14 @@ class Connection {
$options = array_get($config, 'options', array()); $options = array_get($config, 'options', array());
// Create connection // Create connection
$this->connection = new \MongoClient($this->getDsn($config), $options); $this->connection = new MongoClient($this->getDsn($config), $options);
// Select database // Select database
$this->db = $this->connection->{$config['database']}; $this->db = $this->connection->{$config['database']};
return $this;
} }
/** /**
* Get a mongodb collection. * Get a MongoDB collection.
* *
* @param string $name * @param string $name
* @return MongoDB * @return MongoDB
...@@ -60,7 +51,7 @@ class Connection { ...@@ -60,7 +51,7 @@ class Connection {
} }
/** /**
* Get the mongodb database object. * Get the MongoDB database object.
* *
* @return MongoDB * @return MongoDB
*/ */
......
<?php namespace Jenssegers\Mongodb;
use Illuminate\Database\ConnectionResolverInterface;
use Jenssegers\Mongodb\Connection;
use Jenssegers\Mongodb\Builder as QueryBuilder;
class DatabaseManager implements ConnectionResolverInterface {
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;
/**
* The active connection instances.
*
* @var array
*/
protected $connections = array();
/**
* The custom connection resolvers.
*
* @var array
*/
protected $extensions = array();
/**
* The default database connection.
*
* @var array
*/
protected $defaultConnection = 'mongodb';
/**
* Create a new database manager instance.
*
* @param \Illuminate\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Get a database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
$name = $name ?: $this->getDefaultConnection();
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if ( ! isset($this->connections[$name]))
{
$connection = $this->makeConnection($name);
$this->connections[$name] = $this->prepare($connection);
}
return $this->connections[$name];
}
/**
* Reconnect to the given database.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function reconnect($name = null)
{
unset($this->connections[$name]);
return $this->connection($name);
}
/**
* Make the database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
protected function makeConnection($name)
{
$config = $this->getConfig($name);
if (isset($this->extensions[$name]))
{
return call_user_func($this->extensions[$name], $config);
}
return new Connection($config);
}
/**
* Prepare the database connection instance.
*
* @param \Illuminate\Database\Connection $connection
* @return \Illuminate\Database\Connection
*/
protected function prepare(Connection $connection)
{
$connection->setEventDispatcher($this->app['events']);
// We will setup a Closure to resolve the paginator instance on the connection
// since the Paginator isn't sued on every request and needs quite a few of
// our dependencies. It'll be more efficient to lazily resolve instances.
$app = $this->app;
$connection->setPaginator(function() use ($app)
{
return $app['paginator'];
});
return $connection;
}
/**
* Get the configuration for a connection.
*
* @param string $name
* @return array
*/
protected function getConfig($name)
{
$name = $name ?: $this->getDefaultConnection();
// To get the database connection configuration, we will just pull each of the
// connection configurations and get the configurations for the given name.
// If the configuration doesn't exist, we'll throw an exception and bail.
$connections = $this->app['config']['database.connections'];
if (is_null($config = array_get($connections, $name)))
{
throw new \InvalidArgumentException("Database [$name] not configured.");
}
return $config;
}
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection()
{
return $this->defaultConnection;
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name)
{
$this->defaultConnection = $name;
}
/**
* Return a new QueryBuilder for a collection
*
* @param string $collection
* @return QueryBuilder
*/
public function collection($collection)
{
return new QueryBuilder($this->connection(), $collection);
}
/**
* Return a new QueryBuilder for a collection
*
* @param string $collection
* @return QueryBuilder
*/
public function __get($collection)
{
return $this->collection($collection);
}
}
\ No newline at end of file
<?php namespace Jenssegers\Mongodb; <?php namespace Jenssegers\Mongodb;
use Illuminate\Database\ConnectionResolverInterface as Resolver; use Jenssegers\Mongodb\DatabaseManager as Resolver;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Jenssegers\Mongodb\Query as QueryBuilder; use Jenssegers\Mongodb\Builder as QueryBuilder;
use DateTime; use DateTime;
use MongoDate; use MongoDate;
......
<?php namespace Jenssegers\Mongodb; <?php namespace Jenssegers\Mongodb;
use Jenssegers\Mongodb\Model; use Jenssegers\Mongodb\Model;
use Jenssegers\Mongodb\ConnectionResolver; use Jenssegers\Mongodb\DatabaseManager;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class MongodbServiceProvider extends ServiceProvider { class MongodbServiceProvider extends ServiceProvider {
...@@ -13,7 +13,8 @@ class MongodbServiceProvider extends ServiceProvider { ...@@ -13,7 +13,8 @@ class MongodbServiceProvider extends ServiceProvider {
*/ */
public function boot() public function boot()
{ {
Model::setConnectionResolver($this->app['db.mongodb']); Model::setConnectionResolver($this->app['mongodb']);
Model::setEventDispatcher($this->app['events']);
} }
/** /**
...@@ -26,9 +27,9 @@ class MongodbServiceProvider extends ServiceProvider { ...@@ -26,9 +27,9 @@ class MongodbServiceProvider extends ServiceProvider {
// The database manager is used to resolve various connections, since multiple // The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver // connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections. // interface which may be used by other components requiring connections.
$this->app['db.mongodb'] = $this->app->share(function($app) $this->app['mongodb'] = $this->app->share(function($app)
{ {
return new ConnectionResolver($app); return new DatabaseManager($app);
}); });
} }
......
<?php namespace Jenssegers\Mongodb;
class Connection {
/**
* The MongoDB database handler.
*
* @var resource
*/
protected $db;
/**
* The MongoClient connection handler.
*
* @var resource
*/
protected $connection;
/**
* The database connection configuration options.
*
* @var string
*/
protected $config = array();
/**
* Create a new database connection instance.
*
* @param array $config
* @return void
*/
public function __construct(array $config)
{
if (!is_null($this->connection)) return;
// Store configuration
$this->config = $config;
// Check for connection options
$options = array_get($config, 'options', array());
// Create connection
$this->connection = new \MongoClient($this->getDsn($config), $options);
// Select database
$this->db = $this->connection->{$config['database']};
return $this;
}
/**
* Get a mongodb collection.
*
* @param string $name
* @return MongoDB
*/
public function getCollection($name)
{
return $this->db->{$name};
}
/**
* Get the mongodb database object.
*
* @return MongoDB
*/
public function getDb()
{
return $this->db;
}
/**
* Create a DSN string from a configuration.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
// First we will create the basic DSN setup as well as the port if it is in
// in the configuration options. This will give us the basic DSN we will
// need to establish the MongoClient and return them back for use.
extract($config);
$dsn = "mongodb://";
if (isset($config['username']) and isset($config['password']))
{
$dsn .= "{$username}:{$password}@";
}
$dsn.= "{$host}";
if (isset($config['port']))
{
$dsn .= ":{$port}";
}
$dsn.= "/{$database}";
return $dsn;
}
}
\ No newline at end of file
<?php namespace Jenssegers\Mongodb;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Collection;
use Jenssegers\Mongodb\Query as QueryBuilder;
use DateTime;
use MongoDate;
abstract class Model extends \Illuminate\Database\Eloquent\Model {
/**
* The collection associated with the model.
*
* @var string
*/
protected $collection;
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = '_id';
/**
* Convert a DateTime to a storable string.
*
* @param DateTime $value
* @return MongoDate
*/
protected function fromDateTime(DateTime $value)
{
return new MongoDate($value->getTimestamp());
}
/**
* Return a timestamp as DateTime object.
*
* @param mixed $value
* @return DateTime
*/
protected function asDateTime($value)
{
if ($value instanceof MongoDate)
{
$value = $value->sec;
}
if (is_int($value))
{
$value = "@$value";
}
return new DateTime($value);
}
/**
* Get the table associated with the model.
*
* @return string
*/
public function getTable()
{
if (isset($this->collection)) return $this->collection;
return parent::getTable();
}
/**
* Get a new query builder instance for the connection.
*
* @return Builder
*/
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new QueryBuilder($connection);
}
}
\ No newline at end of file
<?php namespace Jenssegers\Mongodb;
use Jenssegers\Mongodb\Model;
use Jenssegers\Mongodb\ConnectionResolver;
use Illuminate\Support\ServiceProvider;
class MongodbServiceProvider extends ServiceProvider {
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
Model::setConnectionResolver($this->app['db.mongodb']);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
// The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app['db.mongodb'] = $this->app->share(function($app)
{
return new ConnectionResolver($app);
});
}
}
\ No newline at end of file
...@@ -10,12 +10,8 @@ class ConnectionTest extends PHPUnit_Framework_TestCase { ...@@ -10,12 +10,8 @@ class ConnectionTest extends PHPUnit_Framework_TestCase {
public function setUp() public function setUp()
{ {
$config = array( include('tests/app.php');
'host' => 'localhost', $this->connection = new Connection($app['config']['database.connections']['mongodb']);
'database' => 'unittest'
);
$this->connection = new Connection($config);
$this->collection = $this->connection->getCollection('unittest'); $this->collection = $this->connection->getCollection('unittest');
} }
......
...@@ -4,19 +4,14 @@ require_once('models/User.php'); ...@@ -4,19 +4,14 @@ require_once('models/User.php');
use Jenssegers\Mongodb\Connection; use Jenssegers\Mongodb\Connection;
use Jenssegers\Mongodb\Model; use Jenssegers\Mongodb\Model;
use Jenssegers\Mongodb\ConnectionResolver; use Jenssegers\Mongodb\DatabaseManager;
class ModelTest extends PHPUnit_Framework_TestCase { class ModelTest extends PHPUnit_Framework_TestCase {
public function setUp() public function setUp()
{ {
$app = array(); include('tests/app.php');
$app['config']['database']['connections']['mongodb'] = array( Model::setConnectionResolver(new DatabaseManager($app));
'host' => 'localhost',
'database' => 'unittest'
);
Model::setConnectionResolver(new ConnectionResolver($app));
} }
public function tearDown() public function tearDown()
......
...@@ -4,19 +4,14 @@ require_once('models/User.php'); ...@@ -4,19 +4,14 @@ require_once('models/User.php');
use Jenssegers\Mongodb\Connection; use Jenssegers\Mongodb\Connection;
use Jenssegers\Mongodb\Model; use Jenssegers\Mongodb\Model;
use Jenssegers\Mongodb\ConnectionResolver; use Jenssegers\Mongodb\DatabaseManager;
class QueryTest extends PHPUnit_Framework_TestCase { class QueryTest extends PHPUnit_Framework_TestCase {
public function setUp() public function setUp()
{ {
$app = array(); include('tests/app.php');
$app['config']['database']['connections']['mongodb'] = array( Model::setConnectionResolver(new DatabaseManager($app));
'host' => 'localhost',
'database' => 'unittest'
);
Model::setConnectionResolver(new ConnectionResolver($app));
// test data // test data
User::create(array('name' => 'John Doe', 'age' => 35, 'title' => 'admin')); User::create(array('name' => 'John Doe', 'age' => 35, 'title' => 'admin'));
...@@ -38,6 +33,7 @@ class QueryTest extends PHPUnit_Framework_TestCase { ...@@ -38,6 +33,7 @@ class QueryTest extends PHPUnit_Framework_TestCase {
public function testGet() public function testGet()
{ {
$users = User::get(); $users = User::get();
$this->assertEquals(9, count($users)); $this->assertEquals(9, count($users));
$this->assertInstanceOf('Jenssegers\Mongodb\Model', $users[0]); $this->assertInstanceOf('Jenssegers\Mongodb\Model', $users[0]);
} }
......
<?php
$app = array();
$app['events'] = new \Illuminate\Events\Dispatcher;
$app['config']['database.connections']['mongodb'] = array(
'host' => 'localhost',
'database' => 'unittest'
);
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment