laravel mongodb 支持

master
Iwannamaybe 3 years ago
commit 405bac0668

1
.gitignore vendored

@ -0,0 +1 @@
/vendor/

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ComposerJsonPluginSettings">
<unboundedVersionInspectionSettings>
<excludedPackages />
</unboundedVersionInspectionSettings>
<customRepositories />
<composerUpdateOptions />
</component>
</project>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData">
<serverData>
<paths name="_development">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
<paths name="_staging">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectDictionaryState">
<dictionary name="TouchWorld" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

@ -0,0 +1,5 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" />
</settings>
</component>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/laravel-mongodb.iml" filepath="$PROJECT_DIR$/.idea/laravel-mongodb.iml" />
</modules>
</component>
</project>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,15 @@
ARG PHP_VERSION=7.2
ARG COMPOSER_VERSION=1.8
FROM composer:${COMPOSER_VERSION}
FROM php:${PHP_VERSION}-cli
RUN apt-get update && \
apt-get install -y autoconf pkg-config libssl-dev git libzip-dev zlib1g-dev && \
pecl install mongodb && docker-php-ext-enable mongodb && \
pecl install xdebug && docker-php-ext-enable xdebug && \
docker-php-ext-install -j$(nproc) pdo_mysql zip
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
WORKDIR /code

File diff suppressed because it is too large Load Diff

@ -0,0 +1,58 @@
{
"name": "ifornew/mongodb",
"description": "A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)",
"keywords": [
"laravel",
"eloquent",
"mongodb",
"mongo",
"database",
"model",
"moloquent",
"transaction"
],
"homepage": "https://git.ifornew.com/laravel-mongodb.git",
"authors": [
{
"name": "Ifornew",
"homepage": "https://ifornew.com"
}
],
"license": "MIT",
"require": {
"illuminate/support": "^5.8|^6.0",
"illuminate/container": "^5.8|^6.0",
"illuminate/database": "^5.8|^6.0",
"illuminate/events": "^5.8|^6.0",
"mongodb/mongodb": "^1.4"
},
"require-dev": {
"phpunit/phpunit": "^6.0|^7.0|^8.0",
"orchestra/testbench": "^3.1|^4.0",
"mockery/mockery": "^1.0",
"satooshi/php-coveralls": "^2.0",
"doctrine/dbal": "^2.5"
},
"autoload": {
"psr-4": {"Ifornew\\": "src"}
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php",
"tests/models",
"tests/seeds"
]
},
"suggest": {
"jenssegers/mongodb-session": "Add MongoDB session support to Laravel-MongoDB",
"jenssegers/mongodb-sentry": "Add Sentry support to Laravel-MongoDB"
},
"extra": {
"laravel": {
"providers": [
"Ifornew\\Mongodb\\MongodbServiceProvider",
"Ifornew\\Mongodb\\MongodbQueueServiceProvider"
]
}
}
}

@ -0,0 +1,32 @@
version: '3'
services:
tests:
container_name: tests
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/code
working_dir: /code
depends_on:
- mongodb
- mysql
mysql:
container_name: mysql
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD:
MYSQL_DATABASE: unittest
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
logging:
driver: none
mongodb:
container_name: mongodb
image: mongo
ports:
- 27017:27017
logging:
driver: none

@ -0,0 +1,42 @@
<?php
namespace Ifornew\Mongodb\Auth;
use DateTime;
use DateTimeZone;
use Illuminate\Auth\Passwords\DatabaseTokenRepository as BaseDatabaseTokenRepository;
use MongoDB\BSON\UTCDateTime;
class DatabaseTokenRepository extends BaseDatabaseTokenRepository
{
/**
* @inheritdoc
*/
protected function getPayload($email, $token)
{
return [
'email' => $email,
'token' => $this->hasher->make($token),
'created_at' => new UTCDateTime(time() * 1000),
];
}
/**
* @inheritdoc
*/
protected function tokenExpired($createdAt)
{
// Convert UTCDateTime to a date string.
if ($createdAt instanceof UTCDateTime) {
$date = $createdAt->toDateTime();
$date->setTimezone(new DateTimeZone(date_default_timezone_get()));
$createdAt = $date->format('Y-m-d H:i:s');
} elseif (is_array($createdAt) && isset($createdAt['date'])) {
$date = new DateTime($createdAt['date'], new DateTimeZone(isset($createdAt['timezone']) ? $createdAt['timezone'] : 'UTC'));
$date->setTimezone(new DateTimeZone(date_default_timezone_get()));
$createdAt = $date->format('Y-m-d H:i:s');
}
return parent::tokenExpired($createdAt);
}
}

@ -0,0 +1,22 @@
<?php
namespace Ifornew\Mongodb\Auth;
use Illuminate\Auth\Passwords\PasswordBrokerManager as BasePasswordBrokerManager;
class PasswordBrokerManager extends BasePasswordBrokerManager
{
/**
* @inheritdoc
*/
protected function createTokenRepository(array $config)
{
return new DatabaseTokenRepository(
$this->app['db']->connection(),
$this->app['hash'],
$config['table'],
$this->app['config']['app.key'],
$config['expire']
);
}
}

@ -0,0 +1,44 @@
<?php
namespace Ifornew\Mongodb\Auth;
use Illuminate\Auth\Passwords\PasswordResetServiceProvider as BasePasswordResetServiceProvider;
class PasswordResetServiceProvider extends BasePasswordResetServiceProvider
{
/**
* Register the token repository implementation.
* @return void
*/
protected function registerTokenRepository()
{
$this->app->singleton('auth.password.tokens', function ($app) {
$connection = $app['db']->connection();
// The database token repository is an implementation of the token repository
// interface, and is responsible for the actual storing of auth tokens and
// their e-mail addresses. We will inject this table and hash key to it.
$table = $app['config']['auth.password.table'];
$key = $app['config']['app.key'];
$expire = $app['config']->get('auth.password.expire', 60);
return new DatabaseTokenRepository($connection, $table, $key, $expire);
});
}
/**
* @inheritdoc
*/
protected function registerPasswordBroker()
{
$this->app->singleton('auth.password', function ($app) {
return new PasswordBrokerManager($app);
});
$this->app->bind('auth.password.broker', function ($app) {
return $app->make('auth.password')->broker();
});
}
}

@ -0,0 +1,19 @@
<?php
namespace Ifornew\Mongodb\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Ifornew\Mongodb\Eloquent\Model;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
}

@ -0,0 +1,75 @@
<?php
namespace Ifornew\Mongodb;
use Exception;
use MongoDB\BSON\ObjectID;
use MongoDB\Collection as MongoCollection;
class Collection
{
/**
* The connection instance.
* @var Connection
*/
protected $connection;
/**
* The MongoCollection instance..
* @var MongoCollection
*/
protected $collection;
/**
* @param Connection $connection
* @param MongoCollection $collection
*/
public function __construct(Connection $connection, MongoCollection $collection)
{
$this->connection = $connection;
$this->collection = $collection;
}
/**
* Handle dynamic method calls.
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
$start = microtime(true);
$result = call_user_func_array([$this->collection, $method], $parameters);
if ($this->connection->logging()) {
// Once we have run the query we will calculate the time that it took to run and
// then log the query, bindings, and execution time so we will report them on
// the event that the developer needs them. We'll log time in milliseconds.
$time = $this->connection->getElapsedTime($start);
$query = [];
// Convert the query parameters to a json string.
array_walk_recursive($parameters, function (&$item, $key) {
if ($item instanceof ObjectID) {
$item = (string) $item;
}
});
// Convert the query parameters to a json string.
foreach ($parameters as $parameter) {
try {
$query[] = json_encode($parameter);
} catch (Exception $e) {
$query[] = '{...}';
}
}
$queryString = $this->collection->getCollectionName() . '.' . $method . '(' . implode(',', $query) . ')';
$this->connection->logQuery($queryString, [], $time);
}
return $result;
}
}

@ -0,0 +1,259 @@
<?php
namespace Ifornew\Mongodb;
use Illuminate\Database\Connection as BaseConnection;
use Illuminate\Support\Arr;
use MongoDB\Client;
class Connection extends BaseConnection
{
use TransactionTrait;
/**
* The MongoDB database handler.
* @var \MongoDB\Database
*/
protected $db;
/**
* The MongoDB connection handler.
* @var \MongoDB\Client
*/
protected $connection;
/**
* Create a new database connection instance.
* @param array $config
*/
public function __construct(array $config)
{
$this->config = $config;
// Build the connection string
$dsn = $this->getDsn($config);
// You can pass options directly to the MongoDB constructor
$options = Arr::get($config, 'options', []);
// Create the connection
$this->connection = $this->createConnection($dsn, $config, $options);
// Select database
$this->db = $this->connection->selectDatabase($config['database']);
$this->useDefaultPostProcessor();
$this->useDefaultSchemaGrammar();
$this->useDefaultQueryGrammar();
}
/**
* Begin a fluent query against a database collection.
* @param string $collection
* @return Query\Builder
*/
public function collection($collection)
{
$query = new Query\Builder($this, $this->getPostProcessor());
return $query->from($collection);
}
/**
* Begin a fluent query against a database collection.
* @param string $table
* @param string|null $as
* @return Query\Builder
*/
public function table($table, $as = null)
{
return $this->collection($table);
}
/**
* Get a MongoDB collection.
* @param string $name
* @return Collection
*/
public function getCollection($name)
{
return new Collection($this, $this->db->selectCollection($name));
}
/**
* @inheritdoc
*/
public function getSchemaBuilder()
{
return new Schema\Builder($this);
}
/**
* Get the MongoDB database object.
* @return \MongoDB\Database
*/
public function getMongoDB()
{
return $this->db;
}
/**
* return MongoDB object.
* @return \MongoDB\Client
*/
public function getMongoClient()
{
return $this->connection;
}
/**
* {@inheritdoc}
*/
public function getDatabaseName()
{
return $this->getMongoDB()->getDatabaseName();
}
/**
* Create a new MongoDB connection.
* @param string $dsn
* @param array $config
* @param array $options
* @return \MongoDB\Client
*/
protected function createConnection($dsn, array $config, array $options)
{
// By default driver options is an empty array.
$driverOptions = [];
if (isset($config['driver_options']) && is_array($config['driver_options'])) {
$driverOptions = $config['driver_options'];
}
// Check if the credentials are not already set in the options
if (!isset($options['username']) && !empty($config['username'])) {
$options['username'] = $config['username'];
}
if (!isset($options['password']) && !empty($config['password'])) {
$options['password'] = $config['password'];
}
return new Client($dsn, $options, $driverOptions);
}
/**
* @inheritdoc
*/
public function disconnect()
{
unset($this->connection);
}
/**
* Determine if the given configuration array has a dsn string.
* @param array $config
* @return bool
*/
protected function hasDsnString(array $config)
{
return isset($config['dsn']) && !empty($config['dsn']);
}
/**
* Get the DSN string form configuration.
* @param array $config
* @return string
*/
protected function getDsnString(array $config)
{
return $config['dsn'];
}
/**
* Get the DSN string for a host / port configuration.
* @param array $config
* @return string
*/
protected function getHostDsn(array $config)
{
// Treat host option as array of hosts
$hosts = is_array($config['host']) ? $config['host'] : [$config['host']];
foreach ($hosts as &$host) {
// Check if we need to add a port to the host
if (strpos($host, ':') === false && !empty($config['port'])) {
$host = $host . ':' . $config['port'];
}
}
// Check if we want to authenticate against a specific database.
$auth_database = isset($config['options']) && !empty($config['options']['database']) ? $config['options']['database'] : null;
return 'mongodb://' . implode(',', $hosts) . ($auth_database ? '/' . $auth_database : '');
}
/**
* Create a DSN string from a configuration.
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
return $this->hasDsnString($config)
? $this->getDsnString($config)
: $this->getHostDsn($config);
}
/**
* @inheritdoc
*/
public function getElapsedTime($start)
{
return parent::getElapsedTime($start);
}
/**
* @inheritdoc
*/
public function getDriverName()
{
return 'mongodb';
}
/**
* @inheritdoc
*/
protected function getDefaultPostProcessor()
{
return new Query\Processor();
}
/**
* @inheritdoc
*/
protected function getDefaultQueryGrammar()
{
return new Query\Grammar();
}
/**
* @inheritdoc
*/
protected function getDefaultSchemaGrammar()
{
return new Schema\Grammar();
}
/**
* Dynamically pass methods to the connection.
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return call_user_func_array([$this->db, $method], $parameters);
}
}

@ -0,0 +1,209 @@
<?php
namespace Ifornew\Mongodb\Eloquent;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Ifornew\Mongodb\Helpers\QueriesRelationships;
use MongoDB\Driver\Cursor;
use MongoDB\Model\BSONDocument;
class Builder extends EloquentBuilder
{
use QueriesRelationships;
/**
* The methods that should be returned from query builder.
* @var array
*/
protected $passthru = [
'toSql',
'insert',
'insertGetId',
'pluck',
'count',
'min',
'max',
'avg',
'sum',
'exists',
'push',
'pull',
];
/**
* @inheritdoc
*/
public function update(array $values, array $options = [])
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$relation->performUpdate($this->model, $values);
return 1;
}
return $this->toBase()->update($this->addUpdatedAtColumn($values), $options);
}
/**
* @inheritdoc
*/
public function insert(array $values)
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$relation->performInsert($this->model, $values);
return true;
}
return parent::insert($values);
}
/**
* @inheritdoc
*/
public function insertGetId(array $values, $sequence = null)
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$relation->performInsert($this->model, $values);
return $this->model->getKey();
}
return parent::insertGetId($values, $sequence);
}
/**
* @inheritdoc
*/
public function delete()
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$relation->performDelete($this->model);
return $this->model->getKey();
}
return parent::delete();
}
/**
* @inheritdoc
*/
public function increment($column, $amount = 1, array $extra = [])
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$value = $this->model->{$column};
// When doing increment and decrements, Eloquent will automatically
// sync the original attributes. We need to change the attribute
// temporary in order to trigger an update query.
$this->model->{$column} = null;
$this->model->syncOriginalAttribute($column);
$result = $this->model->update([$column => $value]);
return $result;
}
return parent::increment($column, $amount, $extra);
}
/**
* @inheritdoc
*/
public function decrement($column, $amount = 1, array $extra = [])
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$value = $this->model->{$column};
// When doing increment and decrements, Eloquent will automatically
// sync the original attributes. We need to change the attribute
// temporary in order to trigger an update query.
$this->model->{$column} = null;
$this->model->syncOriginalAttribute($column);
return $this->model->update([$column => $value]);
}
return parent::decrement($column, $amount, $extra);
}
/**
* @inheritdoc
*/
public function chunkById($count, callable $callback, $column = '_id', $alias = null)
{
return parent::chunkById($count, $callback, $column, $alias);
}
/**
* @inheritdoc
*/
public function raw($expression = null)
{
// Get raw results from the query builder.
$results = $this->query->raw($expression);
// Convert MongoCursor results to a collection of models.
if ($results instanceof Cursor) {
$results = iterator_to_array($results, false);
return $this->model->hydrate($results);
} // Convert Mongo BSONDocument to a single object.
elseif ($results instanceof BSONDocument) {
$results = $results->getArrayCopy();
return $this->model->newFromBuilder((array) $results);
} // The result is a single object.
elseif (is_array($results) && array_key_exists('_id', $results)) {
return $this->model->newFromBuilder((array) $results);
}
return $results;
}
/**
* Add the "updated at" column to an array of values.
* TODO Remove if https://github.com/laravel/framework/commit/6484744326531829341e1ff886cc9b628b20d73e
* wiil be reverted
* Issue in laravel frawework https://github.com/laravel/framework/issues/27791
* @param array $values
* @return array
*/
protected function addUpdatedAtColumn(array $values)
{
if (!$this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) {
return $values;
}
$column = $this->model->getUpdatedAtColumn();
$values = array_merge(
[$column => $this->model->freshTimestampString()],
$values
);
return $values;
}
/**
* @return \Illuminate\Database\ConnectionInterface
*/
public function getConnection()
{
return $this->query->getConnection();
}
}

@ -0,0 +1,78 @@
<?php
namespace Ifornew\Mongodb\Eloquent;
use Illuminate\Support\Str;
use Ifornew\Mongodb\Relations\EmbedsMany;
use Ifornew\Mongodb\Relations\EmbedsOne;
trait EmbedsRelations
{
/**
* Define an embedded one-to-many relationship.
* @param string $related
* @param string $localKey
* @param string $foreignKey
* @param string $relation
* @return \Ifornew\Mongodb\Relations\EmbedsMany
*/
protected function embedsMany($related, $localKey = null, $foreignKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
if ($localKey === null) {
$localKey = $relation;
}
if ($foreignKey === null) {
$foreignKey = Str::snake(class_basename($this));
}
$query = $this->newQuery();
$instance = new $related;
return new EmbedsMany($query, $this, $instance, $localKey, $foreignKey, $relation);
}
/**
* Define an embedded one-to-many relationship.
* @param string $related
* @param string $localKey
* @param string $foreignKey
* @param string $relation
* @return \Ifornew\Mongodb\Relations\EmbedsOne
*/
protected function embedsOne($related, $localKey = null, $foreignKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
if ($localKey === null) {
$localKey = $relation;
}
if ($foreignKey === null) {
$foreignKey = Str::snake(class_basename($this));
}
$query = $this->newQuery();
$instance = new $related;
return new EmbedsOne($query, $this, $instance, $localKey, $foreignKey, $relation);
}
}

@ -0,0 +1,300 @@
<?php
namespace Ifornew\Mongodb\Eloquent;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Support\Str;
use Ifornew\Mongodb\Helpers\EloquentBuilder;
use Ifornew\Mongodb\Relations\BelongsTo;
use Ifornew\Mongodb\Relations\BelongsToMany;
use Ifornew\Mongodb\Relations\HasMany;
use Ifornew\Mongodb\Relations\HasOne;
use Ifornew\Mongodb\Relations\MorphTo;
trait HybridRelations
{
/**
* Define a one-to-one relationship.
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOne($related, $foreignKey = null, $localKey = null)
{
// Check if it is a relation with an original model.
if (!is_subclass_of($related, \Ifornew\Mongodb\Eloquent\Model::class)) {
return parent::hasOne($related, $foreignKey, $localKey);
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$localKey = $localKey ?: $this->getKeyName();
return new HasOne($instance->newQuery(), $this, $foreignKey, $localKey);
}
/**
* Define a polymorphic one-to-one relationship.
* @param string $related
* @param string $name
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
{
// Check if it is a relation with an original model.
if (!is_subclass_of($related, \Ifornew\Mongodb\Eloquent\Model::class)) {
return parent::morphOne($related, $name, $type, $id, $localKey);
}
$instance = new $related;
list($type, $id) = $this->getMorphs($name, $type, $id);
$localKey = $localKey ?: $this->getKeyName();
return new MorphOne($instance->newQuery(), $this, $type, $id, $localKey);
}
/**
* Define a one-to-many relationship.
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasMany($related, $foreignKey = null, $localKey = null)
{
// Check if it is a relation with an original model.
if (!is_subclass_of($related, \Ifornew\Mongodb\Eloquent\Model::class)) {
return parent::hasMany($related, $foreignKey, $localKey);
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$localKey = $localKey ?: $this->getKeyName();
return new HasMany($instance->newQuery(), $this, $foreignKey, $localKey);
}
/**
* Define a polymorphic one-to-many relationship.
* @param string $related
* @param string $name
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
// Check if it is a relation with an original model.
if (!is_subclass_of($related, \Ifornew\Mongodb\Eloquent\Model::class)) {
return parent::morphMany($related, $name, $type, $id, $localKey);
}
$instance = new $related;
// Here we will gather up the morph type and ID for the relationship so that we
// can properly query the intermediate table of a relation. Finally, we will
// get the table and create the relationship instances for the developers.
list($type, $id) = $this->getMorphs($name, $type, $id);
$table = $instance->getTable();
$localKey = $localKey ?: $this->getKeyName();
return new MorphMany($instance->newQuery(), $this, $type, $id, $localKey);
}
/**
* Define an inverse one-to-one or many relationship.
* @param string $related
* @param string $foreignKey
* @param string $otherKey
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if ($relation === null) {
list($current, $caller) = debug_backtrace(false, 2);
$relation = $caller['function'];
}
// Check if it is a relation with an original model.
if (!is_subclass_of($related, \Ifornew\Mongodb\Eloquent\Model::class)) {
return parent::belongsTo($related, $foreignKey, $otherKey, $relation);
}
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if ($foreignKey === null) {
$foreignKey = Str::snake($relation) . '_id';
}
$instance = new $related;
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$query = $instance->newQuery();
$otherKey = $otherKey ?: $instance->getKeyName();
return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation);
}
/**
* Define a polymorphic, inverse one-to-one or many relationship.
* @param string $name
* @param string $type
* @param string $id
* @param string $ownerKey
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null, $ownerKey = null)
{
// If no name is provided, we will use the backtrace to get the function name
// since that is most likely the name of the polymorphic interface. We can
// use that to get both the class and foreign key that will be utilized.
if ($name === null) {
list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$name = Str::snake($caller['function']);
}
list($type, $id) = $this->getMorphs($name, $type, $id);
// If the type value is null it is probably safe to assume we're eager loading
// the relationship. When that is the case we will pass in a dummy query as
// there are multiple types in the morph and we can't use single queries.
if (($class = $this->$type) === null) {
return new MorphTo(
$this->newQuery(), $this, $id, null, $type, $name
);
}
// If we are not eager loading the relationship we will essentially treat this
// as a belongs-to style relationship since morph-to extends that class and
// we will pass in the appropriate values so that it behaves as expected.
$class = $this->getActualClassNameForMorph($class);
$instance = new $class;
return new MorphTo(
$instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
);
}
/**
* Define a many-to-many relationship.
* @param string $related
* @param string $collection
* @param string $foreignKey
* @param string $otherKey
* @param string $parentKey
* @param string $relatedKey
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function belongsToMany(
$related,
$collection = null,
$foreignKey = null,
$otherKey = null,
$parentKey = null,
$relatedKey = null,
$relation = null
) {
// If no relationship name was passed, we will pull backtraces to get the
// name of the calling function. We will use that function name as the
// title of this relation since that is a great convention to apply.
if ($relation === null) {
$relation = $this->guessBelongsToManyRelation();
}
// Check if it is a relation with an original model.
if (!is_subclass_of($related, \Ifornew\Mongodb\Eloquent\Model::class)) {
return parent::belongsToMany(
$related,
$collection,
$foreignKey,
$otherKey,
$parentKey,
$relatedKey,
$relation
);
}
// First, we'll need to determine the foreign key and "other key" for the
// relationship. Once we have determined the keys we'll make the query
// instances as well as the relationship instances we need for this.
$foreignKey = $foreignKey ?: $this->getForeignKey() . 's';
$instance = new $related;
$otherKey = $otherKey ?: $instance->getForeignKey() . 's';
// If no table name was provided, we can guess it by concatenating the two
// models using underscores in alphabetical order. The two model names
// are transformed to snake case from their default CamelCase also.
if ($collection === null) {
$collection = $instance->getTable();
}
// Now we're ready to create a new query builder for the related model and
// the relationship instances for the relation. The relations will set
// appropriate query constraint and entirely manages the hydrations.
$query = $instance->newQuery();
return new BelongsToMany(
$query,
$this,
$collection,
$foreignKey,
$otherKey,
$parentKey ?: $this->getKeyName(),
$relatedKey ?: $instance->getKeyName(),
$relation
);
}
/**
* Get the relationship name of the belongs to many.
* @return string
*/
protected function guessBelongsToManyRelation()
{
if (method_exists($this, 'getBelongsToManyCaller')) {
return $this->getBelongsToManyCaller();
}
return parent::guessBelongsToManyRelation();
}
/**
* @inheritdoc
*/
public function newEloquentBuilder($query)
{
if (is_subclass_of($this, \Ifornew\Mongodb\Eloquent\Model::class)) {
return new Builder($query);
}
return new EloquentBuilder($query);
}
}

@ -0,0 +1,482 @@
<?php
namespace Ifornew\Mongodb\Eloquent;
use Carbon\Carbon;
use DateTime;
use Illuminate\Contracts\Queue\QueueableCollection;
use Illuminate\Contracts\Queue\QueueableEntity;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Ifornew\Mongodb\Query\Builder as QueryBuilder;
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\UTCDateTime;
abstract class Model extends BaseModel
{
use HybridRelations, EmbedsRelations;
/**
* The collection associated with the model.
* @var string
*/
protected $collection;
/**
* The primary key for the model.
* @var string
*/
protected $primaryKey = '_id';
/**
* The primary key type.
* @var string
*/
protected $keyType = 'string';
/**
* The parent relation instance.
* @var Relation
*/
protected $parentRelation;
/**
* Custom accessor for the model's id.
* @param mixed $value
* @return mixed
*/
public function getIdAttribute($value = null)
{
// If we don't have a value for 'id', we will use the Mongo '_id' value.
// This allows us to work with models in a more sql-like way.
if (!$value && array_key_exists('_id', $this->attributes)) {
$value = $this->attributes['_id'];
}
// Convert ObjectID to string.
if ($value instanceof ObjectID) {
return (string) $value;
} elseif ($value instanceof Binary) {
return (string) $value->getData();
}
return $value;
}
/**
* @inheritdoc
*/
public function getQualifiedKeyName()
{
return $this->getKeyName();
}
/**
* @inheritdoc
*/
public function fromDateTime($value)
{
// If the value is already a UTCDateTime instance, we don't need to parse it.
if ($value instanceof UTCDateTime) {
return $value;
}
// Let Eloquent convert the value to a DateTime instance.
if (!$value instanceof DateTime) {
$value = parent::asDateTime($value);
}
return new UTCDateTime($value->getTimestamp() * 1000);
}
/**
* @inheritdoc
*/
protected function asDateTime($value)
{
// Convert UTCDateTime instances.
if ($value instanceof UTCDateTime) {
return Carbon::createFromTimestamp($value->toDateTime()->getTimestamp());
}
return parent::asDateTime($value);
}
/**
* @inheritdoc
*/
public function getDateFormat()
{
return $this->dateFormat ?: 'Y-m-d H:i:s';
}
/**
* @inheritdoc
*/
public function freshTimestamp()
{
return new UTCDateTime(time() * 1000);
}
/**
* @inheritdoc
*/
public function getTable()
{
return $this->collection ?: parent::getTable();
}
/**
* @inheritdoc
*/
public function getAttribute($key)
{
if (!$key) {
return;
}
// Dot notation support.
if (Str::contains($key, '.') && Arr::has($this->attributes, $key)) {
return $this->getAttributeValue($key);
}
// This checks for embedded relation support.
if (method_exists($this, $key) && !method_exists(self::class, $key)) {
return $this->getRelationValue($key);
}
return parent::getAttribute($key);
}
/**
* @inheritdoc
*/
protected function getAttributeFromArray($key)
{
// Support keys in dot notation.
if (Str::contains($key, '.')) {
return Arr::get($this->attributes, $key);
}
return parent::getAttributeFromArray($key);
}
/**
* @inheritdoc
*/
public function setAttribute($key, $value)
{
// Convert _id to ObjectID.
if ($key == '_id' && is_string($value)) {
$builder = $this->newBaseQueryBuilder();
$value = $builder->convertKey($value);
} // Support keys in dot notation.
elseif (Str::contains($key, '.')) {
if (in_array($key, $this->getDates()) && $value) {
$value = $this->fromDateTime($value);
}
Arr::set($this->attributes, $key, $value);
return;
}
return parent::setAttribute($key, $value);
}
/**
* @inheritdoc
*/
public function attributesToArray()
{
$attributes = parent::attributesToArray();
// Because the original Eloquent never returns objects, we convert
// MongoDB related objects to a string representation. This kind
// of mimics the SQL behaviour so that dates are formatted
// nicely when your models are converted to JSON.
foreach ($attributes as $key => &$value) {
if ($value instanceof ObjectID) {
$value = (string) $value;
} elseif ($value instanceof Binary) {
$value = (string) $value->getData();
}
}
// Convert dot-notation dates.
foreach ($this->getDates() as $key) {
if (Str::contains($key, '.') && Arr::has($attributes, $key)) {
Arr::set($attributes, $key, (string) $this->asDateTime(Arr::get($attributes, $key)));
}
}
return $attributes;
}
/**
* @inheritdoc
*/
public function getCasts()
{
return $this->casts;
}
/**
* @inheritdoc
*/
public function originalIsEquivalent($key, $current)
{
if (!array_key_exists($key, $this->original)) {
return false;
}
$original = $this->getOriginal($key);
if ($current === $original) {
return true;
}
if (null === $current) {
return false;
}
if ($this->isDateAttribute($key)) {
$current = $current instanceof UTCDateTime ? $this->asDateTime($current) : $current;
$original = $original instanceof UTCDateTime ? $this->asDateTime($original) : $original;
return $current == $original;
}
if ($this->hasCast($key)) {
return $this->castAttribute($key, $current) ===
$this->castAttribute($key, $original);
}
return is_numeric($current) && is_numeric($original)
&& strcmp((string) $current, (string) $original) === 0;
}
/**
* Remove one or more fields.
* @param mixed $columns
* @return int
*/
public function drop($columns)
{
$columns = Arr::wrap($columns);
// Unset attributes
foreach ($columns as $column) {
$this->__unset($column);
}
// Perform unset only on current document
return $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns);
}
/**
* @inheritdoc
*/
public function push()
{
if ($parameters = func_get_args()) {
$unique = false;
if (count($parameters) === 3) {
list($column, $values, $unique) = $parameters;
} else {
list($column, $values) = $parameters;
}
// Do batch push by default.
$values = Arr::wrap($values);
$query = $this->setKeysForSaveQuery($this->newQuery());
$this->pushAttributeValues($column, $values, $unique);
return $query->push($column, $values, $unique);
}
return parent::push();
}
/**
* Remove one or more values from an array.
* @param string $column
* @param mixed $values
* @return mixed
*/
public function pull($column, $values)
{
// Do batch pull by default.
$values = Arr::wrap($values);
$query = $this->setKeysForSaveQuery($this->newQuery());
$this->pullAttributeValues($column, $values);
return $query->pull($column, $values);
}
/**
* Append one or more values to the underlying attribute value and sync with original.
* @param string $column
* @param array $values
* @param bool $unique
*/
protected function pushAttributeValues($column, array $values, $unique = false)
{
$current = $this->getAttributeFromArray($column) ?: [];
foreach ($values as $value) {
// Don't add duplicate values when we only want unique values.
if ($unique && (!is_array($current) || in_array($value, $current))) {
continue;
}
$current[] = $value;
}
$this->attributes[$column] = $current;
$this->syncOriginalAttribute($column);
}
/**
* Remove one or more values to the underlying attribute value and sync with original.
* @param string $column
* @param array $values
*/
protected function pullAttributeValues($column, array $values)
{
$current = $this->getAttributeFromArray($column) ?: [];
if (is_array($current)) {
foreach ($values as $value) {
$keys = array_keys($current, $value);
foreach ($keys as $key) {
unset($current[$key]);
}
}
}
$this->attributes[$column] = array_values($current);
$this->syncOriginalAttribute($column);
}
/**
* @inheritdoc
*/
public function getForeignKey()
{
return Str::snake(class_basename($this)) . '_' . ltrim($this->primaryKey, '_');
}
/**
* Set the parent relation.
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
*/
public function setParentRelation(Relation $relation)
{
$this->parentRelation = $relation;
}
/**
* Get the parent relation.
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function getParentRelation()
{
return $this->parentRelation;
}
/**
* @inheritdoc
*/
public function newEloquentBuilder($query)
{
return new Builder($query);
}
/**
* @inheritdoc
*/
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new QueryBuilder($connection, $connection->getPostProcessor());
}
/**
* @inheritdoc
*/
protected function removeTableFromKey($key)
{
return $key;
}
/**
* Get the queueable relationships for the entity.
* @return array
*/
public function getQueueableRelations()
{
$relations = [];
foreach ($this->getRelationsWithoutParent() as $key => $relation) {
if (method_exists($this, $key)) {
$relations[] = $key;
}
if ($relation instanceof QueueableCollection) {
foreach ($relation->getQueueableRelations() as $collectionValue) {
$relations[] = $key . '.' . $collectionValue;
}
}
if ($relation instanceof QueueableEntity) {
foreach ($relation->getQueueableRelations() as $entityKey => $entityValue) {
$relations[] = $key . '.' . $entityValue;
}
}
}
return array_unique($relations);
}
/**
* Get loaded relations for the instance without parent.
* @return array
*/
protected function getRelationsWithoutParent()
{
$relations = $this->getRelations();
if ($parentRelation = $this->getParentRelation()) {
unset($relations[$parentRelation->getQualifiedForeignKeyName()]);
}
return $relations;
}
/**
* @inheritdoc
*/
public function __call($method, $parameters)
{
// Unset method
if ($method == 'unset') {
return call_user_func_array([$this, 'drop'], $parameters);
}
return parent::__call($method, $parameters);
}
}

@ -0,0 +1,16 @@
<?php
namespace Ifornew\Mongodb\Eloquent;
trait SoftDeletes
{
use \Illuminate\Database\Eloquent\SoftDeletes;
/**
* @inheritdoc
*/
public function getQualifiedDeletedAtColumn()
{
return $this->getDeletedAtColumn();
}
}

@ -0,0 +1,10 @@
<?php
namespace Ifornew\Mongodb\Helpers;
use Illuminate\Database\Eloquent\Builder;
class EloquentBuilder extends Builder
{
use QueriesRelationships;
}

@ -0,0 +1,183 @@
<?php
namespace Ifornew\Mongodb\Helpers;
use Closure;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Ifornew\Mongodb\Eloquent\Model;
trait QueriesRelationships
{
/**
* Add a relationship count / exists condition to the query.
* @param string $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @param Closure|null $callback
* @return Builder|static
*/
public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
if (strpos($relation, '.') !== false) {
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
}
$relation = $this->getRelationWithoutConstraints($relation);
//修正混合关联关系无法使用 whereHas 的问题
if(!($relation instanceof \Ifornew\Mongodb\Relations\BelongsTo ||
$relation instanceof \Ifornew\Mongodb\Relations\BelongsToMany ||
$relation instanceof \Ifornew\Mongodb\Relations\HasMany ||
$relation instanceof \Ifornew\Mongodb\Relations\HasOne ||
$relation instanceof \Ifornew\Mongodb\Relations\MorphTo ||
$relation instanceof \Ifornew\Mongodb\Relations\EmbedsMany ||
$relation instanceof \Ifornew\Mongodb\Relations\EmbedsOne ||
$relation instanceof \Ifornew\Mongodb\Relations\EmbedsOneOrMany
)){
return parent::has($relation, $operator, $count, $boolean, $callback);
}
// If this is a hybrid relation then we can not use a normal whereExists() query that relies on a subquery
// We need to use a `whereIn` query
if ($this->getModel() instanceof Model || $this->isAcrossConnections($relation)) {
return $this->addHybridHas($relation, $operator, $count, $boolean, $callback);
}
// If we only need to check for the existence of the relation, then we can optimize
// the subquery to only run a "where exists" clause instead of this full "count"
// clause. This will make these queries run much faster compared with a count.
$method = $this->canUseExistsForExistenceCheck($operator, $count)
? 'getRelationExistenceQuery'
: 'getRelationExistenceCountQuery';
$hasQuery = $relation->{$method}(
$relation->getRelated()->newQuery(), $this
);
// Next we will call any given callback as an "anonymous" scope so they can get the
// proper logical grouping of the where clauses if needed by this Eloquent query
// builder. Then, we will be ready to finalize and return this query instance.
if ($callback) {
$hasQuery->callScope($callback);
}
return $this->addHasWhere(
$hasQuery, $relation, $operator, $count, $boolean
);
}
/**
* @param $relation
* @return bool
*/
protected function isAcrossConnections($relation)
{
return $relation->getParent()->getConnectionName() !== $relation->getRelated()->getConnectionName();
}
/**
* Compare across databases
* @param $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @param Closure|null $callback
* @return mixed
* @throws Exception
*/
public function addHybridHas($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
$hasQuery = $relation->getQuery();
if ($callback) {
$hasQuery->callScope($callback);
}
// If the operator is <, <= or !=, we will use whereNotIn.
$not = in_array($operator, ['<', '<=', '!=']);
// If we are comparing to 0, we need an additional $not flip.
if ($count == 0) {
$not = !$not;
}
$relations = $hasQuery->pluck($this->getHasCompareKey($relation));
$relatedIds = $this->getConstrainedRelatedIds($relations, $operator, $count);
return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not);
}
/**
* @param $relation
* @return string
*/
protected function getHasCompareKey($relation)
{
if (method_exists($relation, 'getHasCompareKey')) {
return $relation->getHasCompareKey();
}
return $relation instanceof HasOneOrMany ? $relation->getForeignKeyName() : $relation->getOwnerKeyName();
}
/**
* @param $relations
* @param $operator
* @param $count
* @return array
*/
protected function getConstrainedRelatedIds($relations, $operator, $count)
{
$relationCount = array_count_values(array_map(function ($id) {
return (string) $id; // Convert Back ObjectIds to Strings
}, is_array($relations) ? $relations : $relations->flatten()->toArray()));
// Remove unwanted related objects based on the operator and count.
$relationCount = array_filter($relationCount, function ($counted) use ($count, $operator) {
// If we are comparing to 0, we always need all results.
if ($count == 0) {
return true;
}
switch ($operator) {
case '>=':
case '<':
return $counted >= $count;
case '>':
case '<=':
return $counted > $count;
case '=':
case '!=':
return $counted == $count;
}
});
// All related ids.
return array_keys($relationCount);
}
/**
* Returns key we are constraining this parent model's query with
* @param $relation
* @return string
* @throws Exception
*/
protected function getRelatedConstraintKey($relation)
{
if ($relation instanceof HasOneOrMany) {
return $this->model->getKeyName();
}
if ($relation instanceof BelongsTo) {
return $relation->getForeignKeyName();
}
if ($relation instanceof BelongsToMany && !$this->isAcrossConnections($relation)) {
return $this->model->getKeyName();
}
throw new Exception(class_basename($relation) . ' is not supported for hybrid query constraints.');
}
}

@ -0,0 +1,25 @@
<?php
namespace Ifornew\Mongodb;
use DB;
use Illuminate\Queue\QueueServiceProvider;
use Ifornew\Mongodb\Queue\Failed\MongoFailedJobProvider;
class MongodbQueueServiceProvider extends QueueServiceProvider
{
/**
* @inheritdoc
*/
protected function registerFailedJobServices()
{
// Add compatible queue failer if mongodb is configured.
if (DB::connection(config('queue.failed.database'))->getDriverName() == 'mongodb') {
$this->app->singleton('queue.failer', function ($app) {
return new MongoFailedJobProvider($app['db'], config('queue.failed.database'), config('queue.failed.table'));
});
} else {
parent::registerFailedJobServices();
}
}
}

@ -0,0 +1,41 @@
<?php
namespace Ifornew\Mongodb;
use Illuminate\Support\ServiceProvider;
use Ifornew\Mongodb\Eloquent\Model;
use Ifornew\Mongodb\Queue\MongoConnector;
class MongodbServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*/
public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}
/**
* Register the service provider.
*/
public function register()
{
// Add database driver.
$this->app->resolving('db', function ($db) {
$db->extend('mongodb', function ($config, $name) {
$config['name'] = $name;
return new Connection($config);
});
});
// Add connector for queue support.
$this->app->resolving('queue', function ($queue) {
$queue->addConnector('mongodb', function () {
return new MongoConnector($this->app['db']);
});
});
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,9 @@
<?php
namespace Ifornew\Mongodb\Query;
use Illuminate\Database\Query\Grammars\Grammar as BaseGrammar;
class Grammar extends BaseGrammar
{
}

@ -0,0 +1,9 @@
<?php
namespace Ifornew\Mongodb\Query;
use Illuminate\Database\Query\Processors\Processor as BaseProcessor;
class Processor extends BaseProcessor
{
}

@ -0,0 +1,67 @@
<?php
namespace Ifornew\Mongodb\Queue\Failed;
use Carbon\Carbon;
use Illuminate\Queue\Failed\DatabaseFailedJobProvider;
class MongoFailedJobProvider extends DatabaseFailedJobProvider
{
/**
* Log a failed job into storage.
* @param string $connection
* @param string $queue
* @param string $payload
* @return void
*/
public function log($connection, $queue, $payload, $exception)
{
$failed_at = Carbon::now()->getTimestamp();
$this->getTable()->insert(compact('connection', 'queue', 'payload', 'failed_at', 'exception'));
}
/**
* Get a list of all of the failed jobs.
* @return object[]
*/
public function all()
{
$all = $this->getTable()->orderBy('_id', 'desc')->get()->all();
$all = array_map(function ($job) {
$job['id'] = (string) $job['_id'];
return (object) $job;
}, $all);
return $all;
}
/**
* Get a single failed job.
* @param mixed $id
* @return object
*/
public function find($id)
{
$job = $this->getTable()->find($id);
if (!$job) {
return;
}
$job['id'] = (string) $job['_id'];
return (object) $job;
}
/**
* Delete a single failed job from storage.
* @param mixed $id
* @return bool
*/
public function forget($id)
{
return $this->getTable()->where('_id', $id)->delete() > 0;
}
}

@ -0,0 +1,40 @@
<?php
namespace Ifornew\Mongodb\Queue;
use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Queue\Connectors\ConnectorInterface;
use Illuminate\Support\Arr;
class MongoConnector implements ConnectorInterface
{
/**
* Database connections.
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $connections;
/**
* Create a new connector instance.
* @param \Illuminate\Database\ConnectionResolverInterface $connections
*/
public function __construct(ConnectionResolverInterface $connections)
{
$this->connections = $connections;
}
/**
* Establish a queue connection.
* @param array $config
* @return \Illuminate\Contracts\Queue\Queue
*/
public function connect(array $config)
{
return new MongoQueue(
$this->connections->connection(Arr::get($config, 'connection')),
$config['table'],
$config['queue'],
Arr::get($config, 'expire', 60)
);
}
}

@ -0,0 +1,25 @@
<?php
namespace Ifornew\Mongodb\Queue;
use Illuminate\Queue\Jobs\DatabaseJob;
class MongoJob extends DatabaseJob
{
/**
* Indicates if the job has been reserved.
* @return bool
*/
public function isReserved()
{
return $this->job->reserved;
}
/**
* @return \DateTime
*/
public function reservedAt()
{
return $this->job->reserved_at;
}
}

@ -0,0 +1,134 @@
<?php
namespace Ifornew\Mongodb\Queue;
use Carbon\Carbon;
use Illuminate\Queue\DatabaseQueue;
use Ifornew\Mongodb\Connection;
use MongoDB\Operation\FindOneAndUpdate;
class MongoQueue extends DatabaseQueue
{
/**
* The expiration time of a job.
* @var int|null
*/
protected $retryAfter = 60;
/**
* The connection name for the queue.
* @var string
*/
protected $connectionName;
/**
* @inheritdoc
*/
public function __construct(Connection $database, $table, $default = 'default', $retryAfter = 60)
{
parent::__construct($database, $table, $default, $retryAfter);
$this->retryAfter = $retryAfter;
}
/**
* @inheritdoc
*/
public function pop($queue = null)
{
$queue = $this->getQueue($queue);
if ($this->retryAfter !== null) {
$this->releaseJobsThatHaveBeenReservedTooLong($queue);
}
if ($job = $this->getNextAvailableJobAndReserve($queue)) {
return new MongoJob(
$this->container, $this, $job, $this->connectionName, $queue
);
}
}
/**
* Get the next available job for the queue and mark it as reserved.
* When using multiple daemon queue listeners to process jobs there
* is a possibility that multiple processes can end up reading the
* same record before one has flagged it as reserved.
* This race condition can result in random jobs being run more then
* once. To solve this we use findOneAndUpdate to lock the next jobs
* record while flagging it as reserved at the same time.
* @param string|null $queue
* @return \StdClass|null
*/
protected function getNextAvailableJobAndReserve($queue)
{
$job = $this->database->getCollection($this->table)->findOneAndUpdate(
[
'queue' => $this->getQueue($queue),
'reserved' => ['$ne' => 1],
'available_at' => ['$lte' => Carbon::now()->getTimestamp()],
],
[
'$set' => [
'reserved' => 1,
'reserved_at' => Carbon::now()->getTimestamp(),
],
'$inc' => [
'attempts' => 1,
],
],
[
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
'sort' => ['available_at' => 1],
]
);
if ($job) {
$job->id = $job->_id;
}
return $job;
}
/**
* Release the jobs that have been reserved for too long.
* @param string $queue
* @return void
*/
protected function releaseJobsThatHaveBeenReservedTooLong($queue)
{
$expiration = Carbon::now()->subSeconds($this->retryAfter)->getTimestamp();
$reserved = $this->database->collection($this->table)
->where('queue', $this->getQueue($queue))
->whereNotNull('reserved_at')
->where('reserved_at', '<=', $expiration)
->get();
foreach ($reserved as $job) {
$this->releaseJob($job['_id'], $job['attempts']);
}
}
/**
* Release the given job ID from reservation.
* @param string $id
* @param int $attempts
* @return void
*/
protected function releaseJob($id, $attempts)
{
$this->database->table($this->table)->where('_id', $id)->update([
'reserved' => 0,
'reserved_at' => null,
'attempts' => $attempts,
]);
}
/**
* @inheritdoc
*/
public function deleteReserved($queue, $id)
{
$this->database->collection($this->table)->where('_id', $id)->delete();
}
}

@ -0,0 +1,72 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class BelongsTo extends \Illuminate\Database\Eloquent\Relations\BelongsTo
{
/**
* Get the key for comparing against the parent key in "has" query.
* @return string
*/
public function getHasCompareKey()
{
return $this->getOwnerKey();
}
/**
* @inheritdoc
*/
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$this->query->where($this->getOwnerKey(), '=', $this->parent->{$this->foreignKey});
}
}
/**
* @inheritdoc
*/
public function addEagerConstraints(array $models)
{
// We'll grab the primary key name of the related models since it could be set to
// a non-standard name and not "id". We will then construct the constraint for
// our eagerly loading query so it returns the proper models from execution.
$key = $this->getOwnerKey();
$this->query->whereIn($key, $this->getEagerModelKeys($models));
}
/**
* @inheritdoc
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
return $query;
}
/**
* Get the owner key with backwards compatible support.
* @return string
*/
public function getOwnerKey()
{
return property_exists($this, 'ownerKey') ? $this->ownerKey : $this->otherKey;
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,345 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany;
use Illuminate\Support\Arr;
class BelongsToMany extends EloquentBelongsToMany
{
/**
* Get the key for comparing against the parent key in "has" query.
* @return string
*/
public function getHasCompareKey()
{
return $this->getForeignKey();
}
/**
* @inheritdoc
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
return $query;
}
/**
* @inheritdoc
*/
protected function hydratePivotRelation(array $models)
{
// Do nothing.
}
/**
* Set the select clause for the relation query.
* @param array $columns
* @return array
*/
protected function getSelectColumns(array $columns = ['*'])
{
return $columns;
}
/**
* @inheritdoc
*/
protected function shouldSelect(array $columns = ['*'])
{
return $columns;
}
/**
* @inheritdoc
*/
public function addConstraints()
{
if (static::$constraints) {
$this->setWhere();
}
}
/**
* Set the where clause for the relation query.
* @return $this
*/
protected function setWhere()
{
$foreign = $this->getForeignKey();
$this->query->where($foreign, '=', $this->parent->getKey());
return $this;
}
/**
* @inheritdoc
*/
public function save(Model $model, array $joining = [], $touch = true)
{
$model->save(['touch' => false]);
$this->attach($model, $joining, $touch);
return $model;
}
/**
* @inheritdoc
*/
public function create(array $attributes = [], array $joining = [], $touch = true)
{
$instance = $this->related->newInstance($attributes);
// Once we save the related model, we need to attach it to the base model via
// through intermediate table so we'll use the existing "attach" method to
// accomplish this which will insert the record and any more attributes.
$instance->save(['touch' => false]);
$this->attach($instance, $joining, $touch);
return $instance;
}
/**
* @inheritdoc
*/
public function sync($ids, $detaching = true)
{
$changes = [
'attached' => [],
'detached' => [],
'updated' => [],
];
if ($ids instanceof Collection) {
$ids = $ids->modelKeys();
}
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->parent->{$this->getRelatedKey()} ?: [];
// See issue #256.
if ($current instanceof Collection) {
$current = $ids->modelKeys();
}
$records = $this->formatSyncList($ids);
$current = Arr::wrap($current);
$detach = array_diff($current, array_keys($records));
// We need to make sure we pass a clean array, so that it is not interpreted
// as an associative array.
$detach = array_values($detach);
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// the array of the IDs given to the method which will complete the sync.
if ($detaching && count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = (array) array_map(function ($v) {
return is_numeric($v) ? (int) $v : (string) $v;
}, $detach);
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
if (count($changes['attached']) || count($changes['updated'])) {
$this->touchIfTouching();
}
return $changes;
}
/**
* @inheritdoc
*/
public function updateExistingPivot($id, array $attributes, $touch = true)
{
// Do nothing, we have no pivot table.
}
/**
* @inheritdoc
*/
public function attach($id, array $attributes = [], $touch = true)
{
if ($id instanceof Model) {
$model = $id;
$id = $model->getKey();
// Attach the new parent id to the related model.
$model->push($this->foreignPivotKey, $this->parent->getKey(), true);
} else {
if ($id instanceof Collection) {
$id = $id->modelKeys();
}
$query = $this->newRelatedQuery();
$query->whereIn($this->related->getKeyName(), (array) $id);
// Attach the new parent id to the related model.
$query->push($this->foreignPivotKey, $this->parent->getKey(), true);
}
// Attach the new ids to the parent model.
$this->parent->push($this->getRelatedKey(), (array) $id, true);
if ($touch) {
$this->touchIfTouching();
}
}
/**
* @inheritdoc
*/
public function detach($ids = [], $touch = true)
{
if ($ids instanceof Model) {
$ids = (array) $ids->getKey();
}
$query = $this->newRelatedQuery();
// If associated IDs were passed to the method we will only delete those
// associations, otherwise all of the association ties will be broken.
// We'll return the numbers of affected rows when we do the deletes.
$ids = (array) $ids;
// Detach all ids from the parent model.
$this->parent->pull($this->getRelatedKey(), $ids);
// Prepare the query to select all related objects.
if (count($ids) > 0) {
$query->whereIn($this->related->getKeyName(), $ids);
}
// Remove the relation to the parent.
$query->pull($this->foreignPivotKey, $this->parent->getKey());
if ($touch) {
$this->touchIfTouching();
}
return count($ids);
}
/**
* @inheritdoc
*/
protected function buildDictionary(Collection $results)
{
$foreign = $this->foreignPivotKey;
// First we will build a dictionary of child models keyed by the foreign key
// of the relation so that we will easily and quickly match them to their
// parents without having a possibly slow inner loops for every models.
$dictionary = [];
foreach ($results as $result) {
foreach ($result->$foreign as $item) {
$dictionary[$item][] = $result;
}
}
return $dictionary;
}
/**
* @inheritdoc
*/
protected function newPivotQuery()
{
return $this->newRelatedQuery();
}
/**
* Create a new query builder for the related model.
* @return \Illuminate\Database\Query\Builder
*/
public function newRelatedQuery()
{
return $this->related->newQuery();
}
/**
* Get the fully qualified foreign key for the relation.
* @return string
*/
public function getForeignKey()
{
return $this->foreignPivotKey;
}
/**
* @inheritdoc
*/
public function getQualifiedForeignPivotKeyName()
{
return $this->foreignPivotKey;
}
/**
* @inheritdoc
*/
public function getQualifiedRelatedPivotKeyName()
{
return $this->relatedPivotKey;
}
/**
* Format the sync list so that it is keyed by ID. (Legacy Support)
* The original function has been renamed to formatRecordsList since Laravel 5.3
* @param array $records
* @return array
* @deprecated
*/
protected function formatSyncList(array $records)
{
$results = [];
foreach ($records as $id => $attributes) {
if (!is_array($attributes)) {
list($id, $attributes) = [$attributes, []];
}
$results[$id] = $attributes;
}
return $results;
}
/**
* Get the related key with backwards compatible support.
* @return string
*/
public function getRelatedKey()
{
return property_exists($this, 'relatedPivotKey') ? $this->relatedPivotKey : $this->relatedKey;
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,331 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use MongoDB\BSON\ObjectID;
class EmbedsMany extends EmbedsOneOrMany
{
/**
* @inheritdoc
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* @inheritdoc
*/
public function getResults()
{
return $this->toCollection($this->getEmbedded());
}
/**
* Save a new model and attach it to the parent model.
* @param Model $model
* @return Model|bool
*/
public function performInsert(Model $model)
{
// Generate a new key if needed.
if ($model->getKeyName() == '_id' && !$model->getKey()) {
$model->setAttribute('_id', new ObjectID);
}
// For deeply nested documents, let the parent handle the changes.
if ($this->isNested()) {
$this->associate($model);
return $this->parent->save() ? $model : false;
}
// Push the new model to the database.
$result = $this->getBaseQuery()->push($this->localKey, $model->getAttributes(), true);
// Attach the model to its parent.
if ($result) {
$this->associate($model);
}
return $result ? $model : false;
}
/**
* Save an existing model and attach it to the parent model.
* @param Model $model
* @return Model|bool
*/
public function performUpdate(Model $model)
{
// For deeply nested documents, let the parent handle the changes.
if ($this->isNested()) {
$this->associate($model);
return $this->parent->save();
}
// Get the correct foreign key value.
$foreignKey = $this->getForeignKeyValue($model);
$values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.$.');
// Update document in database.
$result = $this->getBaseQuery()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey)
->update($values);
// Attach the model to its parent.
if ($result) {
$this->associate($model);
}
return $result ? $model : false;
}
/**
* Delete an existing model and detach it from the parent model.
* @param Model $model
* @return int
*/
public function performDelete(Model $model)
{
// For deeply nested documents, let the parent handle the changes.
if ($this->isNested()) {
$this->dissociate($model);
return $this->parent->save();
}
// Get the correct foreign key value.
$foreignKey = $this->getForeignKeyValue($model);
$result = $this->getBaseQuery()->pull($this->localKey, [$model->getKeyName() => $foreignKey]);
if ($result) {
$this->dissociate($model);
}
return $result;
}
/**
* Associate the model instance to the given parent, without saving it to the database.
* @param Model $model
* @return Model
*/
public function associate(Model $model)
{
if (!$this->contains($model)) {
return $this->associateNew($model);
}
return $this->associateExisting($model);
}
/**
* Dissociate the model instance from the given parent, without saving it to the database.
* @param mixed $ids
* @return int
*/
public function dissociate($ids = [])
{
$ids = $this->getIdsArrayFrom($ids);
$records = $this->getEmbedded();
$primaryKey = $this->related->getKeyName();
// Remove the document from the parent model.
foreach ($records as $i => $record) {
if (in_array($record[$primaryKey], $ids)) {
unset($records[$i]);
}
}
$this->setEmbedded($records);
// We return the total number of deletes for the operation. The developers
// can then check this number as a boolean type value or get this total count
// of records deleted for logging, etc.
return count($ids);
}
/**
* Destroy the embedded models for the given IDs.
* @param mixed $ids
* @return int
*/
public function destroy($ids = [])
{
$count = 0;
$ids = $this->getIdsArrayFrom($ids);
// Get all models matching the given ids.
$models = $this->getResults()->only($ids);
// Pull the documents from the database.
foreach ($models as $model) {
if ($model->delete()) {
$count++;
}
}
return $count;
}
/**
* Delete all embedded models.
* @return int
*/
public function delete()
{
// Overwrite the local key with an empty array.
$result = $this->query->update([$this->localKey => []]);
if ($result) {
$this->setEmbedded([]);
}
return $result;
}
/**
* Destroy alias.
* @param mixed $ids
* @return int
*/
public function detach($ids = [])
{
return $this->destroy($ids);
}
/**
* Save alias.
* @param Model $model
* @return Model
*/
public function attach(Model $model)
{
return $this->save($model);
}
/**
* Associate a new model instance to the given parent, without saving it to the database.
* @param Model $model
* @return Model
*/
protected function associateNew($model)
{
// Create a new key if needed.
if ($model->getKeyName() === '_id' && !$model->getAttribute('_id')) {
$model->setAttribute('_id', new ObjectID);
}
$records = $this->getEmbedded();
// Add the new model to the embedded documents.
$records[] = $model->getAttributes();
return $this->setEmbedded($records);
}
/**
* Associate an existing model instance to the given parent, without saving it to the database.
* @param Model $model
* @return Model
*/
protected function associateExisting($model)
{
// Get existing embedded documents.
$records = $this->getEmbedded();
$primaryKey = $this->related->getKeyName();
$key = $model->getKey();
// Replace the document in the parent model.
foreach ($records as &$record) {
if ($record[$primaryKey] == $key) {
$record = $model->getAttributes();
break;
}
}
return $this->setEmbedded($records);
}
/**
* Get a paginator for the "select" statement.
* @param int $perPage
* @return \Illuminate\Pagination\AbstractPaginator
*/
public function paginate($perPage = null)
{
$page = Paginator::resolveCurrentPage();
$perPage = $perPage ?: $this->related->getPerPage();
$results = $this->getEmbedded();
$total = count($results);
$start = ($page - 1) * $perPage;
$sliced = array_slice($results, $start, $perPage);
return new LengthAwarePaginator($sliced, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
]);
}
/**
* @inheritdoc
*/
protected function getEmbedded()
{
return parent::getEmbedded() ?: [];
}
/**
* @inheritdoc
*/
protected function setEmbedded($models)
{
if (!is_array($models)) {
$models = [$models];
}
return parent::setEmbedded(array_values($models));
}
/**
* @inheritdoc
*/
public function __call($method, $parameters)
{
if (method_exists(Collection::class, $method)) {
return call_user_func_array([$this->getResults(), $method], $parameters);
}
return parent::__call($method, $parameters);
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,145 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use MongoDB\BSON\ObjectID;
class EmbedsOne extends EmbedsOneOrMany
{
/**
* @inheritdoc
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, null);
}
return $models;
}
/**
* @inheritdoc
*/
public function getResults()
{
return $this->toModel($this->getEmbedded());
}
/**
* Save a new model and attach it to the parent model.
* @param Model $model
* @return Model|bool
*/
public function performInsert(Model $model)
{
// Generate a new key if needed.
if ($model->getKeyName() == '_id' && !$model->getKey()) {
$model->setAttribute('_id', new ObjectID);
}
// For deeply nested documents, let the parent handle the changes.
if ($this->isNested()) {
$this->associate($model);
return $this->parent->save() ? $model : false;
}
$result = $this->getBaseQuery()->update([$this->localKey => $model->getAttributes()]);
// Attach the model to its parent.
if ($result) {
$this->associate($model);
}
return $result ? $model : false;
}
/**
* Save an existing model and attach it to the parent model.
* @param Model $model
* @return Model|bool
*/
public function performUpdate(Model $model)
{
if ($this->isNested()) {
$this->associate($model);
return $this->parent->save();
}
$values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.');
$result = $this->getBaseQuery()->update($values);
// Attach the model to its parent.
if ($result) {
$this->associate($model);
}
return $result ? $model : false;
}
/**
* Delete an existing model and detach it from the parent model.
* @return int
*/
public function performDelete()
{
// For deeply nested documents, let the parent handle the changes.
if ($this->isNested()) {
$this->dissociate();
return $this->parent->save();
}
// Overwrite the local key with an empty array.
$result = $this->getBaseQuery()->update([$this->localKey => null]);
// Detach the model from its parent.
if ($result) {
$this->dissociate();
}
return $result;
}
/**
* Attach the model to its parent.
* @param Model $model
* @return Model
*/
public function associate(Model $model)
{
return $this->setEmbedded($model->getAttributes());
}
/**
* Detach the model from its parent.
* @return Model
*/
public function dissociate()
{
return $this->setEmbedded(null);
}
/**
* Delete all embedded models.
* @return int
*/
public function delete()
{
return $this->performDelete();
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,397 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\Relation;
use Ifornew\Mongodb\Eloquent\Model;
abstract class EmbedsOneOrMany extends Relation
{
/**
* The local key of the parent model.
* @var string
*/
protected $localKey;
/**
* The foreign key of the parent model.
* @var string
*/
protected $foreignKey;
/**
* The "name" of the relationship.
* @var string
*/
protected $relation;
/**
* Create a new embeds many relationship instance.
* @param Builder $query
* @param Model $parent
* @param Model $related
* @param string $localKey
* @param string $foreignKey
* @param string $relation
*/
public function __construct(Builder $query, Model $parent, Model $related, $localKey, $foreignKey, $relation)
{
$this->query = $query;
$this->parent = $parent;
$this->related = $related;
$this->localKey = $localKey;
$this->foreignKey = $foreignKey;
$this->relation = $relation;
// If this is a nested relation, we need to get the parent query instead.
if ($parentRelation = $this->getParentRelation()) {
$this->query = $parentRelation->getQuery();
}
$this->addConstraints();
}
/**
* @inheritdoc
*/
public function addConstraints()
{
if (static::$constraints) {
$this->query->where($this->getQualifiedParentKeyName(), '=', $this->getParentKey());
}
}
/**
* @inheritdoc
*/
public function addEagerConstraints(array $models)
{
// There are no eager loading constraints.
}
/**
* @inheritdoc
*/
public function match(array $models, Collection $results, $relation)
{
foreach ($models as $model) {
$results = $model->$relation()->getResults();
$model->setParentRelation($this);
$model->setRelation($relation, $results);
}
return $models;
}
/**
* Shorthand to get the results of the relationship.
* @param array $columns
* @return Collection
*/
public function get($columns = ['*'])
{
return $this->getResults();
}
/**
* Get the number of embedded models.
* @return int
*/
public function count()
{
return count($this->getEmbedded());
}
/**
* Attach a model instance to the parent model.
* @param Model $model
* @return Model|bool
*/
public function save(Model $model)
{
$model->setParentRelation($this);
return $model->save() ? $model : false;
}
/**
* Attach a collection of models to the parent instance.
* @param Collection|array $models
* @return Collection|array
*/
public function saveMany($models)
{
foreach ($models as $model) {
$this->save($model);
}
return $models;
}
/**
* Create a new instance of the related model.
* @param array $attributes
* @return Model
*/
public function create(array $attributes = [])
{
// Here we will set the raw attributes to avoid hitting the "fill" method so
// that we do not have to worry about a mass accessor rules blocking sets
// on the models. Otherwise, some of these attributes will not get set.
$instance = $this->related->newInstance($attributes);
$instance->setParentRelation($this);
$instance->save();
return $instance;
}
/**
* Create an array of new instances of the related model.
* @param array $records
* @return array
*/
public function createMany(array $records)
{
$instances = [];
foreach ($records as $record) {
$instances[] = $this->create($record);
}
return $instances;
}
/**
* Transform single ID, single Model or array of Models into an array of IDs.
* @param mixed $ids
* @return array
*/
protected function getIdsArrayFrom($ids)
{
if ($ids instanceof \Illuminate\Support\Collection) {
$ids = $ids->all();
}
if (!is_array($ids)) {
$ids = [$ids];
}
foreach ($ids as &$id) {
if ($id instanceof Model) {
$id = $id->getKey();
}
}
return $ids;
}
/**
* @inheritdoc
*/
protected function getEmbedded()
{
// Get raw attributes to skip relations and accessors.
$attributes = $this->parent->getAttributes();
// Get embedded models form parent attributes.
$embedded = isset($attributes[$this->localKey]) ? (array) $attributes[$this->localKey] : null;
return $embedded;
}
/**
* @inheritdoc
*/
protected function setEmbedded($records)
{
// Assign models to parent attributes array.
$attributes = $this->parent->getAttributes();
$attributes[$this->localKey] = $records;
// Set raw attributes to skip mutators.
$this->parent->setRawAttributes($attributes);
// Set the relation on the parent.
return $this->parent->setRelation($this->relation, $records === null ? null : $this->getResults());
}
/**
* Get the foreign key value for the relation.
* @param mixed $id
* @return mixed
*/
protected function getForeignKeyValue($id)
{
if ($id instanceof Model) {
$id = $id->getKey();
}
// Convert the id to MongoId if necessary.
return $this->getBaseQuery()->convertKey($id);
}
/**
* Convert an array of records to a Collection.
* @param array $records
* @return Collection
*/
protected function toCollection(array $records = [])
{
$models = [];
foreach ($records as $attributes) {
$models[] = $this->toModel($attributes);
}
if (count($models) > 0) {
$models = $this->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
}
/**
* Create a related model instanced.
* @param array $attributes
* @return Model
*/
protected function toModel($attributes = [])
{
if ($attributes === null) {
return;
}
$connection = $this->related->getConnection();
$model = $this->related->newFromBuilder(
(array) $attributes,
$connection ? $connection->getName() : null
);
$model->setParentRelation($this);
$model->setRelation($this->foreignKey, $this->parent);
// If you remove this, you will get segmentation faults!
$model->setHidden(array_merge($model->getHidden(), [$this->foreignKey]));
return $model;
}
/**
* Get the relation instance of the parent.
* @return Relation
*/
protected function getParentRelation()
{
return $this->parent->getParentRelation();
}
/**
* @inheritdoc
*/
public function getQuery()
{
// Because we are sharing this relation instance to models, we need
// to make sure we use separate query instances.
return clone $this->query;
}
/**
* @inheritdoc
*/
public function getBaseQuery()
{
// Because we are sharing this relation instance to models, we need
// to make sure we use separate query instances.
return clone $this->query->getQuery();
}
/**
* Check if this relation is nested in another relation.
* @return bool
*/
protected function isNested()
{
return $this->getParentRelation() != null;
}
/**
* Get the fully qualified local key name.
* @param string $glue
* @return string
*/
protected function getPathHierarchy($glue = '.')
{
if ($parentRelation = $this->getParentRelation()) {
return $parentRelation->getPathHierarchy($glue) . $glue . $this->localKey;
}
return $this->localKey;
}
/**
* @inheritdoc
*/
public function getQualifiedParentKeyName()
{
if ($parentRelation = $this->getParentRelation()) {
return $parentRelation->getPathHierarchy() . '.' . $this->parent->getKeyName();
}
return $this->parent->getKeyName();
}
/**
* Get the primary key value of the parent.
* @return string
*/
protected function getParentKey()
{
return $this->parent->getKey();
}
/**
* Return update values
* @param $array
* @param string $prepend
* @return array
*/
public static function getUpdateValues($array, $prepend = '')
{
$results = [];
foreach ($array as $key => $value) {
$results[$prepend . $key] = $value;
}
return $results;
}
/**
* Get the foreign key for the relationship.
* @return string
*/
public function getQualifiedForeignKeyName()
{
return $this->foreignKey;
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,87 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\HasMany as EloquentHasMany;
class HasMany extends EloquentHasMany
{
/**
* Get the plain foreign key.
* @return string
*/
public function getForeignKeyName()
{
return $this->foreignKey;
}
/**
* Get the plain foreign key.
* @return string
*/
public function getPlainForeignKey()
{
return $this->getForeignKeyName();
}
/**
* Get the key for comparing against the parent key in "has" query.
* @return string
*/
public function getHasCompareKey()
{
return $this->getForeignKeyName();
}
/**
* @inheritdoc
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$foreignKey = $this->getHasCompareKey();
return $query->select($foreignKey)->where($foreignKey, 'exists', true);
}
/**
* Add the constraints for a relationship count query.
* @param Builder $query
* @param Builder $parent
* @return Builder
*/
public function getRelationCountQuery(Builder $query, Builder $parent)
{
$foreignKey = $this->getHasCompareKey();
return $query->select($foreignKey)->where($foreignKey, 'exists', true);
}
/**
* Add the constraints for a relationship query.
* @param Builder $query
* @param Builder $parent
* @param array|mixed $columns
* @return Builder
*/
public function getRelationQuery(Builder $query, Builder $parent, $columns = ['*'])
{
$query->select($columns);
$key = $this->wrap($this->getQualifiedParentKeyName());
return $query->where($this->getHasCompareKey(), 'exists', true);
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,87 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\HasOne as EloquentHasOne;
class HasOne extends EloquentHasOne
{
/**
* Get the key for comparing against the parent key in "has" query.
* @return string
*/
public function getForeignKeyName()
{
return $this->foreignKey;
}
/**
* Get the key for comparing against the parent key in "has" query.
* @return string
*/
public function getHasCompareKey()
{
return $this->getForeignKeyName();
}
/**
* Get the plain foreign key.
* @return string
*/
public function getPlainForeignKey()
{
return $this->getForeignKeyName();
}
/**
* @inheritdoc
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$foreignKey = $this->getForeignKeyName();
return $query->select($foreignKey)->where($foreignKey, 'exists', true);
}
/**
* Add the constraints for a relationship count query.
* @param Builder $query
* @param Builder $parent
* @return Builder
*/
public function getRelationCountQuery(Builder $query, Builder $parent)
{
$foreignKey = $this->getForeignKeyName();
return $query->select($foreignKey)->where($foreignKey, 'exists', true);
}
/**
* Add the constraints for a relationship query.
* @param Builder $query
* @param Builder $parent
* @param array|mixed $columns
* @return Builder
*/
public function getRelationQuery(Builder $query, Builder $parent, $columns = ['*'])
{
$query->select($columns);
$key = $this->wrap($this->getQualifiedParentKeyName());
return $query->where($this->getForeignKeyName(), 'exists', true);
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,56 @@
<?php
namespace Ifornew\Mongodb\Relations;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\MorphTo as EloquentMorphTo;
class MorphTo extends EloquentMorphTo
{
/**
* @inheritdoc
*/
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$this->query->where($this->getOwnerKey(), '=', $this->parent->{$this->foreignKey});
}
}
/**
* @inheritdoc
*/
protected function getResultsByType($type)
{
$instance = $this->createModelByType($type);
$key = $instance->getKeyName();
$query = $instance->newQuery();
return $query->whereIn($key, $this->gatherKeysByType($type))->get();
}
/**
* Get the owner key with backwards compatible support.
* @return string
*/
public function getOwnerKey()
{
return property_exists($this, 'ownerKey') ? $this->ownerKey : $this->otherKey;
}
/**
* Get the name of the "where in" method for eager loading.
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(EloquentModel $model, $key)
{
return 'whereIn';
}
}

@ -0,0 +1,302 @@
<?php
namespace Ifornew\Mongodb\Schema;
use Illuminate\Database\Connection;
class Blueprint extends \Illuminate\Database\Schema\Blueprint
{
/**
* The MongoConnection object for this blueprint.
* @var \Ifornew\Mongodb\Connection
*/
protected $connection;
/**
* The MongoCollection object for this blueprint.
* @var \Ifornew\Mongodb\Collection|\MongoDB\Collection
*/
protected $collection;
/**
* Fluent columns.
* @var array
*/
protected $columns = [];
/**
* @inheritdoc
*/
public function __construct(Connection $connection, $collection)
{
$this->connection = $connection;
$this->collection = $this->connection->getCollection($collection);
}
/**
* @inheritdoc
*/
public function index($columns = null, $name = null, $algorithm = null, $options = [])
{
$columns = $this->fluent($columns);
// Columns are passed as a default array.
if (is_array($columns) && is_int(key($columns))) {
// Transform the columns to the required array format.
$transform = [];
foreach ($columns as $column) {
$transform[$column] = 1;
}
$columns = $transform;
}
if ($name !== null) {
$options['name'] = $name;
}
$this->collection->createIndex($columns, $options);
return $this;
}
/**
* @inheritdoc
*/
public function primary($columns = null, $name = null, $algorithm = null, $options = [])
{
return $this->unique($columns, $name, $algorithm, $options);
}
/**
* @inheritdoc
*/
public function dropIndex($indexOrColumns = null)
{
$indexOrColumns = $this->transformColumns($indexOrColumns);
$this->collection->dropIndex($indexOrColumns);
return $this;
}
/**
* Indicate that the given index should be dropped, but do not fail if it didn't exist.
*
* @param string|array $indexOrColumns
* @return Blueprint
*/
public function dropIndexIfExists($indexOrColumns = null)
{
if ($this->hasIndex($indexOrColumns)) {
$this->dropIndex($indexOrColumns);
}
return $this;
}
/**
* Check whether the given index exists.
*
* @param string|array $indexOrColumns
* @return bool
*/
public function hasIndex($indexOrColumns = null)
{
$indexOrColumns = $this->transformColumns($indexOrColumns);
foreach ($this->collection->listIndexes() as $index) {
if (is_array($indexOrColumns) && in_array($index->getName(), $indexOrColumns)) {
return true;
}
if (is_string($indexOrColumns) && $index->getName() == $indexOrColumns) {
return true;
}
}
return false;
}
/**
* @param string|array $indexOrColumns
* @return string
*/
protected function transformColumns($indexOrColumns)
{
if (is_array($indexOrColumns)) {
$indexOrColumns = $this->fluent($indexOrColumns);
// Transform the columns to the index name.
$transform = [];
foreach ($indexOrColumns as $column) {
$transform[$column] = $column . '_1';
}
$indexOrColumns = implode('_', $transform);
}
return $indexOrColumns;
}
/**
* @inheritdoc
*/
public function unique($columns = null, $name = null, $algorithm = null, $options = [])
{
$columns = $this->fluent($columns);
$options['unique'] = true;
$this->index($columns, $name, $algorithm, $options);
return $this;
}
/**
* Specify a non blocking index for the collection.
* @param string|array $columns
* @return Blueprint
*/
public function background($columns = null)
{
$columns = $this->fluent($columns);
$this->index($columns, null, null, ['background' => true]);
return $this;
}
/**
* Specify a sparse index for the collection.
* @param string|array $columns
* @param array $options
* @return Blueprint
*/
public function sparse($columns = null, $options = [])
{
$columns = $this->fluent($columns);
$options['sparse'] = true;
$this->index($columns, null, null, $options);
return $this;
}
/**
* Specify a geospatial index for the collection.
* @param string|array $columns
* @param string $index
* @param array $options
* @return Blueprint
*/
public function geospatial($columns = null, $index = '2d', $options = [])
{
if ($index == '2d' || $index == '2dsphere') {
$columns = $this->fluent($columns);
$columns = array_flip($columns);
foreach ($columns as $column => $value) {
$columns[$column] = $index;
}
$this->index($columns, null, null, $options);
}
return $this;
}
/**
* Specify the number of seconds after wich a document should be considered expired based,
* on the given single-field index containing a date.
* @param string|array $columns
* @param int $seconds
* @return Blueprint
*/
public function expire($columns, $seconds)
{
$columns = $this->fluent($columns);
$this->index($columns, null, null, ['expireAfterSeconds' => $seconds]);
return $this;
}
/**
* @inheritdoc
*/
public function create()
{
$collection = $this->collection->getCollectionName();
$db = $this->connection->getMongoDB();
// Ensure the collection is created.
$db->createCollection($collection);
}
/**
* @inheritdoc
*/
public function drop()
{
$this->collection->drop();
}
/**
* @inheritdoc
*/
public function addColumn($type, $name, array $parameters = [])
{
$this->fluent($name);
return $this;
}
/**
* Specify a sparse and unique index for the collection.
* @param string|array $columns
* @param array $options
* @return Blueprint
*/
public function sparse_and_unique($columns = null, $options = [])
{
$columns = $this->fluent($columns);
$options['sparse'] = true;
$options['unique'] = true;
$this->index($columns, null, null, $options);
return $this;
}
/**
* Allow fluent columns.
* @param string|array $columns
* @return string|array
*/
protected function fluent($columns = null)
{
if ($columns === null) {
return $this->columns;
} elseif (is_string($columns)) {
return $this->columns = [$columns];
} else {
return $this->columns = $columns;
}
}
/**
* Allows the use of unsupported schema methods.
* @param $method
* @param $args
* @return Blueprint
*/
public function __call($method, $args)
{
// Dummy.
return $this;
}
}

@ -0,0 +1,150 @@
<?php
namespace Ifornew\Mongodb\Schema;
use Closure;
use Ifornew\Mongodb\Connection;
class Builder extends \Illuminate\Database\Schema\Builder
{
/**
* @inheritdoc
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* @inheritdoc
*/
public function hasColumn($table, $column)
{
return true;
}
/**
* @inheritdoc
*/
public function hasColumns($table, array $columns)
{
return true;
}
/**
* Determine if the given collection exists.
* @param string $collection
* @return bool
*/
public function hasCollection($collection)
{
$db = $this->connection->getMongoDB();
foreach ($db->listCollections() as $collectionFromMongo) {
if ($collectionFromMongo->getName() == $collection) {
return true;
}
}
return false;
}
/**
* @inheritdoc
*/
public function hasTable($collection)
{
return $this->hasCollection($collection);
}
/**
* Modify a collection on the schema.
* @param string $collection
* @param Closure $callback
* @return bool
*/
public function collection($collection, Closure $callback)
{
$blueprint = $this->createBlueprint($collection);
if ($callback) {
$callback($blueprint);
}
}
/**
* @inheritdoc
*/
public function table($collection, Closure $callback)
{
return $this->collection($collection, $callback);
}
/**
* @inheritdoc
*/
public function create($collection, Closure $callback = null, array $options = [])
{
$blueprint = $this->createBlueprint($collection);
$blueprint->create($options);
if ($callback) {
$callback($blueprint);
}
}
/**
* @inheritdoc
*/
public function dropIfExists($collection)
{
if ($this->hasCollection($collection)) {
return $this->drop($collection);
}
return false;
}
/**
* @inheritdoc
*/
public function drop($collection)
{
$blueprint = $this->createBlueprint($collection);
return $blueprint->drop();
}
/**
* @inheritdoc
*/
public function dropAllTables()
{
foreach ($this->getAllCollections() as $collection) {
$this->drop($collection);
}
}
/**
* @inheritdoc
*/
protected function createBlueprint($collection, Closure $callback = null)
{
return new Blueprint($this->connection, $collection);
}
/**
* Get all of the collections names for the database.
* @return array
*/
protected function getAllCollections()
{
$collections = [];
foreach ($this->connection->getMongoDB()->listCollections() as $collection) {
$collections[] = $collection->getName();
}
return $collections;
}
}

@ -0,0 +1,9 @@
<?php
namespace Ifornew\Mongodb\Schema;
use Illuminate\Database\Schema\Grammars\Grammar as BaseGrammar;
class Grammar extends BaseGrammar
{
}

@ -0,0 +1,83 @@
<?php
namespace Ifornew\Mongodb;
use Ifornew\Mongodb\Query\Builder;
trait TransactionTrait
{
/**
* @var \MongoDB\Driver\Session
*/
protected $session;
/**
* @description: 创建事务
*
* @param array $options
* @date 2019-07-22
*/
public function beginTransaction(array $options = [])
{
if (!$this->getSession()) {
$this->session = $this->getMongoClient()->startSession();
$this->session->startTransaction($options);
}
}
/**
* @description: 提交
*
* @date 2019-07-22
*/
public function commit()
{
if ($this->getSession()) {
$this->session->commitTransaction();
$this->clearSession();
}
}
/**
* @description: 回滚
*
* @param $toLevel
* @date 2019-07-22
*/
public function rollBack($toLevel = null)
{
if ($this->getSession()) {
$this->session->abortTransaction();
$this->clearSession();
}
}
/**
* @description: 清理session
*
* @date 2019-07-22
*/
protected function clearSession()
{
$this->session = null;
}
/**
*
* @param string $collection
* @return Builder|\Ifornew\Mongodb\Query\Builder
* @date 2019-07-22
*/
public function collection($collection)
{
$query = new Query\Builder($this, $this->getPostProcessor());
return $query->from($collection);
}
public function getSession()
{
return $this->session;
}
}

@ -0,0 +1,53 @@
<?php
namespace Ifornew\Mongodb\Validation;
class DatabasePresenceVerifier extends \Illuminate\Validation\DatabasePresenceVerifier
{
/**
* Count the number of objects in a collection having the given value.
* @param string $collection
* @param string $column
* @param string $value
* @param int $excludeId
* @param string $idColumn
* @param array $extra
* @return int
*/
public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = [])
{
$query = $this->table($collection)->where($column, 'regex', "/$value/i");
if ($excludeId !== null && $excludeId != 'NULL') {
$query->where($idColumn ?: 'id', '<>', $excludeId);
}
foreach ($extra as $key => $extraValue) {
$this->addWhere($query, $key, $extraValue);
}
return $query->count();
}
/**
* Count the number of objects in a collection with the given values.
* @param string $collection
* @param string $column
* @param array $values
* @param array $extra
* @return int
*/
public function getMultiCount($collection, $column, array $values, array $extra = [])
{
// Generates a regex like '/(a|b|c)/i' which can query multiple values
$regex = '/(' . implode('|', $values) . ')/i';
$query = $this->table($collection)->where($column, 'regex', $regex);
foreach ($extra as $key => $extraValue) {
$this->addWhere($query, $key, $extraValue);
}
return $query->count();
}
}

@ -0,0 +1,15 @@
<?php
namespace Ifornew\Mongodb\Validation;
use Illuminate\Validation\ValidationServiceProvider as BaseProvider;
class ValidationServiceProvider extends BaseProvider
{
protected function registerPresenceVerifier()
{
$this->app->singleton('validation.presence', function ($app) {
return new DatabasePresenceVerifier($app['db']);
});
}
}
Loading…
Cancel
Save