diff --git a/.gitignore b/.gitignore index 1fe4e30f6..027823e15 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ composer.lock *.sublime-workspace *.project .idea/ +build/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 357d4036a..65b320c15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,3 +32,6 @@ script: after_success: - sh -c 'php vendor/bin/coveralls -v' + +notifications: + - email: false diff --git a/README.md b/README.md index 2a1263ec7..0005d18e7 100644 --- a/README.md +++ b/README.md @@ -1,948 +1,21 @@ -Laravel MongoDB -=============== +Moloquent (Laravel MongoDB) +============================= -[![Latest Stable Version](http://img.shields.io/github/release/jenssegers/laravel-mongodb.svg)](https://packagist.org/packages/jenssegers/mongodb) [![Total Downloads](http://img.shields.io/packagist/dm/jenssegers/mongodb.svg)](https://packagist.org/packages/jenssegers/mongodb) [![Build Status](http://img.shields.io/travis/jenssegers/laravel-mongodb.svg)](https://travis-ci.org/jenssegers/laravel-mongodb) [![Coverage Status](http://img.shields.io/coveralls/jenssegers/laravel-mongodb.svg)](https://coveralls.io/r/jenssegers/laravel-mongodb?branch=master) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/jenssegers) -An Eloquent model and Query builder with support for MongoDB, using the original Laravel API. *This library extends the original Laravel classes, so it uses exactly the same methods.* +[![Latest Version](http://img.shields.io/packagist/v/moloquent/moloquent.svg)](https://packagist.org/packages/moloquent/moloquent) +[![Downloads](http://img.shields.io/packagist/dt/moloquent/moloquent.svg)](https://packagist.org/packages/moloquent/moloquent) +[![Build Status](http://img.shields.io/travis/moloquent/moloquent.svg)](https://travis-ci.org/moloquent/moloquent) +[![Coverage Status](http://img.shields.io/coveralls/moloquent/moloquent.svg)](https://coveralls.io/r/moloquent/moloquent?branch=master) +[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/jenssegers) -Table of contents ------------------ -* [Installation](#installation) -* [Upgrading](#upgrading) -* [Configuration](#configuration) -* [Eloquent](#eloquent) -* [Optional: Alias](#optional-alias) -* [Query Builder](#query-builder) -* [Schema](#schema) -* [Extensions](#extensions) -* [Troubleshooting](#troubleshooting) -* [Examples](#examples) +An Eloquent model and Query builder with support for MongoDB, using the original Laravel API.**This library extends the original Laravel classes, so it uses exactly the same methods.** -Installation ------------- +Getting Started +---------------- +Just go ahead to [Documentation](https://moloquent.github.io) to get started using this library. -Make sure you have the MongoDB PHP driver installed. You can find installation instructions at http://php.net/manual/en/mongodb.installation.php -**WARNING**: The old mongo PHP driver is not supported anymore in versions >= 3.0. - -Installation using composer: - -``` -composer require jenssegers/mongodb -``` - -### Laravel version Compatibility - - Laravel | Package -:---------|:---------- - 4.2.x | 2.0.x - 5.0.x | 2.1.x - 5.1.x | 2.2.x or 3.0.x - 5.2.x | 2.3.x or 3.0.x - 5.3.x | 3.1.x - -And add the service provider in `config/app.php`: - -```php -Jenssegers\Mongodb\MongodbServiceProvider::class, -``` - -For usage with [Lumen](http://lumen.laravel.com), add the service provider in `bootstrap/app.php`. In this file, you will also need to enable Eloquent. You must however ensure that your call to `$app->withEloquent();` is **below** where you have registered the `MongodbServiceProvider`: - -```php -$app->register('Jenssegers\Mongodb\MongodbServiceProvider'); - -$app->withEloquent(); -``` - -The service provider will register a mongodb database extension with the original database manager. There is no need to register additional facades or objects. When using mongodb connections, Laravel will automatically provide you with the corresponding mongodb objects. - -For usage outside Laravel, check out the [Capsule manager](https://github.com/illuminate/database/blob/master/README.md) and add: - -```php -$capsule->getDatabaseManager()->extend('mongodb', function($config) -{ - return new Jenssegers\Mongodb\Connection($config); -}); -``` - -Upgrading ---------- - -#### Upgrading from version 2 to 3 - -In this new major release which supports the new mongodb PHP extension, we also moved the location of the Model class and replaced the MySQL model class with a trait. - -Please change all `Jenssegers\Mongodb\Model` references to `Jenssegers\Mongodb\Eloquent\Model` either at the top of your model files, or your registered alias. - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent {} -``` - -If you are using hybrid relations, your MySQL classes should now extend the original Eloquent model class `Illuminate\Database\Eloquent\Model` instead of the removed `Jenssegers\Eloquent\Model`. Instead use the new `Jenssegers\Mongodb\Eloquent\HybridRelations` trait. This should make things more clear as there is only one single model class in this package. - -```php -use Jenssegers\Mongodb\Eloquent\HybridRelations; - -class User extends Eloquent { - - use HybridRelations; - - protected $connection = 'mysql'; - -} -``` - -Embedded relations now return an `Illuminate\Database\Eloquent\Collection` rather than a custom Collection class. If you were using one of the special methods that were available, convert them to Collection operations. - -```php -$books = $user->books()->sortBy('title'); -``` - -Configuration +Who are we ? ------------- - -Change your default database connection name in `config/database.php`: - -```php -'default' => env('DB_CONNECTION', 'mongodb'), -``` - -And add a new mongodb connection: - -```php -'mongodb' => [ - 'driver' => 'mongodb', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 27017), - 'database' => env('DB_DATABASE'), - 'username' => env('DB_USERNAME'), - 'password' => env('DB_PASSWORD'), - 'options' => [ - 'database' => 'admin' // sets the authentication database required by mongo 3 - ] -], -``` - -You can connect to multiple servers or replica sets with the following configuration: - -```php -'mongodb' => [ - 'driver' => 'mongodb', - 'host' => ['server1', 'server2'], - 'port' => env('DB_PORT', 27017), - 'database' => env('DB_DATABASE'), - 'username' => env('DB_USERNAME'), - 'password' => env('DB_PASSWORD'), - 'options' => ['replicaSet' => 'replicaSetName'] -], -``` - -Eloquent --------- - -This package includes a MongoDB enabled Eloquent class that you can use to define models for corresponding collections. - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent {} -``` - -Note that we did not tell Eloquent which collection to use for the `User` model. Just like the original Eloquent, the lower-case, plural name of the class will be used as the table name unless another name is explicitly specified. You may specify a custom collection (alias for table) by defining a `collection` property on your model: - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent { - - protected $collection = 'users_collection'; - -} -``` - -**NOTE:** Eloquent will also assume that each collection has a primary key column named id. You may define a `primaryKey` property to override this convention. Likewise, you may define a `connection` property to override the name of the database connection that should be used when utilizing the model. - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class MyModel extends Eloquent { - - protected $connection = 'mongodb'; - -} -``` - -Everything else (should) work just like the original Eloquent model. Read more about the Eloquent on http://laravel.com/docs/eloquent - -### Optional: Alias - -You may also register an alias for the MongoDB model by adding the following to the alias array in `config/app.php`: - -```php -'Moloquent' => 'Jenssegers\Mongodb\Eloquent\Model', -``` - -This will allow you to use the registered alias like: - -```php -class MyModel extends Moloquent {} -``` - -Query Builder -------------- - -The database driver plugs right into the original query builder. When using mongodb connections, you will be able to build fluent queries to perform database operations. For your convenience, there is a `collection` alias for `table` as well as some additional mongodb specific operators/operations. - -```php -$users = DB::collection('users')->get(); - -$user = DB::collection('users')->where('name', 'John')->first(); -``` - -If you did not change your default database connection, you will need to specify it when querying. - -```php -$user = DB::connection('mongodb')->collection('users')->get(); -``` - -Read more about the query builder on http://laravel.com/docs/queries - -Schema ------- - -The database driver also has (limited) schema builder support. You can easily manipulate collections and set indexes: - -```php -Schema::create('users', function($collection) -{ - $collection->index('name'); - - $collection->unique('email'); -}); -``` - -Supported operations are: - - - create and drop - - collection - - hasCollection - - index and dropIndex (compound indexes supported as well) - - unique - - background, sparse, expire (MongoDB specific) - -All other (unsupported) operations are implemented as dummy pass-through methods, because MongoDB does not use a predefined schema. Read more about the schema builder on http://laravel.com/docs/schema - -Extensions ----------- - -### Auth - -If you want to use Laravel's native Auth functionality, register this included service provider: - -```php -'Jenssegers\Mongodb\Auth\PasswordResetServiceProvider', -``` - -This service provider will slightly modify the internal DatabaseReminderRepository to add support for MongoDB based password reminders. If you don't use password reminders, you don't have to register this service provider and everything else should work just fine. - -### Queues - -If you want to use MongoDB as your database backend, change the the driver in `config/queue.php`: - -```php -'connections' => [ - 'database' => [ - 'driver' => 'mongodb', - 'table' => 'jobs', - 'queue' => 'default', - 'expire' => 60, - ], -``` - -If you want to use MongoDB to handle failed jobs, change the database in `config/queue.php`: - -```php -'failed' => [ - 'database' => 'mongodb', - 'table' => 'failed_jobs', - ], -``` - -And add the service provider in `config/app.php`: - -```php -Jenssegers\Mongodb\MongodbQueueServiceProvider::class, -``` - -### Sentry - -If you want to use this library with [Sentry](https://cartalyst.com/manual/sentry), then check out https://github.com/jenssegers/Laravel-MongoDB-Sentry - -### Sessions - -The MongoDB session driver is available in a separate package, check out https://github.com/jenssegers/Laravel-MongoDB-Session - -Examples --------- - -### Basic Usage - -**Retrieving All Models** - -```php -$users = User::all(); -``` - -**Retrieving A Record By Primary Key** - -```php -$user = User::find('517c43667db388101e00000f'); -``` - -**Wheres** - -```php -$users = User::where('votes', '>', 100)->take(10)->get(); -``` - -**Or Statements** - -```php -$users = User::where('votes', '>', 100)->orWhere('name', 'John')->get(); -``` - -**And Statements** - -```php -$users = User::where('votes', '>', 100)->where('name', '=', 'John')->get(); -``` - -**Using Where In With An Array** - -```php -$users = User::whereIn('age', [16, 18, 20])->get(); -``` - -When using `whereNotIn` objects will be returned if the field is non existent. Combine with `whereNotNull('age')` to leave out those documents. - -**Using Where Between** - -```php -$users = User::whereBetween('votes', [1, 100])->get(); -``` - -**Where null** - -```php -$users = User::whereNull('updated_at')->get(); -``` - -**Order By** - -```php -$users = User::orderBy('name', 'desc')->get(); -``` - -**Offset & Limit** - -```php -$users = User::skip(10)->take(5)->get(); -``` - -**Distinct** - -Distinct requires a field for which to return the distinct values. - -```php -$users = User::distinct()->get(['name']); -// or -$users = User::distinct('name')->get(); -``` - -Distinct can be combined with **where**: - -```php -$users = User::where('active', true)->distinct('name')->get(); -``` - -**Advanced Wheres** - -```php -$users = User::where('name', '=', 'John')->orWhere(function($query) - { - $query->where('votes', '>', 100) - ->where('title', '<>', 'Admin'); - }) - ->get(); -``` - -**Group By** - -Selected columns that are not grouped will be aggregated with the $last function. - -```php -$users = Users::groupBy('title')->get(['title', 'name']); -``` - -**Aggregation** - -*Aggregations are only available for MongoDB versions greater than 2.2.* - -```php -$total = Order::count(); -$price = Order::max('price'); -$price = Order::min('price'); -$price = Order::avg('price'); -$total = Order::sum('price'); -``` - -Aggregations can be combined with **where**: - -```php -$sold = Orders::where('sold', true)->sum('price'); -``` - -**Like** - -```php -$user = Comment::where('body', 'like', '%spam%')->get(); -``` - -**Incrementing or decrementing a value of a column** - -Perform increments or decrements (default 1) on specified attributes: - -```php -User::where('name', 'John Doe')->increment('age'); -User::where('name', 'Jaques')->decrement('weight', 50); -``` - -The number of updated objects is returned: - -```php -$count = User->increment('age'); -``` - -You may also specify additional columns to update: - -```php -User::where('age', '29')->increment('age', 1, ['group' => 'thirty something']); -User::where('bmi', 30)->decrement('bmi', 1, ['category' => 'overweight']); -``` - -**Soft deleting** - -When soft deleting a model, it is not actually removed from your database. Instead, a deleted_at timestamp is set on the record. To enable soft deletes for a model, apply the SoftDeletingTrait to the model: - -```php -use Jenssegers\Mongodb\Eloquent\SoftDeletes; - -class User extends Eloquent { - - use SoftDeletes; - - protected $dates = ['deleted_at']; - -} -``` - -For more information check http://laravel.com/docs/eloquent#soft-deleting - -### MongoDB specific operators - -**Exists** - -Matches documents that have the specified field. - -```php -User::where('age', 'exists', true)->get(); -``` - -**All** - -Matches arrays that contain all elements specified in the query. - -```php -User::where('roles', 'all', ['moderator', 'author'])->get(); -``` - -**Size** - -Selects documents if the array field is a specified size. - -```php -User::where('tags', 'size', 3)->get(); -``` - -**Regex** - -Selects documents where values match a specified regular expression. - -```php -User::where('name', 'regex', new \MongoDB\BSON\Regex("/.*doe/i"))->get(); -``` - -**NOTE:** you can also use the Laravel regexp operations. These are a bit more flexible and will automatically convert your regular expression string to a MongoDB\BSON\Regex object. - -```php -User::where('name', 'regexp', '/.*doe/i'))->get(); -``` - -And the inverse: - -```php -User::where('name', 'not regexp', '/.*doe/i'))->get(); -``` - -**Type** - -Selects documents if a field is of the specified type. For more information check: http://docs.mongodb.org/manual/reference/operator/query/type/#op._S_type - -```php -User::where('age', 'type', 2)->get(); -``` - -**Mod** - -Performs a modulo operation on the value of a field and selects documents with a specified result. - -```php -User::where('age', 'mod', [10, 0])->get(); -``` - -**Where** - -Matches documents that satisfy a JavaScript expression. For more information check http://docs.mongodb.org/manual/reference/operator/query/where/#op._S_where - -### Inserts, updates and deletes - -Inserting, updating and deleting records works just like the original Eloquent. - -**Saving a new model** - -```php -$user = new User; -$user->name = 'John'; -$user->save(); -``` - -You may also use the create method to save a new model in a single line: - -```php -User::create(['name' => 'John']); -``` - -**Updating a model** - -To update a model, you may retrieve it, change an attribute, and use the save method. - -```php -$user = User::first(); -$user->email = 'john@foo.com'; -$user->save(); -``` - -*There is also support for upsert operations, check https://github.com/jenssegers/laravel-mongodb#mongodb-specific-operations* - -**Deleting a model** - -To delete a model, simply call the delete method on the instance: - -```php -$user = User::first(); -$user->delete(); -``` - -Or deleting a model by its key: - -```php -User::destroy('517c43667db388101e00000f'); -``` - -For more information about model manipulation, check http://laravel.com/docs/eloquent#insert-update-delete - -### Dates - -Eloquent allows you to work with Carbon/DateTime objects instead of MongoDate objects. Internally, these dates will be converted to MongoDate objects when saved to the database. If you wish to use this functionality on non-default date fields you will need to manually specify them as described here: http://laravel.com/docs/eloquent#date-mutators - -Example: - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent { - - protected $dates = ['birthday']; - -} -``` - -Which allows you to execute queries like: - -```php -$users = User::where('birthday', '>', new DateTime('-18 years'))->get(); -``` - -### Relations - -Supported relations are: - - - hasOne - - hasMany - - belongsTo - - belongsToMany - - embedsOne - - embedsMany - -Example: - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent { - - public function items() - { - return $this->hasMany('Item'); - } - -} -``` - -And the inverse relation: - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class Item extends Eloquent { - - public function user() - { - return $this->belongsTo('User'); - } - -} -``` - -The belongsToMany relation will not use a pivot "table", but will push id's to a __related_ids__ attribute instead. This makes the second parameter for the belongsToMany method useless. If you want to define custom keys for your relation, set it to `null`: - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent { - - public function groups() - { - return $this->belongsToMany('Group', null, 'user_ids', 'group_ids'); - } - -} -``` - - -Other relations are not yet supported, but may be added in the future. Read more about these relations on http://laravel.com/docs/eloquent#relationships - -### EmbedsMany Relations - -If you want to embed models, rather than referencing them, you can use the `embedsMany` relation. This relation is similar to the `hasMany` relation, but embeds the models inside the parent object. - -**REMEMBER**: these relations return Eloquent collections, they don't return query builder objects! - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class User extends Eloquent { - - public function books() - { - return $this->embedsMany('Book'); - } - -} -``` - -You access the embedded models through the dynamic property: - -```php -$books = User::first()->books; -``` - -The inverse relation is auto*magically* available, you don't need to define this reverse relation. - -```php -$user = $book->user; -``` - -Inserting and updating embedded models works similar to the `hasMany` relation: - -```php -$book = new Book(['title' => 'A Game of Thrones']); - -$user = User::first(); - -$book = $user->books()->save($book); -// or -$book = $user->books()->create(['title' => 'A Game of Thrones']) -``` - -You can update embedded models using their `save` method (available since release 2.0.0): - -```php -$book = $user->books()->first(); - -$book->title = 'A Game of Thrones'; - -$book->save(); -``` - -You can remove an embedded model by using the `destroy` method on the relation, or the `delete` method on the model (available since release 2.0.0): - -```php -$book = $user->books()->first(); - -$book->delete(); -// or -$user->books()->destroy($book); -``` - -If you want to add or remove an embedded model, without touching the database, you can use the `associate` and `dissociate` methods. To eventually write the changes to the database, save the parent object: - -```php -$user->books()->associate($book); - -$user->save(); -``` - -Like other relations, embedsMany assumes the local key of the relationship based on the model name. You can override the default local key by passing a second argument to the embedsMany method: - -```php -return $this->embedsMany('Book', 'local_key'); -``` - -Embedded relations will return a Collection of embedded items instead of a query builder. Check out the available operations here: https://laravel.com/docs/master/collections - -### EmbedsOne Relations - -The embedsOne relation is similar to the EmbedsMany relation, but only embeds a single model. - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class Book extends Eloquent { - - public function author() - { - return $this->embedsOne('Author'); - } - -} -``` - -You access the embedded models through the dynamic property: - -```php -$author = Book::first()->author; -``` - -Inserting and updating embedded models works similar to the `hasOne` relation: - -```php -$author = new Author(['name' => 'John Doe']); - -$book = Books::first(); - -$author = $book->author()->save($author); -// or -$author = $book->author()->create(['name' => 'John Doe']); -``` - -You can update the embedded model using the `save` method (available since release 2.0.0): - -```php -$author = $book->author; - -$author->name = 'Jane Doe'; -$author->save(); -``` - -You can replace the embedded model with a new model like this: - -```php -$newAuthor = new Author(['name' => 'Jane Doe']); -$book->author()->save($newAuthor); -``` - -### MySQL Relations - -If you're using a hybrid MongoDB and SQL setup, you're in luck! The model will automatically return a MongoDB- or SQL-relation based on the type of the related model. Of course, if you want this functionality to work both ways, your SQL-models will need use the `Jenssegers\Mongodb\Eloquent\HybridRelations` trait. Note that this functionality only works for hasOne, hasMany and belongsTo relations. - -Example SQL-based User model: - -```php -use Jenssegers\Mongodb\Eloquent\HybridRelations; - -class User extends Eloquent { - - use HybridRelations; - - protected $connection = 'mysql'; - - public function messages() - { - return $this->hasMany('Message'); - } - -} -``` - -And the Mongodb-based Message model: - -```php -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; - -class Message extends Eloquent { - - protected $connection = 'mongodb'; - - public function user() - { - return $this->belongsTo('User'); - } - -} -``` - -### Raw Expressions - -These expressions will be injected directly into the query. - -```php -User::whereRaw(['age' => array('$gt' => 30, '$lt' => 40)])->get(); -``` - -You can also perform raw expressions on the internal MongoCollection object. If this is executed on the model class, it will return a collection of models. If this is executed on the query builder, it will return the original response. - -```php -// Returns a collection of User models. -$models = User::raw(function($collection) -{ - return $collection->find(); -}); - -// Returns the original MongoCursor. -$cursor = DB::collection('users')->raw(function($collection) -{ - return $collection->find(); -}); -``` - -Optional: if you don't pass a closure to the raw method, the internal MongoCollection object will be accessible: - -```php -$model = User::raw()->findOne(['age' => array('$lt' => 18])); -``` - -The internal MongoClient and MongoDB objects can be accessed like this: - -```php -$client = DB::getMongoClient(); -$db = DB::getMongoDB(); -``` - -### MongoDB specific operations - -**Cursor timeout** - -To prevent MongoCursorTimeout exceptions, you can manually set a timeout value that will be applied to the cursor: - -```php -DB::collection('users')->timeout(-1)->get(); -``` - -**Upsert** - -Update or insert a document. Additional options for the update method are passed directly to the native update method. - -```php -DB::collection('users')->where('name', 'John') - ->update($data, ['upsert' => true]); -``` - -**Projections** - -You can apply projections to your queries using the `project` method. - -```php -DB::collection('items')->project(['tags' => array('$slice' => 1]))->get(); -``` - -**Projections with Pagination** - -```php -$limit = 25; -$projections = ['id', 'name']; -DB::collection('items')->paginate($limit, $projections); -``` - - -**Push** - -Add an items to an array. - -```php -DB::collection('users')->where('name', 'John')->push('items', 'boots'); -DB::collection('users')->where('name', 'John')->push('messages', ['from' => 'Jane Doe', 'message' => 'Hi John']); -``` - -If you don't want duplicate items, set the third parameter to `true`: - -```php -DB::collection('users')->where('name', 'John')->push('items', 'boots', true); -``` - -**Pull** - -Remove an item from an array. - -```php -DB::collection('users')->where('name', 'John')->pull('items', 'boots'); -DB::collection('users')->where('name', 'John')->pull('messages', ['from' => 'Jane Doe', 'message' => 'Hi John']); -``` - -**Unset** - -Remove one or more fields from a document. - -```php -DB::collection('users')->where('name', 'John')->unset('note'); -``` - -You can also perform an unset on a model. - -```php -$user = User::where('name', 'John')->first(); -$user->unset('note'); -``` - -### Query Caching - -You may easily cache the results of a query using the remember method: - -```php -$users = User::remember(10)->get(); -``` - -*From: http://laravel.com/docs/queries#caching-queries* - -### Query Logging - -By default, Laravel keeps a log in memory of all queries that have been run for the current request. However, in some cases, such as when inserting a large number of rows, this can cause the application to use excess memory. To disable the log, you may use the `disableQueryLog` method: - -```php -DB::connection()->disableQueryLog(); -``` - -*From: http://laravel.com/docs/database#query-logging* +The base repo WAS and IS being developed and maintained by [Jenssegers](https://github.com/jenssegers/laravel-mongodb). +Our goal is to provide higher quality support/bugfixes with fresh and additional features. diff --git a/composer.json b/composer.json index c98a67dff..ced4857d6 100644 --- a/composer.json +++ b/composer.json @@ -1,44 +1,51 @@ { - "name": "jenssegers/mongodb", - "description": "A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)", - "keywords": ["laravel","eloquent","mongodb","mongo","database","model","moloquent"], - "homepage": "https://github.com/jenssegers/laravel-mongodb", - "authors": [ - { - "name": "Jens Segers", - "homepage": "https://jenssegers.com" - } - ], - "license" : "MIT", - "require": { - "illuminate/support": "^5.1", - "illuminate/container": "^5.1", - "illuminate/database": "^5.1", - "illuminate/events": "^5.1", - "mongodb/mongodb": "^1.0.0" - + "name": "moloquent/moloquent", + "description": "A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)", + "keywords": [ + "laravel", + "eloquent", + "mongodb", + "mongo", + "database", + "model", + "moloquent" + ], + "homepage": "https://github.com/moloquent/moloquent", + "authors": [ + { + "name": "Moloquent", + "homepage": "https://moloquent.github.io" }, - "require-dev": { - "phpunit/phpunit": "^4.0|^5.0", - "orchestra/testbench": "^3.1", - "mockery/mockery": "^0.9", - "satooshi/php-coveralls": "^0.6" - }, - "autoload": { - "psr-0": { - "Jenssegers\\Mongodb": "src/", - "Jenssegers\\Eloquent": "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" + { + "name": "Jens Segers", + "homepage": "https://jenssegers.com" + } + ], + "license": "MIT", + "require": { + "illuminate/support": "^5.1", + "illuminate/container": "^5.1", + "illuminate/database": "^5.1", + "illuminate/events": "^5.1", + "mongodb/mongodb": "^1.0.0" + }, + "require-dev": { + "laravel/passport": "^1.0", + "phpunit/phpunit": "^4.0|^5.0", + "orchestra/testbench": "^3.1", + "mockery/mockery": "^0.9", + "satooshi/php-coveralls": "^0.6" + }, + "autoload": { + "psr-4" :{ + "Moloquent\\": "src/" } + }, + "autoload-dev": { + "classmap": [ + "tests/TestCase.php", + "tests/models", + "tests/seeds" + ] + } } diff --git a/moloquent.iml b/moloquent.iml new file mode 100644 index 000000000..13b229f58 --- /dev/null +++ b/moloquent.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 796bd5b38..14c440728 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" syntaxCheck="false" verbose="true" > @@ -36,6 +36,7 @@ tests/RelationsTest.php tests/EmbeddedRelationsTest.php + tests/RelationsWithMongoIdTest.php tests/RelationsTest.php @@ -44,5 +45,15 @@ tests/ValidationTest.php + + tests/PassportTest.php + + + + + src + + + diff --git a/src/Auth/DatabaseTokenRepository.php b/src/Auth/DatabaseTokenRepository.php new file mode 100644 index 000000000..46707d56a --- /dev/null +++ b/src/Auth/DatabaseTokenRepository.php @@ -0,0 +1,36 @@ + $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon]; + } + + /** + * Determine if the token has expired. + * + * @param array $createdAt + * + * @return bool + */ + protected function tokenExpired($createdAt) + { + return Carbon::parse($createdAt['date'])->addSeconds($this->expires)->isPast(); + } +} diff --git a/src/Jenssegers/Mongodb/Auth/PasswordBrokerManager.php b/src/Auth/PasswordBrokerManager.php similarity index 85% rename from src/Jenssegers/Mongodb/Auth/PasswordBrokerManager.php rename to src/Auth/PasswordBrokerManager.php index fcb9e9415..82d3b0349 100644 --- a/src/Jenssegers/Mongodb/Auth/PasswordBrokerManager.php +++ b/src/Auth/PasswordBrokerManager.php @@ -1,4 +1,6 @@ -app['db']->connection(), + $this->app['hash'], $config['table'], $this->app['config']['app.key'], $config['expire'] diff --git a/src/Jenssegers/Mongodb/Auth/PasswordResetServiceProvider.php b/src/Auth/PasswordResetServiceProvider.php similarity index 97% rename from src/Jenssegers/Mongodb/Auth/PasswordResetServiceProvider.php rename to src/Auth/PasswordResetServiceProvider.php index 0bd3fdae1..a2965a37f 100644 --- a/src/Jenssegers/Mongodb/Auth/PasswordResetServiceProvider.php +++ b/src/Auth/PasswordResetServiceProvider.php @@ -1,4 +1,6 @@ -collection->getCollectionName() . '.' . $method . '(' . implode(',', $query) . ')'; + $queryString = $this->collection->getCollectionName().'.'.$method.'('.implode(',', $query).')'; $this->connection->logQuery($queryString, [], $time); } diff --git a/src/Jenssegers/Mongodb/Connection.php b/src/Connection.php similarity index 87% rename from src/Jenssegers/Mongodb/Connection.php rename to src/Connection.php index 23e6d49ba..b8e160773 100644 --- a/src/Jenssegers/Mongodb/Connection.php +++ b/src/Connection.php @@ -1,6 +1,9 @@ -schemaGrammar = new Grammar(); + $this->config = $config; // Build the connection string @@ -49,13 +54,14 @@ public function __construct(array $config) */ protected function getDefaultPostProcessor() { - return new Query\Processor; + return new Query\Processor(); } /** * Begin a fluent query against a database collection. * - * @param string $collection + * @param string $collection + * * @return Query\Builder */ public function collection($collection) @@ -70,7 +76,8 @@ public function collection($collection) /** * Begin a fluent query against a database collection. * - * @param string $table + * @param string $table + * * @return Query\Builder */ public function table($table) @@ -81,7 +88,8 @@ public function table($table) /** * Get a MongoDB collection. * - * @param string $name + * @param string $name + * * @return Collection */ public function getCollection($name) @@ -122,10 +130,11 @@ public function getMongoClient() /** * Create a new MongoDB connection. * - * @param string $dsn - * @param array $config - * @param array $options - * @return MongoDB + * @param string $dsn + * @param array $config + * @param array $options + * + * @return \MongoDB\Client */ protected function createConnection($dsn, array $config, array $options) { @@ -158,7 +167,8 @@ public function disconnect() /** * Create a DSN string from a configuration. * - * @param array $config + * @param array $config + * * @return string */ protected function getDsn(array $config) @@ -166,10 +176,11 @@ protected function getDsn(array $config) // First we will create the basic DSN setup as well as the port if it is in // in the configuration options. This will give us the basic DSN we will // need to establish the MongoDB and return them back for use. + $host = $database = null; extract($config); // Check if the user passed a complete dsn to the configuration. - if (! empty($dsn)) { + if (!empty($dsn)) { return $dsn; } @@ -183,13 +194,14 @@ protected function getDsn(array $config) } } - return "mongodb://" . implode(',', $hosts) . "/{$database}"; + return 'mongodb://'.implode(',', $hosts)."/{$database}"; } /** * Get the elapsed time since a given starting point. * - * @param int $start + * @param int $start + * * @return float */ public function getElapsedTime($start) @@ -210,8 +222,9 @@ public function getDriverName() /** * Dynamically pass methods to the connection. * - * @param string $method - * @param array $parameters + * @param string $method + * @param array $parameters + * * @return mixed */ public function __call($method, $parameters) diff --git a/src/Jenssegers/Mongodb/Eloquent/Builder.php b/src/Eloquent/Builder.php similarity index 82% rename from src/Jenssegers/Mongodb/Eloquent/Builder.php rename to src/Eloquent/Builder.php index 30feb67e3..c01997ac5 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -1,4 +1,6 @@ -pluck($relation->getHasCompareKey()); - $relationCount = array_count_values(array_map(function ($id) { - return (string) $id; // Convert Back ObjectIds to Strings - }, is_array($relations) ? $relations : $relations->toArray())); + $relations = array_map(function ($id) { + return (string) $id; + }, is_array($relations) ? $relations : $relations->toArray()); + $relationCount = array_count_values($relations); // Remove unwanted related objects based on the operator and count. $relationCount = array_filter($relationCount, function ($counted) use ($count, $operator) { @@ -202,7 +211,16 @@ protected function addHasWhere(EloquentBuilder $hasQuery, Relation $relation, $o } // All related ids. - $relatedIds = array_keys($relationCount); + $relatedIds = array_map(function ($id) use ($relation) { + $relationModel = $relation->getRelated(); + $relationModel->setRelationCast($relation->getHasCompareKey()); + if ($relationModel->useMongoId() + && $relationModel->hasCast($relation->getHasCompareKey(), null, 'set')) { + $id = $relationModel->castAttribute($relation->getHasCompareKey(), $id, 'set'); + } + + return $id; + }, array_keys($relationCount)); // Add whereIn to the query. return $this->whereIn($this->model->getKeyName(), $relatedIds, $boolean, $not); @@ -211,7 +229,8 @@ protected function addHasWhere(EloquentBuilder $hasQuery, Relation $relation, $o /** * Create a raw database expression. * - * @param closure $expression + * @param closure $expression + * * @return mixed */ public function raw($expression = null) @@ -222,12 +241,14 @@ public function raw($expression = null) // 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); } diff --git a/src/Jenssegers/Mongodb/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php similarity index 72% rename from src/Jenssegers/Mongodb/Eloquent/HybridRelations.php rename to src/Eloquent/HybridRelations.php index 66b425224..29c8ae415 100644 --- a/src/Jenssegers/Mongodb/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -1,34 +1,37 @@ -getForeignKey(); - $instance = new $related; + $instance = new $related(); $localKey = $localKey ?: $this->getKeyName(); @@ -38,21 +41,22 @@ public function hasOne($related, $foreignKey = null, $localKey = null) /** * Define a polymorphic one-to-one relationship. * - * @param string $related - * @param string $name - * @param string $type - * @param string $id - * @param string $localKey + * @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, 'Jenssegers\Mongodb\Eloquent\Model')) { + if (!is_subclass_of($related, 'Moloquent\Eloquent\Model')) { return parent::morphOne($related, $name, $type, $id, $localKey); } - $instance = new $related; + $instance = new $related(); list($type, $id) = $this->getMorphs($name, $type, $id); @@ -66,21 +70,22 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey = /** * Define a one-to-many relationship. * - * @param string $related - * @param string $foreignKey - * @param string $localKey + * @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, 'Jenssegers\Mongodb\Eloquent\Model')) { + if (!is_subclass_of($related, 'Moloquent\Eloquent\Model')) { return parent::hasMany($related, $foreignKey, $localKey); } $foreignKey = $foreignKey ?: $this->getForeignKey(); - $instance = new $related; + $instance = new $related(); $localKey = $localKey ?: $this->getKeyName(); @@ -90,21 +95,22 @@ public function hasMany($related, $foreignKey = null, $localKey = null) /** * Define a polymorphic one-to-many relationship. * - * @param string $related - * @param string $name - * @param string $type - * @param string $id - * @param string $localKey + * @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, 'Jenssegers\Mongodb\Eloquent\Model')) { + if (!is_subclass_of($related, 'Moloquent\Eloquent\Model')) { return parent::morphMany($related, $name, $type, $id, $localKey); } - $instance = new $related; + $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 @@ -121,10 +127,11 @@ public function morphMany($related, $name, $type = null, $id = null, $localKey = /** * Define an inverse one-to-one or many relationship. * - * @param string $related - * @param string $foreignKey - * @param string $otherKey - * @param string $relation + * @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) @@ -139,7 +146,7 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat } // Check if it is a relation with an original model. - if (! is_subclass_of($related, 'Jenssegers\Mongodb\Eloquent\Model')) { + if (!is_subclass_of($related, 'Moloquent\Eloquent\Model')) { return parent::belongsTo($related, $foreignKey, $otherKey, $relation); } @@ -147,10 +154,10 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat // foreign key name by using the name of the relationship function, which // when combined with an "_id" should conventionally match the columns. if (is_null($foreignKey)) { - $foreignKey = Str::snake($relation) . '_id'; + $foreignKey = Str::snake($relation).'_id'; } - $instance = new $related; + $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 @@ -165,9 +172,10 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat /** * Define a polymorphic, inverse one-to-one or many relationship. * - * @param string $name - * @param string $type - * @param string $id + * @param string $name + * @param string $type + * @param string $id + * * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function morphTo($name = null, $type = null, $id = null) @@ -198,7 +206,7 @@ public function morphTo($name = null, $type = null, $id = null) else { $class = $this->getActualClassNameForMorph($class); - $instance = new $class; + $instance = new $class(); return new MorphTo( $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name @@ -210,34 +218,45 @@ public function morphTo($name = null, $type = null, $id = null) * Define a many-to-many relationship. * * @param string $related - * @param string $collection - * @param string $foreignKey - * @param string $otherKey + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @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, $relation = null) + public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = 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 (is_null($relation)) { - $relation = $this->getBelongsToManyCaller(); + + // Laravel >= 5.4 + if (method_exists($this, 'guessBelongsToManyRelation')) { + $relation = $this->guessBelongsToManyRelation(); + } else { + $relation = $this->getBelongsToManyCaller(); + } } // Check if it is a relation with an original model. - if (! is_subclass_of($related, 'Jenssegers\Mongodb\Eloquent\Model')) { - return parent::belongsToMany($related, $collection, $foreignKey, $otherKey, $relation); + if (!is_subclass_of($related, 'Moloquent\Eloquent\Model')) { + return parent::belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, + $parentKey = null, $relatedKey = null, $relation = null); } // 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'; + $foreignKey = $foreignKey ?: $this->getForeignKey().'s'; - $instance = new $related; + $instance = new $related(); - $otherKey = $otherKey ?: $instance->getForeignKey() . 's'; + $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 @@ -251,6 +270,7 @@ public function belongsToMany($related, $collection = null, $foreignKey = null, // appropriate query constraint and entirely manages the hydrations. $query = $instance->newQuery(); - return new BelongsToMany($query, $this, $collection, $foreignKey, $otherKey, $relation); + return new BelongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, + $parentKey = null, $relatedKey = null, $relation = null); } } diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Eloquent/Model.php similarity index 66% rename from src/Jenssegers/Mongodb/Eloquent/Model.php rename to src/Eloquent/Model.php index 797f1907f..9f2794a04 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Eloquent/Model.php @@ -1,13 +1,16 @@ -attributes)) { + if (!$value and array_key_exists('_id', $this->attributes)) { $value = $this->attributes['_id']; } @@ -72,11 +83,12 @@ public function getQualifiedKeyName() /** * Define an embedded one-to-many relationship. * - * @param string $related - * @param string $localKey - * @param string $foreignKey - * @param string $relation - * @return \Jenssegers\Mongodb\Relations\EmbedsMany + * @param string $related + * @param string $localKey + * @param string $foreignKey + * @param string $relation + * + * @return \Moloquent\Relations\EmbedsMany */ protected function embedsMany($related, $localKey = null, $foreignKey = null, $relation = null) { @@ -99,7 +111,7 @@ protected function embedsMany($related, $localKey = null, $foreignKey = null, $r $query = $this->newQuery(); - $instance = new $related; + $instance = new $related(); return new EmbedsMany($query, $this, $instance, $localKey, $foreignKey, $relation); } @@ -107,11 +119,12 @@ protected function embedsMany($related, $localKey = null, $foreignKey = null, $r /** * Define an embedded one-to-many relationship. * - * @param string $related - * @param string $localKey - * @param string $foreignKey - * @param string $relation - * @return \Jenssegers\Mongodb\Relations\EmbedsOne + * @param string $related + * @param string $localKey + * @param string $foreignKey + * @param string $relation + * + * @return \Moloquent\Relations\EmbedsOne */ protected function embedsOne($related, $localKey = null, $foreignKey = null, $relation = null) { @@ -134,7 +147,7 @@ protected function embedsOne($related, $localKey = null, $foreignKey = null, $re $query = $this->newQuery(); - $instance = new $related; + $instance = new $related(); return new EmbedsOne($query, $this, $instance, $localKey, $foreignKey, $relation); } @@ -142,7 +155,8 @@ protected function embedsOne($related, $localKey = null, $foreignKey = null, $re /** * Convert a DateTime to a storable UTCDateTime object. * - * @param DateTime|int $value + * @param DateTime|int $value + * * @return UTCDateTime */ public function fromDateTime($value) @@ -153,7 +167,7 @@ public function fromDateTime($value) } // Let Eloquent convert the value to a DateTime instance. - if (! $value instanceof DateTime) { + if (!$value instanceof DateTime) { $value = parent::asDateTime($value); } @@ -163,7 +177,8 @@ public function fromDateTime($value) /** * Return a timestamp as DateTime object. * - * @param mixed $value + * @param mixed $value + * * @return DateTime */ protected function asDateTime($value) @@ -209,7 +224,8 @@ public function getTable() /** * Get an attribute from the model. * - * @param string $key + * @param string $key + * * @return mixed */ public function getAttribute($key) @@ -219,6 +235,11 @@ public function getAttribute($key) return $this->getAttributeValue($key); } + // Eloquent behaviour would prioritise the mutator, so Check for hasGetMutator first + if ($this->hasGetMutator($key)) { + return $this->getAttributeValue($key); + } + $camelKey = camel_case($key); // If the "attribute" exists as a method on the model, it may be an @@ -228,7 +249,7 @@ public function getAttribute($key) $method = new ReflectionMethod(get_called_class(), $camelKey); // Ensure the method is not static to avoid conflicting with Eloquent methods. - if (! $method->isStatic()) { + if (!$method->isStatic()) { $relations = $this->$camelKey(); // This attribute matches an embedsOne or embedsMany relation so we need @@ -242,7 +263,7 @@ public function getAttribute($key) } // Get the relation results. - return $this->getRelationshipFromMethod($key, $camelKey); + return $this->getRelationshipFromMethod($camelKey); } if ($relations instanceof Relation) { @@ -254,7 +275,7 @@ public function getAttribute($key) } // Get the relation results. - return $this->getRelationshipFromMethod($key, $camelKey); + return $this->getRelationshipFromMethod($camelKey); } } } @@ -265,7 +286,8 @@ public function getAttribute($key) /** * Get an attribute from the $attributes array. * - * @param string $key + * @param string $key + * * @return mixed */ protected function getAttributeFromArray($key) @@ -285,20 +307,18 @@ protected function getAttributeFromArray($key) /** * Set a given attribute on the model. * - * @param string $key - * @param mixed $value + * @param string $key + * @param mixed $value */ public function setAttribute($key, $value) { - // Convert _id to ObjectID. - if ($key == '_id' and is_string($value)) { - $builder = $this->newBaseQueryBuilder(); - - $value = $builder->convertKey($value); - } + // cast data for saving. + // set _id to converted into ObjectID if its possible. + $this->setRelationCast($key); + $value = $this->castAttribute($key, $value, 'set'); // Support keys in dot notation. - elseif (str_contains($key, '.')) { + if (str_contains($key, '.')) { if (in_array($key, $this->getDates()) && $value) { $value = $this->fromDateTime($value); } @@ -340,20 +360,11 @@ public function attributesToArray() return $attributes; } - /** - * Get the casts array. - * - * @return array - */ - public function getCasts() - { - return $this->casts; - } - /** * Determine if the new and old values for a given key are numerically equivalent. * - * @param string $key + * @param string $key + * * @return bool */ protected function originalIsNumericallyEquivalent($key) @@ -375,12 +386,13 @@ protected function originalIsNumericallyEquivalent($key) /** * Remove one or more fields. * - * @param mixed $columns + * @param mixed $columns + * * @return int */ public function drop($columns) { - if (! is_array($columns)) { + if (!is_array($columns)) { $columns = [$columns]; } @@ -410,7 +422,7 @@ public function push() } // Do batch push by default. - if (! is_array($values)) { + if (!is_array($values)) { $values = [$values]; } @@ -427,14 +439,15 @@ public function push() /** * Remove one or more values from an array. * - * @param string $column - * @param mixed $values + * @param string $column + * @param mixed $values + * * @return mixed */ public function pull($column, $values) { // Do batch pull by default. - if (! is_array($values)) { + if (!is_array($values)) { $values = [$values]; } @@ -448,9 +461,9 @@ public function 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 + * @param string $column + * @param array $values + * @param bool $unique */ protected function pushAttributeValues($column, array $values, $unique = false) { @@ -473,8 +486,8 @@ protected function pushAttributeValues($column, array $values, $unique = false) /** * Remove one or more values to the underlying attribute value and sync with original. * - * @param string $column - * @param array $values + * @param string $column + * @param array $values */ protected function pullAttributeValues($column, array $values) { @@ -496,7 +509,7 @@ protected function pullAttributeValues($column, array $values) /** * Set the parent relation. * - * @param \Illuminate\Database\Eloquent\Relations\Relation $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation $relation */ public function setParentRelation(Relation $relation) { @@ -512,12 +525,23 @@ public function getParentRelation() { return $this->parentRelation; } + + /** + * Get the default foreign key name for the model. + * + * @return string + */ + public function getForeignKey() + { + return Str::snake(class_basename($this)) . '_' . trim($this->primaryKey, '_'); + } /** * Create a new Eloquent query builder for the model. * - * @param \Jenssegers\Mongodb\Query\Builder $query - * @return \Jenssegers\Mongodb\Eloquent\Builder|static + * @param \Moloquent\Query\Builder $query + * + * @return \Moloquent\Eloquent\Builder|static */ public function newEloquentBuilder($query) { @@ -536,11 +560,24 @@ protected function newBaseQueryBuilder() return new QueryBuilder($connection, $connection->getPostProcessor()); } + /** + * We just return original key here in order to support keys in dot-notation. + * + * @param string $key + * + * @return string + */ + protected function removeTableFromKey($key) + { + return $key; + } + /** * Handle dynamic method calls into the method. * - * @param string $method - * @param array $parameters + * @param string $method + * @param array $parameters + * * @return mixed */ public function __call($method, $parameters) @@ -552,4 +589,182 @@ public function __call($method, $parameters) return parent::__call($method, $parameters); } + + /** + * setter for casts. + * + * @param $cast + * @param string $castType + * + * @return void + */ + public function setCasts($cast, $castType = 'get') + { + if ($castType == 'set') { + $this->saveCasts = $cast; + + return; + } + $this->casts = $cast; + } + + /** + * Get the casts array. + * + * @param string $castType + * + * @return array + */ + public function getCasts($castType = 'get') + { + if ($castType == 'set') { + return $this->saveCasts; + } + + return $this->casts; + } + + /** + * Get the type of save cast for a model attribute. + * + * @param string $key + * @param string $castType + * + * @return string + */ + protected function getCastType($key, $castType = 'get') + { + return trim(strtolower($this->getCasts($castType)[$key])); + } + + /** + * Determine whether an attribute should be cast to a native type. + * + * @param string $key + * @param array|string|null $types + * @param string $castType + * + * @return bool + */ + public function hasCast($key, $types = null, $castType = 'get') + { + if (array_key_exists($key, $this->getCasts($castType))) { + return $types ? in_array($this->getCastType($key, $castType), (array) $types, true) : true; + } + + return false; + } + + /** + * check if driver uses mongoId in relations. + * + * @return bool + */ + public function useMongoId() + { + if (function_exists('config')) { + return (bool) config('database.connections.mongodb.use_mongo_id', false); + } + + $connection = $this->getConnection(); + return $connection->getConfig('use_mongo_id'); + } + + /** + * Cast an attribute to a mongo type. + * + * @param string $key + * @param mixed $value + * @param string $castType + * + * @return mixed + */ + public function castAttribute($key, $value, $castType = 'get') + { + if (is_null($value)) { + return; + } + + if (!$this->hasCast($key, null, $castType)) { + return $value; + } + + switch ($this->getCastType($key, $castType)) { + case 'int': + case 'integer': + return (int) $value; + case 'real': + case 'float': + case 'double': + return (float) $value; + case 'string': + return (string) $value; + case 'bool': + case 'boolean': + return (bool) $value; + case 'date': + case 'utcdatetime': + case 'mongodate': + return $this->asMongoDate($value); + case 'mongoid': + case 'objectid': + return $this->asMongoID($value); + case 'timestamp': + return $this->asTimeStamp($value); + default: + return $value; + } + } + + /** + * convert value into ObjectID if its possible. + * + * @param $value + * + * @return UTCDatetime + */ + protected function asMongoID($value) + { + if (is_string($value) and strlen($value) === 24 and ctype_xdigit($value)) { + return new ObjectID($value); + } + + return $value; + } + + /** + * convert value into UTCDatetime. + * + * @param $value + * + * @return UTCDatetime + */ + protected function asMongoDate($value) + { + if ($value instanceof UTCDatetime) { + return $value; + } + + return new UTCDatetime($this->asTimeStamp($value) * 1000); + } + + /** + * add relation that ended with _id into objectId + * if config allow it. + * + * @param $key + */ + public function setRelationCast($key) + { + if ($key == '_id') { + $this->saveCasts['_id'] = 'ObjectID'; + + return; + } + if ($this->useMongoId()) { + if (ends_with($key, '_id')) { + $this->saveCasts[$key] = 'ObjectID'; + } + } + } } diff --git a/src/Jenssegers/Mongodb/Eloquent/SoftDeletes.php b/src/Eloquent/SoftDeletes.php similarity index 86% rename from src/Jenssegers/Mongodb/Eloquent/SoftDeletes.php rename to src/Eloquent/SoftDeletes.php index 165582db7..028ae3750 100644 --- a/src/Jenssegers/Mongodb/Eloquent/SoftDeletes.php +++ b/src/Eloquent/SoftDeletes.php @@ -1,4 +1,6 @@ - $email, 'token' => $token, 'created_at' => new UTCDateTime(round(microtime(true) * 1000))]; - } - - /** - * Determine if the token has expired. - * - * @param array $token - * @return bool - */ - protected function tokenExpired($token) - { - // Convert UTCDateTime to a date string. - if ($token['created_at'] instanceof UTCDateTime) { - $date = $token['created_at']->toDateTime(); - $date->setTimezone(new DateTimeZone(date_default_timezone_get())); - $token['created_at'] = $date->format('Y-m-d H:i:s'); - } elseif (is_array($token['created_at']) and isset($token['created_at']['date'])) { - $date = new DateTime($token['created_at']['date'], new DateTimeZone(isset($token['created_at']['timezone']) ? $token['created_at']['timezone'] : 'UTC')); - $date->setTimezone(new DateTimeZone(date_default_timezone_get())); - $token['created_at'] = $date->format('Y-m-d H:i:s'); - } - - return parent::tokenExpired($token); - } -} diff --git a/src/Jenssegers/Mongodb/Relations/BelongsTo.php b/src/Jenssegers/Mongodb/Relations/BelongsTo.php deleted file mode 100644 index b73c6b05e..000000000 --- a/src/Jenssegers/Mongodb/Relations/BelongsTo.php +++ /dev/null @@ -1,32 +0,0 @@ -query->where($this->otherKey, '=', $this->parent->{$this->foreignKey}); - } - } - - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - */ - 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->otherKey; - - $this->query->whereIn($key, $this->getEagerModelKeys($models)); - } -} diff --git a/src/Jenssegers/Mongodb/MongodbQueueServiceProvider.php b/src/MongodbQueueServiceProvider.php similarity index 87% rename from src/Jenssegers/Mongodb/MongodbQueueServiceProvider.php rename to src/MongodbQueueServiceProvider.php index 8321992e4..56c09cc4e 100644 --- a/src/Jenssegers/Mongodb/MongodbQueueServiceProvider.php +++ b/src/MongodbQueueServiceProvider.php @@ -1,7 +1,9 @@ -app['db']); Model::setEventDispatcher($this->app['events']); + + $this->loadSupportClasses(); } /** @@ -35,4 +39,11 @@ public function register() }); }); } + + private function loadSupportClasses() + { + if (!class_exists('Jenssegers\Mongodb\Eloquent\Model')) { + require_once __DIR__.'/SupportClasses/Jenssegers/Model.php'; + } + } } diff --git a/src/Passport/AuthCode.php b/src/Passport/AuthCode.php new file mode 100644 index 000000000..f81e81133 --- /dev/null +++ b/src/Passport/AuthCode.php @@ -0,0 +1,50 @@ + 'bool', + ]; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = [ + 'expires_at', + ]; + + /** + * Get the client that owns the authentication code. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function client() + { + return $this->hasMany(Client::class); + } +} diff --git a/src/Passport/Client.php b/src/Passport/Client.php new file mode 100644 index 000000000..30ec24a9e --- /dev/null +++ b/src/Passport/Client.php @@ -0,0 +1,72 @@ + 'bool', + 'password_client' => 'bool', + 'revoked' => 'bool', + ]; + + /** + * Get all of the authentication codes for the client. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function authCodes() + { + return $this->hasMany(AuthCode::class); + } + + /** + * Get all of the tokens that belong to the client. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function tokens() + { + return $this->hasMany(Token::class); + } + + /** + * Determine if the client is a "first party" client. + * + * @return bool + */ + public function firstParty() + { + return $this->personal_access_client || $this->password_client; + } +} diff --git a/src/Passport/PassportServiceProvider.php b/src/Passport/PassportServiceProvider.php new file mode 100644 index 000000000..0f4590742 --- /dev/null +++ b/src/Passport/PassportServiceProvider.php @@ -0,0 +1,21 @@ +alias('Laravel\Passport\AuthCode', AuthCode::class); + $loader->alias('Laravel\Passport\Client', Client::class); + $loader->alias('Laravel\Passport\PersonalAccessClient', PersonalAccessClient::class); + $loader->alias('Laravel\Passport\Token', Token::class); + } +} diff --git a/src/Passport/PersonalAccessClient.php b/src/Passport/PersonalAccessClient.php new file mode 100644 index 000000000..c68cdbd40 --- /dev/null +++ b/src/Passport/PersonalAccessClient.php @@ -0,0 +1,32 @@ +belongsTo(Client::class); + } +} diff --git a/src/Passport/Token.php b/src/Passport/Token.php new file mode 100644 index 000000000..f1f079f30 --- /dev/null +++ b/src/Passport/Token.php @@ -0,0 +1,126 @@ + 'bool', + ]; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = [ + 'expires_at', + ]; + + /** + * Overwrite scopes setter to handle default passport JSON string + * and save native array. + * + * @param mixed $scopes + */ + public function setScopesAttribute($scopes) + { + if (is_string($scopes)) { + $scopes = json_decode($scopes, true); + } + + // If successfully decoded into array, then it will be saved as array. + // If still string, will be converted to array to preserve consistency. + $this->attributes['scopes'] = (array) $scopes; + } + + /** + * Get the client that the token belongs to. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function client() + { + return $this->belongsTo(Client::class); + } + + /** + * Determine if the token has a given scope. + * + * @param string $scope + * + * @return bool + */ + public function can($scope) + { + return in_array('*', $this->scopes) || + array_key_exists($scope, array_flip($this->scopes)); + } + + /** + * Determine if the token is missing a given scope. + * + * @param string $scope + * + * @return bool + */ + public function cant($scope) + { + return !$this->can($scope); + } + + /** + * Revoke the token instance. + * + * @return void + */ + public function revoke() + { + $this->forceFill(['revoked' => true])->save(); + } + + /** + * Determine if the token is a transient JWT token. + * + * @return bool + */ + public function transient() + { + return false; + } +} diff --git a/src/Jenssegers/Mongodb/Query/Builder.php b/src/Query/Builder.php similarity index 80% rename from src/Jenssegers/Mongodb/Query/Builder.php rename to src/Query/Builder.php index 74b9ee3a6..4047d680f 100644 --- a/src/Jenssegers/Mongodb/Query/Builder.php +++ b/src/Query/Builder.php @@ -1,4 +1,6 @@ -', '<=', '>=', '<>', '!=', 'like', 'not like', 'between', 'ilike', '&', '|', '^', '<<', '>>', @@ -79,9 +88,9 @@ class Builder extends BaseBuilder ]; /** - * Check if we need to return Collections instead of plain arrays (laravel >= 5.3 ) + * Check if we need to return Collections instead of plain arrays (laravel >= 5.3 ). * - * @var boolean + * @var bool */ protected $useCollections; @@ -93,16 +102,34 @@ class Builder extends BaseBuilder */ public function __construct(Connection $connection, Processor $processor) { - $this->grammar = new Grammar; + $this->grammar = new Grammar(); $this->connection = $connection; $this->processor = $processor; - $this->useCollections = version_compare(\Illuminate\Foundation\Application::VERSION, '5.3', '>='); + $this->useCollections = $this->shouldUseCollections(); + } + + /** + * Returns true if Laravel or Lumen >= 5.3. + * + * @return bool + */ + protected function shouldUseCollections() + { + if (function_exists('app')) { + $version = app()->version(); + $version = filter_var(explode(')', $version)[0], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); // lumen + return version_compare($version, '5.3', '>='); + } else { + $connection = $this->getConnection(); + return $connection->getConfig('use_collection'); + } } /** * Set the projections. * - * @param array $columns + * @param array $columns + * * @return $this */ public function project($columns) @@ -115,7 +142,8 @@ public function project($columns) /** * Set the cursor timeout in seconds. * - * @param int $seconds + * @param int $seconds + * * @return $this */ public function timeout($seconds) @@ -128,7 +156,8 @@ public function timeout($seconds) /** * Set the cursor hint. * - * @param mixed $index + * @param mixed $index + * * @return $this */ public function hint($index) @@ -141,8 +170,9 @@ public function hint($index) /** * Execute a query for a single record by ID. * - * @param mixed $id - * @param array $columns + * @param mixed $id + * @param array $columns + * * @return mixed */ public function find($id, $columns = []) @@ -153,7 +183,8 @@ public function find($id, $columns = []) /** * Execute the query as a "select" statement. * - * @param array $columns + * @param array $columns + * * @return array|static[]|Collection */ public function get($columns = []) @@ -164,7 +195,8 @@ public function get($columns = []) /** * Execute the query as a fresh "select" statement. * - * @param array $columns + * @param array $columns + * * @return array|static[]|Collection */ public function getFresh($columns = []) @@ -187,22 +219,23 @@ public function getFresh($columns = []) // Use MongoDB's aggregation framework when using grouping or aggregation functions. if ($this->groups or $this->aggregate or $this->paginating) { $group = []; + $unwinds = []; // Add grouping columns to the $group part of the aggregation pipeline. if ($this->groups) { foreach ($this->groups as $column) { - $group['_id'][$column] = '$' . $column; + $group['_id'][$column] = '$'.$column; // When grouping, also add the $last operator to each grouped field, // this mimics MySQL's behaviour a bit. - $group[$column] = ['$last' => '$' . $column]; + $group[$column] = ['$last' => '$'.$column]; } // Do the same for other columns that are selected. foreach ($this->columns as $column) { $key = str_replace('.', '_', $column); - $group[$key] = ['$last' => '$' . $column]; + $group[$key] = ['$last' => '$'.$column]; } } @@ -212,13 +245,20 @@ public function getFresh($columns = []) $function = $this->aggregate['function']; foreach ($this->aggregate['columns'] as $column) { + // Add unwind if a subdocument array should be aggregated + // column: subarray.price => {$unwind: '$subarray'} + if (count($splitColumns = explode('.*.', $column)) == 2) { + $unwinds[] = $splitColumns[0]; + $column = implode('.', $splitColumns); + } + // Translate count into sum. if ($function == 'count') { $group['aggregate'] = ['$sum' => 1]; } // Pass other functions directly. else { - $group['aggregate'] = ['$' . $function => '$' . $column]; + $group['aggregate'] = ['$'.$function => '$'.$column]; } } } @@ -241,6 +281,12 @@ public function getFresh($columns = []) if ($wheres) { $pipeline[] = ['$match' => $wheres]; } + + // apply unwinds for subdocument array aggregation + foreach ($unwinds as $unwind) { + $pipeline[] = ['$unwind' => '$'.$unwind]; + } + if ($group) { $pipeline[] = ['$group' => $group]; } @@ -263,6 +309,11 @@ public function getFresh($columns = []) 'typeMap' => ['root' => 'array', 'document' => 'array'], ]; + // Add custom query options + if (count($this->options)) { + $options = array_merge($options, $this->options); + } + // Execute aggregation $results = iterator_to_array($this->collection->aggregate($pipeline, $options)); @@ -321,11 +372,17 @@ public function getFresh($columns = []) // Fix for legacy support, converts the results to arrays instead of objects. $options['typeMap'] = ['root' => 'array', 'document' => 'array']; + // Add custom query options + if (count($this->options)) { + $options = array_merge($options, $this->options); + } + // Execute query and get MongoCursor $cursor = $this->collection->find($wheres, $options); // Return results as an array with numeric keys $results = iterator_to_array($cursor, false); + return $this->useCollections ? new Collection($results) : $results; } } @@ -355,21 +412,32 @@ public function generateCacheKey() /** * Execute an aggregate function on the database. * - * @param string $function - * @param array $columns + * @param string $function + * @param array $columns + * * @return mixed */ public function aggregate($function, $columns = []) { $this->aggregate = compact('function', 'columns'); + $previousColumns = $this->columns; + + // We will also back up the select bindings since the select clause will be + // removed when performing the aggregate function. Once the query is run + // we will add the bindings back onto this query so they can get used. + $previousSelectBindings = $this->bindings['select']; + + $this->bindings['select'] = []; + $results = $this->get($columns); // Once we have executed the query, we will reset the aggregate property so // that more select queries can be executed against the database without // the aggregate value getting in the way when the grammar builds it. - $this->columns = null; $this->aggregate = null; + $this->columns = $previousColumns; + $this->bindings['select'] = $previousSelectBindings; if (isset($results[0])) { $result = (array) $results[0]; @@ -385,7 +453,7 @@ public function aggregate($function, $columns = []) */ public function exists() { - return ! is_null($this->first()); + return !is_null($this->first()); } /** @@ -407,8 +475,9 @@ public function distinct($column = false) /** * Add an "order by" clause to the query. * - * @param string $column - * @param string $direction + * @param string $column + * @param string $direction + * * @return Builder */ public function orderBy($column, $direction = 'asc') @@ -429,10 +498,11 @@ public function orderBy($column, $direction = 'asc') /** * Add a where between statement to the query. * - * @param string $column - * @param array $values - * @param string $boolean - * @param bool $not + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * * @return Builder */ public function whereBetween($column, array $values, $boolean = 'and', $not = false) @@ -447,8 +517,9 @@ public function whereBetween($column, array $values, $boolean = 'and', $not = fa /** * Set the limit and offset for a given page. * - * @param int $page - * @param int $perPage + * @param int $page + * @param int $perPage + * * @return \Illuminate\Database\Query\Builder|static */ public function forPage($page, $perPage = 15) @@ -461,7 +532,8 @@ public function forPage($page, $perPage = 15) /** * Insert a new record into the database. * - * @param array $values + * @param array $values + * * @return bool */ public function insert(array $values) @@ -473,27 +545,28 @@ public function insert(array $values) foreach ($values as $value) { // As soon as we find a value that is not an array we assume the user is // inserting a single document. - if (! is_array($value)) { + if (!is_array($value)) { $batch = false; break; } } - if (! $batch) { + if (!$batch) { $values = [$values]; } // Batch insert $result = $this->collection->insertMany($values); - return (1 == (int) $result->isAcknowledged()); + return 1 == (int) $result->isAcknowledged(); } /** * Insert a new record and get the value of the primary key. * - * @param array $values - * @param string $sequence + * @param array $values + * @param string $sequence + * * @return int */ public function insertGetId(array $values, $sequence = null) @@ -513,14 +586,15 @@ public function insertGetId(array $values, $sequence = null) /** * Update a record in the database. * - * @param array $values - * @param array $options + * @param array $values + * @param array $options + * * @return int */ public function update(array $values, array $options = []) { // Use $set as default operator. - if (! starts_with(key($values), '$')) { + if (!starts_with(key($values), '$')) { $values = ['$set' => $values]; } @@ -530,16 +604,17 @@ public function update(array $values, array $options = []) /** * Increment a column's value by a given amount. * - * @param string $column - * @param int $amount - * @param array $extra + * @param string $column + * @param int $amount + * @param array $extra + * * @return int */ public function increment($column, $amount = 1, array $extra = [], array $options = []) { $query = ['$inc' => [$column => $amount]]; - if (! empty($extra)) { + if (!empty($extra)) { $query['$set'] = $extra; } @@ -556,9 +631,10 @@ public function increment($column, $amount = 1, array $extra = [], array $option /** * Decrement a column's value by a given amount. * - * @param string $column - * @param int $amount - * @param array $extra + * @param string $column + * @param int $amount + * @param array $extra + * * @return int */ public function decrement($column, $amount = 1, array $extra = [], array $options = []) @@ -569,8 +645,9 @@ public function decrement($column, $amount = 1, array $extra = [], array $option /** * Get an array with the values of a given column. * - * @param string $column - * @param string|null $key + * @param string $column + * @param string|null $key + * * @return array */ public function pluck($column, $key = null) @@ -581,18 +658,21 @@ public function pluck($column, $key = null) if ($key == '_id') { $results = $results->map(function ($item) { $item['_id'] = (string) $item['_id']; + return $item; }); } $p = Arr::pluck($results, $column, $key); + return $this->useCollections ? new Collection($p) : $p; } /** * Delete a record from the database. * - * @param mixed $id + * @param mixed $id + * * @return int */ public function delete($id = null) @@ -609,7 +689,8 @@ public function delete($id = null) /** * Set the collection which the query is targeting. * - * @param string $collection + * @param string $collection + * * @return Builder */ public function from($collection) @@ -628,15 +709,17 @@ public function truncate() { $result = $this->collection->drop(); - return (1 == (int) $result->ok); + return 1 == (int) $result->ok; } /** * Get an array with the values of a given column. * * @deprecated - * @param string $column - * @param string $key + * + * @param string $column + * @param string $key + * * @return array */ public function lists($column, $key = null) @@ -647,7 +730,8 @@ public function lists($column, $key = null) /** * Create a raw database expression. * - * @param closure $expression + * @param closure $expression + * * @return mixed */ public function raw($expression = null) @@ -658,7 +742,7 @@ public function raw($expression = null) } // Create an expression for the given value - elseif (! is_null($expression)) { + elseif (!is_null($expression)) { return new Expression($expression); } @@ -669,8 +753,9 @@ public function raw($expression = null) /** * Append one or more values to an array. * - * @param mixed $column - * @param mixed $value + * @param mixed $column + * @param mixed $value + * * @return int */ public function push($column, $value = null, $unique = false) @@ -695,8 +780,9 @@ public function push($column, $value = null, $unique = false) /** * Remove one or more values from an array. * - * @param mixed $column - * @param mixed $value + * @param mixed $column + * @param mixed $value + * * @return int */ public function pull($column, $value = null) @@ -719,12 +805,13 @@ public function pull($column, $value = null) /** * Remove one or more fields. * - * @param mixed $columns + * @param mixed $columns + * * @return int */ public function drop($columns) { - if (! is_array($columns)) { + if (!is_array($columns)) { $columns = [$columns]; } @@ -746,20 +833,21 @@ public function drop($columns) */ public function newQuery() { - return new Builder($this->connection, $this->processor); + return new self($this->connection, $this->processor); } /** * Perform an update query. * - * @param array $query - * @param array $options + * @param array $query + * @param array $options + * * @return int */ protected function performUpdate($query, array $options = []) { // Update multiple items by default. - if (! array_key_exists('multiple', $options)) { + if (!array_key_exists('multiple', $options)) { $options['multiple'] = true; } @@ -775,7 +863,8 @@ protected function performUpdate($query, array $options = []) /** * Convert a key to ObjectID if needed. * - * @param mixed $id + * @param mixed $id + * * @return mixed */ public function convertKey($id) @@ -790,13 +879,14 @@ public function convertKey($id) /** * Add a basic where clause to the query. * - * @param string $column - * @param string $operator - * @param mixed $value - * @param string $boolean - * @return \Illuminate\Database\Query\Builder|static + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean * * @throws \InvalidArgumentException + * + * @return \Illuminate\Database\Query\Builder|static */ public function where($column, $operator = null, $value = null, $boolean = 'and') { @@ -866,7 +956,18 @@ protected function compileWheres() // Convert DateTime values to UTCDateTime. if (isset($where['value']) and $where['value'] instanceof DateTime) { - $where['value'] = new UTCDateTime($where['value']->getTimestamp() * 1000); + $where['value'] = $this->dateTimeConvertion($where['value']); + } + + // Convert DateTime values to UTCDateTime in $where['values'] key. + if (array_key_exists('values', $where)) { + if (is_array($where['values']) && !empty($where['values'])) { + foreach ($where['values'] as $keyWhere => $valueWhere) { + if ($valueWhere instanceof DateTime) { + $where['values'][$keyWhere] = $this->dateTimeConvertion($valueWhere); + } + } + } } // The next item in a "chain" of wheres devices the boolean of the @@ -910,11 +1011,11 @@ protected function compileWhereBasic($where) $regex = preg_replace('#(^|[^\\\])%#', '$1.*', preg_quote($value)); // Convert like to regular expression. - if (! starts_with($value, '%')) { - $regex = '^' . $regex; + if (!starts_with($value, '%')) { + $regex = '^'.$regex; } - if (! ends_with($value, '%')) { - $regex = $regex . '$'; + if (!ends_with($value, '%')) { + $regex = $regex.'$'; } $value = new Regex($regex, 'i'); @@ -923,7 +1024,7 @@ protected function compileWhereBasic($where) // Manipulate regexp operations. elseif (in_array($operator, ['regexp', 'not regexp', 'regex', 'not regex'])) { // Automatically convert regular expression strings to Regex objects. - if (! $value instanceof Regex) { + if (!$value instanceof Regex) { $e = explode('/', $value); $flag = end($e); $regstr = substr($value, 1, -(strlen($flag) + 1)); @@ -937,12 +1038,12 @@ protected function compileWhereBasic($where) } } - if (! isset($operator) or $operator == '=') { + if (!isset($operator) or $operator == '=') { $query = [$column => $value]; } elseif (array_key_exists($operator, $this->conversion)) { $query = [$column => [$this->conversion[$operator] => $value]]; } else { - $query = [$column => ['$' . $operator => $value]]; + $query = [$column => ['$'.$operator => $value]]; } return $query; @@ -1019,11 +1120,42 @@ protected function compileWhereRaw($where) return $where['sql']; } + /** + * Convert to MongoDB\BSON\UTCDateTime if $value is a DateTime object. + * + * @param mixed $value + * + * @return mixed + */ + protected function dateTimeConvertion($value) + { + if ($value instanceof DateTime) { + return new UTCDateTime($value->getTimestamp() * 1000); + } + + return $value; + } + + /** + * Set custom options for the query. + * + * @param array $options + * + * @return $this + */ + public function options(array $options) + { + $this->options = $options; + + return $this; + } + /** * Handle dynamic method calls into the method. * - * @param string $method - * @param array $parameters + * @param string $method + * @param array $parameters + * * @return mixed */ public function __call($method, $parameters) diff --git a/src/Jenssegers/Mongodb/Query/Grammar.php b/src/Query/Grammar.php similarity index 71% rename from src/Jenssegers/Mongodb/Query/Grammar.php rename to src/Query/Grammar.php index 9ecbc547f..ca50914cc 100644 --- a/src/Jenssegers/Mongodb/Query/Grammar.php +++ b/src/Query/Grammar.php @@ -1,4 +1,6 @@ -getTimestamp(); - $this->getTable()->insert(compact('connection', 'queue', 'payload', 'failed_at')); + $this->getTable()->insert(compact('connection', 'queue', 'payload', 'failed_at', 'exception')); } /** @@ -32,6 +35,7 @@ public function all() $all = array_map(function ($job) { $job['id'] = (string) $job['_id']; + return $job; }, $all); @@ -41,7 +45,8 @@ public function all() /** * Get a single failed job. * - * @param mixed $id + * @param mixed $id + * * @return array */ public function find($id) @@ -56,7 +61,8 @@ public function find($id) /** * Delete a single failed job from storage. * - * @param mixed $id + * @param mixed $id + * * @return bool */ public function forget($id) diff --git a/src/Jenssegers/Mongodb/Queue/MongoConnector.php b/src/Queue/MongoConnector.php similarity index 85% rename from src/Jenssegers/Mongodb/Queue/MongoConnector.php rename to src/Queue/MongoConnector.php index fbdf480ac..27ae6171e 100644 --- a/src/Jenssegers/Mongodb/Queue/MongoConnector.php +++ b/src/Queue/MongoConnector.php @@ -1,4 +1,6 @@ -job->reserved; + } + /** + * @return \DateTime + */ + public function reservedAt() + { + return $this->job->reserved_at; + } +} diff --git a/src/Jenssegers/Mongodb/Queue/MongoQueue.php b/src/Queue/MongoQueue.php similarity index 84% rename from src/Jenssegers/Mongodb/Queue/MongoQueue.php rename to src/Queue/MongoQueue.php index 88279e17f..151f7e835 100644 --- a/src/Jenssegers/Mongodb/Queue/MongoQueue.php +++ b/src/Queue/MongoQueue.php @@ -1,4 +1,6 @@ -getQueue($queue); - + if (!is_null($this->expire)) { $this->releaseJobsThatHaveBeenReservedTooLong($queue); } - + if ($job = $this->getNextAvailableJobAndReserve($queue)) { - return new DatabaseJob( - $this->container, $this, $job, $queue + return new MongoJob( + $this->container, $this, $job, $this->connectionName, $queue ); } } @@ -39,7 +49,7 @@ public function pop($queue = null) * 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 + * @param string|null $queue * * @return \StdClass|null */ @@ -49,13 +59,13 @@ protected function getNextAvailableJobAndReserve($queue) [ 'queue' => $this->getQueue($queue), 'reserved' => 0, - 'available_at' => ['$lte' => $this->getTime()], + 'available_at' => ['$lte' => $this->currentTime()], ], [ '$set' => [ 'reserved' => 1, - 'reserved_at' => $this->getTime(), + 'reserved_at' => $this->currentTime(), ], ], [ @@ -74,7 +84,8 @@ protected function getNextAvailableJobAndReserve($queue) /** * Release the jobs that have been reserved for too long. * - * @param string $queue + * @param string $queue + * * @return void */ protected function releaseJobsThatHaveBeenReservedTooLong($queue) @@ -104,8 +115,9 @@ protected function releaseJobsThatHaveBeenReservedTooLong($queue) /** * Release the given job ID from reservation. * - * @param string $id - * @param int $attempts + * @param string $id + * @param int $attempts + * * @return void */ protected function releaseJob($id, $attempts) @@ -120,8 +132,9 @@ protected function releaseJob($id, $attempts) /** * Delete a reserved job from the queue. * - * @param string $queue - * @param string $id + * @param string $queue + * @param string $id + * * @return void */ public function deleteReserved($queue, $id) diff --git a/src/Relations/BelongsTo.php b/src/Relations/BelongsTo.php new file mode 100755 index 000000000..3506230df --- /dev/null +++ b/src/Relations/BelongsTo.php @@ -0,0 +1,78 @@ +query->where($this->getOtherKey(), '=', $this->parent->{$this->foreignKey}); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + */ + 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->getOtherKey(); + + $this->query->whereIn($key, $this->getEagerModelKeys($models)); + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * + * @return array + */ + public function match(array $models, \Illuminate\Database\Eloquent\Collection $results, $relation) + { + $foreign = $this->foreignKey; + $other = $this->getOtherKey(); + + // First we will get to build a dictionary of the child models by their primary + // key of the relationship, then we can easily match the children back onto + // the parents using that dictionary and the primary key of the children. + $dictionary = []; + foreach ($results as $result) { + $dictionary[$result->getAttribute($other)] = $result; + } + // Once we have the dictionary constructed, we can loop through all the parents + // and match back onto their children using these keys of the dictionary and + // the primary key of the children to map them onto the correct instances. + foreach ($models as $model) { + if (isset($dictionary[(string)$model->$foreign])) { + $model->setRelation($relation, $dictionary[(string)$model->$foreign]); + } + } + + return $models; + } + + public function getOtherKey() + { + // Laravel >= 5.4 + if (property_exists($this, 'ownerKey')) { + return $this->ownerKey; + } + + return $this->otherKey; + } +} diff --git a/src/Jenssegers/Mongodb/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php similarity index 87% rename from src/Jenssegers/Mongodb/Relations/BelongsToMany.php rename to src/Relations/BelongsToMany.php index 37ae75e8d..e25bc1abe 100644 --- a/src/Jenssegers/Mongodb/Relations/BelongsToMany.php +++ b/src/Relations/BelongsToMany.php @@ -1,4 +1,6 @@ -parent->{$this->otherKey} ?: []; + $current = $this->parent->{$this->relatedKey} ?: []; // See issue #256. if ($current instanceof Collection) { @@ -153,9 +167,9 @@ public function sync($ids, $detaching = true) /** * Update an existing pivot record on the table. * - * @param mixed $id - * @param array $attributes - * @param bool $touch + * @param mixed $id + * @param array $attributes + * @param bool $touch */ public function updateExistingPivot($id, array $attributes, $touch = true) { @@ -165,9 +179,9 @@ public function updateExistingPivot($id, array $attributes, $touch = true) /** * Attach a model to the parent. * - * @param mixed $id - * @param array $attributes - * @param bool $touch + * @param mixed $id + * @param array $attributes + * @param bool $touch */ public function attach($id, array $attributes = [], $touch = true) { @@ -192,7 +206,7 @@ public function attach($id, array $attributes = [], $touch = true) } // Attach the new ids to the parent model. - $this->parent->push($this->otherKey, (array) $id, true); + $this->parent->push($this->relatedKey, (array) $id, true); if ($touch) { $this->touchIfTouching(); @@ -202,8 +216,9 @@ public function attach($id, array $attributes = [], $touch = true) /** * Detach models from the relationship. * - * @param int|array $ids - * @param bool $touch + * @param int|array $ids + * @param bool $touch + * * @return int */ public function detach($ids = [], $touch = true) @@ -220,7 +235,7 @@ public function detach($ids = [], $touch = true) $ids = (array) $ids; // Detach all ids from the parent model. - $this->parent->pull($this->otherKey, $ids); + $this->parent->pull($this->relatedKey, $ids); // Prepare the query to select all related objects. if (count($ids) > 0) { @@ -240,7 +255,8 @@ public function detach($ids = [], $touch = true) /** * Build model dictionary keyed by the relation's foreign key. * - * @param \Illuminate\Database\Eloquent\Collection $results + * @param \Illuminate\Database\Eloquent\Collection $results + * * @return array */ protected function buildDictionary(Collection $results) @@ -293,21 +309,24 @@ public function getForeignKey() /** * 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 + * The original function has been renamed to formatRecordsList since Laravel 5.3. * * @deprecated - * @param array $records + * + * @param array $records + * * @return array */ protected function formatSyncList(array $records) { $results = []; foreach ($records as $id => $attributes) { - if (! is_array($attributes)) { + if (!is_array($attributes)) { list($id, $attributes) = [$attributes, []]; } $results[$id] = $attributes; } + return $results; } } diff --git a/src/Jenssegers/Mongodb/Relations/EmbedsMany.php b/src/Relations/EmbedsMany.php similarity index 86% rename from src/Jenssegers/Mongodb/Relations/EmbedsMany.php rename to src/Relations/EmbedsMany.php index e314ea9a5..894f759a5 100644 --- a/src/Jenssegers/Mongodb/Relations/EmbedsMany.php +++ b/src/Relations/EmbedsMany.php @@ -1,4 +1,6 @@ -getKeyName() == '_id' and ! $model->getKey()) { - $model->setAttribute('_id', new ObjectID); + if ($model->getKeyName() == '_id' and !$model->getKey()) { + $model->setAttribute('_id', new ObjectID()); } // For deeply nested documents, let the parent handle the changes. @@ -66,7 +69,8 @@ public function performInsert(Model $model) /** * Save an existing model and attach it to the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return Model|bool */ public function performUpdate(Model $model) @@ -82,10 +86,10 @@ public function performUpdate(Model $model) $foreignKey = $this->getForeignKeyValue($model); // Use array dot notation for better update behavior. - $values = array_dot($model->getDirty(), $this->localKey . '.$.'); + $values = array_dot($model->getDirty(), $this->localKey.'.$.'); // Update document in database. - $result = $this->getBaseQuery()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey) + $result = $this->getBaseQuery()->where($this->localKey.'.'.$model->getKeyName(), $foreignKey) ->update($values); // Attach the model to its parent. @@ -99,7 +103,8 @@ public function performUpdate(Model $model) /** * Delete an existing model and detach it from the parent model. * - * @param Model $model + * @param Model $model + * * @return int */ public function performDelete(Model $model) @@ -126,12 +131,13 @@ public function performDelete(Model $model) /** * Associate the model instance to the given parent, without saving it to the database. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return \Illuminate\Database\Eloquent\Model */ public function associate(Model $model) { - if (! $this->contains($model)) { + if (!$this->contains($model)) { return $this->associateNew($model); } else { return $this->associateExisting($model); @@ -141,7 +147,8 @@ public function associate(Model $model) /** * Dissociate the model instance from the given parent, without saving it to the database. * - * @param mixed $ids + * @param mixed $ids + * * @return int */ public function dissociate($ids = []) @@ -170,7 +177,8 @@ public function dissociate($ids = []) /** * Destroy the embedded models for the given IDs. * - * @param mixed $ids + * @param mixed $ids + * * @return int */ public function destroy($ids = []) @@ -212,7 +220,8 @@ public function delete() /** * Destroy alias. * - * @param mixed $ids + * @param mixed $ids + * * @return int */ public function detach($ids = []) @@ -223,7 +232,8 @@ public function detach($ids = []) /** * Save alias. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return \Illuminate\Database\Eloquent\Model */ public function attach(Model $model) @@ -234,14 +244,15 @@ public function attach(Model $model) /** * Associate a new model instance to the given parent, without saving it to the database. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return \Illuminate\Database\Eloquent\Model */ protected function associateNew($model) { // Create a new key if needed. - if (! $model->getAttribute('_id')) { - $model->setAttribute('_id', new ObjectID); + if (!$model->getKey()) { + $model->setAttribute($model->getKeyName(), new ObjectID()); } $records = $this->getEmbedded(); @@ -255,7 +266,8 @@ protected function associateNew($model) /** * Associate an existing model instance to the given parent, without saving it to the database. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return \Illuminate\Database\Eloquent\Model */ protected function associateExisting($model) @@ -281,7 +293,8 @@ protected function associateExisting($model) /** * Get a paginator for the "select" statement. * - * @param int $perPage + * @param int $perPage + * * @return \Illuminate\Pagination\Paginator */ public function paginate($perPage = null) @@ -314,11 +327,11 @@ protected function getEmbedded() /** * Set the embedded records array. * - * @param array $models + * @param array $models */ protected function setEmbedded($models) { - if (! is_array($models)) { + if (!is_array($models)) { $models = [$models]; } @@ -328,8 +341,9 @@ protected function setEmbedded($models) /** * Handle dynamic method calls to the relationship. * - * @param string $method - * @param array $parameters + * @param string $method + * @param array $parameters + * * @return mixed */ public function __call($method, $parameters) diff --git a/src/Jenssegers/Mongodb/Relations/EmbedsOne.php b/src/Relations/EmbedsOne.php similarity index 85% rename from src/Jenssegers/Mongodb/Relations/EmbedsOne.php rename to src/Relations/EmbedsOne.php index 7c1897bb4..6c22315af 100644 --- a/src/Jenssegers/Mongodb/Relations/EmbedsOne.php +++ b/src/Relations/EmbedsOne.php @@ -1,4 +1,6 @@ -getKeyName() == '_id' and ! $model->getKey()) { - $model->setAttribute('_id', new ObjectID); + if ($model->getKeyName() == '_id' and !$model->getKey()) { + $model->setAttribute('_id', new ObjectID()); } // For deeply nested documents, let the parent handle the changes. @@ -63,7 +66,8 @@ public function performInsert(Model $model) /** * Save an existing model and attach it to the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return \Illuminate\Database\Eloquent\Model|bool */ public function performUpdate(Model $model) @@ -75,7 +79,7 @@ public function performUpdate(Model $model) } // Use array dot notation for better update behavior. - $values = array_dot($model->getDirty(), $this->localKey . '.'); + $values = array_dot($model->getDirty(), $this->localKey.'.'); $result = $this->getBaseQuery()->update($values); @@ -90,7 +94,8 @@ public function performUpdate(Model $model) /** * Delete an existing model and detach it from the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return int */ public function performDelete(Model $model) @@ -116,7 +121,8 @@ public function performDelete(Model $model) /** * Attach the model to its parent. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model + * * @return \Illuminate\Database\Eloquent\Model */ public function associate(Model $model) diff --git a/src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php b/src/Relations/EmbedsOneOrMany.php similarity index 86% rename from src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php rename to src/Relations/EmbedsOneOrMany.php index 8d599cd5f..1fb764bc0 100644 --- a/src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php +++ b/src/Relations/EmbedsOneOrMany.php @@ -1,4 +1,6 @@ -all(); } - if (! is_array($ids)) { + if (!is_array($ids)) { $ids = [$ids]; } @@ -225,7 +233,8 @@ protected function getEmbedded() /** * Set the embedded records array. * - * @param array $records + * @param array $records + * * @return \Illuminate\Database\Eloquent\Model */ protected function setEmbedded($records) @@ -244,7 +253,8 @@ protected function setEmbedded($records) /** * Get the foreign key value for the relation. * - * @param mixed $id + * @param mixed $id + * * @return mixed */ protected function getForeignKeyValue($id) @@ -260,8 +270,9 @@ protected function getForeignKeyValue($id) /** * Convert an array of records to a Collection. * - * @param array $records - * @return \Jenssegers\Mongodb\Eloquent\Collection + * @param array $records + * + * @return \Moloquent\Eloquent\Collection */ protected function toCollection(array $records = []) { @@ -281,7 +292,8 @@ protected function toCollection(array $records = []) /** * Create a related model instanced. * - * @param array $attributes + * @param array $attributes + * * @return \Illuminate\Database\Eloquent\Model */ protected function toModel($attributes = []) @@ -349,13 +361,14 @@ protected function isNested() /** * Get the fully qualified local key name. * - * @param string $glue + * @param string $glue + * * @return string */ protected function getPathHierarchy($glue = '.') { if ($parentRelation = $this->getParentRelation()) { - return $parentRelation->getPathHierarchy($glue) . $glue . $this->localKey; + return $parentRelation->getPathHierarchy($glue).$glue.$this->localKey; } return $this->localKey; @@ -369,7 +382,7 @@ protected function getPathHierarchy($glue = '.') public function getQualifiedParentKeyName() { if ($parentRelation = $this->getParentRelation()) { - return $parentRelation->getPathHierarchy() . '.' . $this->parent->getKeyName(); + return $parentRelation->getPathHierarchy().'.'.$this->parent->getKeyName(); } return $this->parent->getKeyName(); diff --git a/src/Jenssegers/Mongodb/Relations/HasMany.php b/src/Relations/HasMany.php similarity index 74% rename from src/Jenssegers/Mongodb/Relations/HasMany.php rename to src/Relations/HasMany.php index 4a30e940f..8f74c3aa6 100644 --- a/src/Jenssegers/Mongodb/Relations/HasMany.php +++ b/src/Relations/HasMany.php @@ -1,15 +1,20 @@ -getAttribute($key) : $model->getKey(); + + if ($this->related->useMongoId()) { + $model->setRelationCast($key); + + return $model->castAttribute($key, $id, 'set'); + } + + return $id; + }, $models))); + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * + * @return array + */ + protected function buildDictionary(Collection $results) + { + $dictionary = []; + $foreign = $this->getPlainForeignKey(); + // First we will create a dictionary of models keyed by the foreign key of the + // relationship as this will allow us to quickly access all of the related + // models without having to do nested looping which will be quite slow. + foreach ($results as $result) { + $dictionary[(string) $result->{$foreign}][] = $result; + } + + return $dictionary; + } + + /** + * Get the key value of the parent's local key. + * + * @return mixed + */ + public function getParentKey() + { + $id = $this->parent->getAttribute($this->localKey); + $this->related->setRelationCast($this->localKey); + if ($this->related->useMongoId() + && $this->related->hasCast($this->localKey, null, 'set')) { + $id = $this->related->castAttribute($this->localKey, $id, 'set'); + } + + return $id; + } +} diff --git a/src/Relations/MorphMany.php b/src/Relations/MorphMany.php new file mode 100644 index 000000000..39fbef3b0 --- /dev/null +++ b/src/Relations/MorphMany.php @@ -0,0 +1,10 @@ +{$this->morphType}) { + $this->dictionary[$model->{$this->morphType}][(string) $model->{$this->foreignKey}][] = $model; + } + } + } + /** * Get all of the relation results for a type. * - * @param string $type + * @param string $type + * * @return \Illuminate\Database\Eloquent\Collection */ protected function getResultsByType($type) diff --git a/src/Jenssegers/Mongodb/Schema/Blueprint.php b/src/Schema/Blueprint.php similarity index 83% rename from src/Jenssegers/Mongodb/Schema/Blueprint.php rename to src/Schema/Blueprint.php index f49aae1f3..056fc9678 100644 --- a/src/Jenssegers/Mongodb/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -1,4 +1,6 @@ -assertInstanceOf('Jenssegers\Mongodb\Connection', $connection); + $this->assertInstanceOf('Moloquent\Connection', $connection); } public function testReconnect() @@ -32,13 +32,13 @@ public function testDb() public function testCollection() { $collection = DB::connection('mongodb')->getCollection('unittest'); - $this->assertInstanceOf('Jenssegers\Mongodb\Collection', $collection); + $this->assertInstanceOf('Moloquent\Collection', $collection); $collection = DB::connection('mongodb')->collection('unittests'); - $this->assertInstanceOf('Jenssegers\Mongodb\Query\Builder', $collection); + $this->assertInstanceOf('Moloquent\Query\Builder', $collection); $collection = DB::connection('mongodb')->table('unittests'); - $this->assertInstanceOf('Jenssegers\Mongodb\Query\Builder', $collection); + $this->assertInstanceOf('Moloquent\Query\Builder', $collection); } // public function testDynamic() @@ -87,7 +87,7 @@ public function testQueryLog() public function testSchemaBuilder() { $schema = DB::connection('mongodb')->getSchemaBuilder(); - $this->assertInstanceOf('Jenssegers\Mongodb\Schema\Builder', $schema); + $this->assertInstanceOf('Moloquent\Schema\Builder', $schema); } public function testDriverName() diff --git a/tests/EmbeddedRelationsTest.php b/tests/EmbeddedRelationsTest.php index b0507c7ef..f53bdf4c6 100644 --- a/tests/EmbeddedRelationsTest.php +++ b/tests/EmbeddedRelationsTest.php @@ -21,10 +21,10 @@ public function testEmbedsManySave() $address = new Address(['city' => 'London']); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($address), $address)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.creating: ' . get_class($address), $address)->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.created: ' . get_class($address), $address); - $events->shouldReceive('fire')->once()->with('eloquent.saved: ' . get_class($address), $address); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($address), $address)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.created: '.get_class($address), $address); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($address), $address); $address = $user->addresses()->save($address); $address->unsetEventDispatcher(); @@ -46,10 +46,10 @@ public function testEmbedsManySave() $this->assertEquals(['London', 'Paris'], $user->addresses->pluck('city')->all()); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($address), $address)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.updating: ' . get_class($address), $address)->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.updated: ' . get_class($address), $address); - $events->shouldReceive('fire')->once()->with('eloquent.saved: ' . get_class($address), $address); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.updating: '.get_class($address), $address)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.updated: '.get_class($address), $address); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($address), $address); $address->city = 'New York'; $user->addresses()->save($address); @@ -76,7 +76,7 @@ public function testEmbedsManySave() $this->assertEquals(['London', 'New York', 'Bruxelles'], $user->addresses->pluck('city')->all()); $address = $user->addresses[1]; - $address->city = "Manhattan"; + $address->city = 'Manhattan'; $user->addresses()->save($address); $this->assertEquals(['London', 'Manhattan', 'Bruxelles'], $user->addresses->pluck('city')->all()); @@ -211,8 +211,8 @@ public function testEmbedsManyDestroy() $address = $user->addresses->first(); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.deleting: ' . get_class($address), Mockery::type('Address'))->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.deleted: ' . get_class($address), Mockery::type('Address')); + $events->shouldReceive('until')->once()->with('eloquent.deleting: '.get_class($address), Mockery::type('Address'))->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.deleted: '.get_class($address), Mockery::type('Address')); $user->addresses()->destroy($address->_id); $this->assertEquals(['Bristol', 'Bruxelles'], $user->addresses->pluck('city')->all()); @@ -249,8 +249,8 @@ public function testEmbedsManyDelete() $address = $user->addresses->first(); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.deleting: ' . get_class($address), Mockery::type('Address'))->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.deleted: ' . get_class($address), Mockery::type('Address')); + $events->shouldReceive('until')->once()->with('eloquent.deleting: '.get_class($address), Mockery::type('Address'))->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.deleted: '.get_class($address), Mockery::type('Address')); $address->delete(); @@ -297,8 +297,8 @@ public function testEmbedsManyCreatingEventReturnsFalse() $address = new Address(['city' => 'London']); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($address), $address)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.creating: ' . get_class($address), $address)->andReturn(false); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($address), $address)->andReturn(false); $this->assertFalse($user->addresses()->save($address)); $address->unsetEventDispatcher(); @@ -311,7 +311,7 @@ public function testEmbedsManySavingEventReturnsFalse() $address->exists = true; $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($address), $address)->andReturn(false); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(false); $this->assertFalse($user->addresses()->save($address)); $address->unsetEventDispatcher(); @@ -324,8 +324,8 @@ public function testEmbedsManyUpdatingEventReturnsFalse() $user->addresses()->save($address); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($address), $address)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.updating: ' . get_class($address), $address)->andReturn(false); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.updating: '.get_class($address), $address)->andReturn(false); $address->city = 'Warsaw'; @@ -341,7 +341,7 @@ public function testEmbedsManyDeletingEventReturnsFalse() $address = $user->addresses->first(); $address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.deleting: ' . get_class($address), Mockery::mustBe($address))->andReturn(false); + $events->shouldReceive('until')->once()->with('eloquent.deleting: '.get_class($address), Mockery::mustBe($address))->andReturn(false); $this->assertEquals(0, $user->addresses()->destroy($address)); $this->assertEquals(['New York'], $user->addresses->pluck('city')->all()); @@ -444,10 +444,10 @@ public function testEmbedsOne() $father = new User(['name' => 'Mark Doe']); $father->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($father), $father)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.creating: ' . get_class($father), $father)->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.created: ' . get_class($father), $father); - $events->shouldReceive('fire')->once()->with('eloquent.saved: ' . get_class($father), $father); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($father), $father)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($father), $father)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.created: '.get_class($father), $father); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($father), $father); $father = $user->father()->save($father); $father->unsetEventDispatcher(); @@ -463,10 +463,10 @@ public function testEmbedsOne() $this->assertInstanceOf('MongoDB\BSON\ObjectID', $raw['_id']); $father->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($father), $father)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.updating: ' . get_class($father), $father)->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.updated: ' . get_class($father), $father); - $events->shouldReceive('fire')->once()->with('eloquent.saved: ' . get_class($father), $father); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($father), $father)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.updating: '.get_class($father), $father)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.updated: '.get_class($father), $father); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($father), $father); $father->name = 'Tom Doe'; $user->father()->save($father); @@ -478,10 +478,10 @@ public function testEmbedsOne() $father = new User(['name' => 'Jim Doe']); $father->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->once()->with('eloquent.saving: ' . get_class($father), $father)->andReturn(true); - $events->shouldReceive('until')->once()->with('eloquent.creating: ' . get_class($father), $father)->andReturn(true); - $events->shouldReceive('fire')->once()->with('eloquent.created: ' . get_class($father), $father); - $events->shouldReceive('fire')->once()->with('eloquent.saved: ' . get_class($father), $father); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($father), $father)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($father), $father)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.created: '.get_class($father), $father); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($father), $father); $father = $user->father()->save($father); $father->unsetEventDispatcher(); @@ -496,7 +496,7 @@ public function testEmbedsOneAssociate() $father = new User(['name' => 'Mark Doe']); $father->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher')); - $events->shouldReceive('until')->times(0)->with('eloquent.saving: ' . get_class($father), $father); + $events->shouldReceive('until')->times(0)->with('eloquent.saving: '.get_class($father), $father); $father = $user->father()->associate($father); $father->unsetEventDispatcher(); @@ -664,7 +664,7 @@ public function testDoubleAssociate() public function testSaveEmptyModel() { $user = User::create(['name' => 'John Doe']); - $user->addresses()->save(new Address); + $user->addresses()->save(new Address()); $this->assertNotNull($user->addresses); $this->assertEquals(1, $user->addresses()->count()); } diff --git a/tests/ModelTest.php b/tests/ModelTest.php index 3faa763f7..8c36bdb75 100644 --- a/tests/ModelTest.php +++ b/tests/ModelTest.php @@ -12,9 +12,9 @@ public function tearDown() public function testNewModel() { - $user = new User; - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $user); - $this->assertInstanceOf('Jenssegers\Mongodb\Connection', $user->getConnection()); + $user = new User(); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $user); + $this->assertInstanceOf('Moloquent\Connection', $user->getConnection()); $this->assertEquals(false, $user->exists); $this->assertEquals('users', $user->getTable()); $this->assertEquals('_id', $user->getKeyName()); @@ -22,7 +22,7 @@ public function testNewModel() public function testInsert() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; @@ -47,7 +47,7 @@ public function testInsert() public function testUpdate() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; @@ -80,7 +80,7 @@ public function testUpdate() public function testManualStringId() { - $user = new User; + $user = new User(); $user->_id = '4af9f23d8ead0e1d32000000'; $user->name = 'John Doe'; $user->title = 'admin'; @@ -93,7 +93,7 @@ public function testManualStringId() $raw = $user->getAttributes(); $this->assertInstanceOf('MongoDB\BSON\ObjectID', $raw['_id']); - $user = new User; + $user = new User(); $user->_id = 'customId'; $user->name = 'John Doe'; $user->title = 'admin'; @@ -109,7 +109,7 @@ public function testManualStringId() public function testManualIntId() { - $user = new User; + $user = new User(); $user->_id = 1; $user->name = 'John Doe'; $user->title = 'admin'; @@ -125,7 +125,7 @@ public function testManualIntId() public function testDelete() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; @@ -141,13 +141,13 @@ public function testDelete() public function testAll() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; $user->save(); - $user = new User; + $user = new User(); $user->name = 'Jane Doe'; $user->title = 'user'; $user->age = 32; @@ -162,7 +162,7 @@ public function testAll() public function testFind() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; @@ -170,7 +170,7 @@ public function testFind() $check = User::find($user->_id); - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $check); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $check); $this->assertEquals(true, $check->exists); $this->assertEquals($user->_id, $check->_id); @@ -188,7 +188,7 @@ public function testGet() $users = User::get(); $this->assertEquals(2, count($users)); $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $users); - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $users[0]); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $users[0]); } public function testFirst() @@ -199,7 +199,7 @@ public function testFirst() ]); $user = User::first(); - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $user); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $user); $this->assertEquals('John Doe', $user->name); } @@ -226,7 +226,7 @@ public function testCreate() { $user = User::create(['name' => 'Jane Poe']); - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $user); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $user); $this->assertEquals(true, $user->exists); $this->assertEquals('Jane Poe', $user->name); @@ -236,7 +236,7 @@ public function testCreate() public function testDestroy() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; @@ -249,7 +249,7 @@ public function testDestroy() public function testTouch() { - $user = new User; + $user = new User(); $user->name = 'John Doe'; $user->title = 'admin'; $user->age = 35; @@ -297,10 +297,10 @@ public function testSoftDelete() public function testPrimaryKey() { - $user = new User; + $user = new User(); $this->assertEquals('_id', $user->getKeyName()); - $book = new Book; + $book = new Book(); $this->assertEquals('title', $book->getKeyName()); $book->title = 'A Game of Thrones'; @@ -401,7 +401,7 @@ public function testDates() $user = User::create(['name' => 'Jane Doe', 'entry' => ['date' => '2005-08-08']]); $this->assertInstanceOf('Carbon\Carbon', $user->getAttribute('entry.date')); - $user->setAttribute('entry.date', new DateTime); + $user->setAttribute('entry.date', new DateTime()); $this->assertInstanceOf('Carbon\Carbon', $user->getAttribute('entry.date')); $data = $user->toArray(); @@ -454,13 +454,13 @@ public function testRaw() return $collection->find(['age' => 35]); }); $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $users); - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $users[0]); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $users[0]); $user = User::raw(function ($collection) { return $collection->findOne(['age' => 35]); }); - $this->assertInstanceOf('Jenssegers\Mongodb\Eloquent\Model', $user); + $this->assertInstanceOf('Moloquent\Eloquent\Model', $user); $count = User::raw(function ($collection) { return $collection->count(); @@ -486,6 +486,13 @@ public function testDotNotation() $this->assertEquals('Paris', $user->getAttribute('address.city')); $this->assertEquals('Paris', $user['address.city']); $this->assertEquals('Paris', $user->{'address.city'}); + + // Fill + $user->fill([ + 'address.city' => 'Strasbourg', + ]); + + $this->assertEquals('Strasbourg', $user['address.city']); } public function testGetDirtyDates() diff --git a/tests/MysqlRelationsTest.php b/tests/MysqlRelationsTest.php index 6dbd0571f..aa1313d70 100644 --- a/tests/MysqlRelationsTest.php +++ b/tests/MysqlRelationsTest.php @@ -20,12 +20,12 @@ public function tearDown() public function testMysqlRelations() { - $user = new MysqlUser; + $user = new MysqlUser(); $this->assertInstanceOf('MysqlUser', $user); $this->assertInstanceOf('Illuminate\Database\MySqlConnection', $user->getConnection()); // Mysql User - $user->name = "John Doe"; + $user->name = 'John Doe'; $user->save(); $this->assertTrue(is_int($user->id)); @@ -50,8 +50,8 @@ public function testMysqlRelations() $this->assertEquals('John Doe', $role->mysqlUser->name); // MongoDB User - $user = new User; - $user->name = "John Doe"; + $user = new User(); + $user->name = 'John Doe'; $user->save(); // MongoDB has many diff --git a/tests/PassportTest.php b/tests/PassportTest.php new file mode 100644 index 000000000..4dc745547 --- /dev/null +++ b/tests/PassportTest.php @@ -0,0 +1,24 @@ +delete(); + DB::collection('oauth_access_tokens')->delete(); + DB::collection('oauth_clients')->delete(); + DB::collection('oauth_personal_access_clients')->delete(); + DB::collection('oauth_refresh_tokens')->delete(); + } + + public function testPassportInstall() + { + $result = Artisan::call('passport:install', []); + $this->assertEquals(0, $result); + } +} diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index ca08bf36a..c449f27bb 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -1,7 +1,7 @@ assertInstanceOf('Jenssegers\Mongodb\Query\Builder', DB::collection('users')); + $this->assertInstanceOf('Moloquent\Query\Builder', DB::collection('users')); } public function testGet() @@ -190,10 +190,10 @@ public function testRaw() $this->assertEquals(1, count($cursor->toArray())); $collection = DB::collection('users')->raw(); - $this->assertInstanceOf('Jenssegers\Mongodb\Collection', $collection); + $this->assertInstanceOf('Moloquent\Collection', $collection); $collection = User::raw(); - $this->assertInstanceOf('Jenssegers\Mongodb\Collection', $collection); + $this->assertInstanceOf('Moloquent\Collection', $collection); $results = DB::collection('users')->whereRaw(['age' => 20])->get(); $this->assertEquals(1, count($results)); @@ -419,6 +419,22 @@ public function testSubdocumentAggregate() $this->assertEquals(16.25, DB::collection('items')->avg('amount.hidden')); } + public function testSubdocumentArrayAggregate() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'amount' => [['hidden' => 10, 'found' => 3], ['hidden' => 5, 'found' => 2]]], + ['name' => 'fork', 'amount' => [['hidden' => 35, 'found' => 12], ['hidden' => 7, 'found' => 17], ['hidden' => 1, 'found' => 19]]], + ['name' => 'spoon', 'amount' => [['hidden' => 14, 'found' => 21]]], + ['name' => 'teaspoon', 'amount' => []], + ]); + + $this->assertEquals(72, DB::collection('items')->sum('amount.*.hidden')); + $this->assertEquals(6, DB::collection('items')->count('amount.*.hidden')); + $this->assertEquals(1, DB::collection('items')->min('amount.*.hidden')); + $this->assertEquals(35, DB::collection('items')->max('amount.*.hidden')); + $this->assertEquals(12, DB::collection('items')->avg('amount.*.hidden')); + } + public function testUpsert() { DB::collection('items')->where('name', 'knife') @@ -473,23 +489,29 @@ public function testUpdateSubdocument() public function testDates() { DB::collection('users')->insert([ - ['name' => 'John Doe', 'birthday' => new UTCDateTime(1000 * strtotime("1980-01-01 00:00:00"))], - ['name' => 'Jane Doe', 'birthday' => new UTCDateTime(1000 * strtotime("1981-01-01 00:00:00"))], - ['name' => 'Robert Roe', 'birthday' => new UTCDateTime(1000 * strtotime("1982-01-01 00:00:00"))], - ['name' => 'Mark Moe', 'birthday' => new UTCDateTime(1000 * strtotime("1983-01-01 00:00:00"))], + ['name' => 'John Doe', 'birthday' => new UTCDateTime(1000 * strtotime('1980-01-01 00:00:00'))], + ['name' => 'Jane Doe', 'birthday' => new UTCDateTime(1000 * strtotime('1981-01-01 00:00:00'))], + ['name' => 'Robert Roe', 'birthday' => new UTCDateTime(1000 * strtotime('1982-01-01 00:00:00'))], + ['name' => 'Mark Moe', 'birthday' => new UTCDateTime(1000 * strtotime('1983-01-01 00:00:00'))], ]); - $user = DB::collection('users')->where('birthday', new UTCDateTime(1000 * strtotime("1980-01-01 00:00:00")))->first(); + $user = DB::collection('users')->where('birthday', new UTCDateTime(1000 * strtotime('1980-01-01 00:00:00')))->first(); $this->assertEquals('John Doe', $user['name']); - $user = DB::collection('users')->where('birthday', '=', new DateTime("1980-01-01 00:00:00"))->first(); + $user = DB::collection('users')->where('birthday', '=', new DateTime('1980-01-01 00:00:00'))->first(); $this->assertEquals('John Doe', $user['name']); - $start = new UTCDateTime(1000 * strtotime("1981-01-01 00:00:00")); - $stop = new UTCDateTime(1000 * strtotime("1982-01-01 00:00:00")); + $start = new UTCDateTime(1000 * strtotime('1981-01-01 00:00:00')); + $stop = new UTCDateTime(1000 * strtotime('1982-01-01 00:00:00')); $users = DB::collection('users')->whereBetween('birthday', [$start, $stop])->get(); $this->assertEquals(2, count($users)); + + $dateTimeStart = new DateTime('1981-01-01 00:00:00'); + $dateTimeStop = new DateTime('1982-01-01 00:00:00'); + + $usersWithDateTimeFilter = DB::collection('users')->whereBetween('birthday', [$dateTimeStart, $dateTimeStop])->get(); + $this->assertEquals(2, count($usersWithDateTimeFilter)); } public function testOperators() @@ -549,11 +571,11 @@ public function testOperators() $results = DB::collection('items')->where('tags', 'size', 4)->get(); $this->assertEquals(1, count($results)); - $regex = new Regex(".*doe", "i"); + $regex = new Regex('.*doe', 'i'); $results = DB::collection('users')->where('name', 'regex', $regex)->get(); $this->assertEquals(2, count($results)); - $regex = new Regex(".*doe", "i"); + $regex = new Regex('.*doe', 'i'); $results = DB::collection('users')->where('name', 'regexp', $regex)->get(); $this->assertEquals(2, count($results)); diff --git a/tests/RelationsWithMongoIdTest.php b/tests/RelationsWithMongoIdTest.php new file mode 100644 index 000000000..b84d9792d --- /dev/null +++ b/tests/RelationsWithMongoIdTest.php @@ -0,0 +1,562 @@ + true]); + } + + public function tearDown() + { + Mockery::close(); + config(['database.connections.mongodb.use_mongo_id' => false]); + + User::truncate(); + Client::truncate(); + Address::truncate(); + Book::truncate(); + Item::truncate(); + Role::truncate(); + Client::truncate(); + Group::truncate(); + Photo::truncate(); + } + + public function testHasMany() + { + $author = User::create(['name' => 'George R. R. Martin']); + Book::create(['title' => 'A Game of Thrones', 'author_id' => $author->_id]); + Book::create(['title' => 'A Clash of Kings', 'author_id' => $author->_id]); + + $books = $author->books; + $this->assertEquals(2, count($books)); + + $user = User::create(['name' => 'John Doe']); + Item::create(['type' => 'knife', 'user_id' => $user->_id]); + Item::create(['type' => 'shield', 'user_id' => $user->_id]); + Item::create(['type' => 'sword', 'user_id' => $user->_id]); + Item::create(['type' => 'bag', 'user_id' => null]); + + $items = $user->items; + $this->assertEquals(3, count($items)); + } + + public function testBelongsTo() + { + $user = User::create(['name' => 'George R. R. Martin']); + Book::create(['title' => 'A Game of Thrones', 'author_id' => $user->_id]); + $book = Book::create(['title' => 'A Clash of Kings', 'author_id' => $user->_id]); + + $author = $book->author; + $this->assertEquals('George R. R. Martin', $author->name); + + $user = User::create(['name' => 'John Doe']); + $item = Item::create(['type' => 'sword', 'user_id' => $user->_id]); + + $owner = $item->user; + $this->assertEquals('John Doe', $owner->name); + + $book = Book::create(['title' => 'A Clash of Kings']); + $this->assertEquals(null, $book->author); + } + + public function testHasOne() + { + $user = User::create(['name' => 'John Doe']); + Role::create(['type' => 'admin', 'user_id' => $user->_id]); + + $role = $user->role; + $this->assertEquals('admin', $role->type); + $this->assertEquals($user->_id, $role->user_id); + + $user = User::create(['name' => 'Jane Doe']); + $role = new Role(['type' => 'user']); + $user->role()->save($role); + + $role = $user->role; + $this->assertEquals('user', $role->type); + $this->assertEquals($user->_id, $role->user_id); + + $user = User::where('name', 'Jane Doe')->first(); + $role = $user->role; + $this->assertEquals('user', $role->type); + $this->assertEquals($user->_id, $role->user_id); + } + + public function testWithBelongsTo() + { + $user = User::create(['name' => 'John Doe']); + Item::create(['type' => 'knife', 'user_id' => $user->_id]); + Item::create(['type' => 'shield', 'user_id' => $user->_id]); + Item::create(['type' => 'sword', 'user_id' => $user->_id]); + Item::create(['type' => 'bag', 'user_id' => null]); + + $items = Item::with('user')->orderBy('user_id', 'desc')->get(); + + $user = $items[0]->getRelation('user'); + $this->assertInstanceOf('User', $user); + $this->assertEquals('John Doe', $user->name); + $this->assertEquals(1, count($items[0]->getRelations())); + $this->assertEquals(null, $items[3]->getRelation('user')); + } + + public function testWithHashMany() + { + $user = User::create(['name' => 'John Doe']); + Item::create(['type' => 'knife', 'user_id' => $user->_id]); + Item::create(['type' => 'shield', 'user_id' => $user->_id]); + Item::create(['type' => 'sword', 'user_id' => $user->_id]); + Item::create(['type' => 'bag', 'user_id' => null]); + + $user = User::with('items')->find($user->_id); + + $items = $user->getRelation('items'); + $this->assertEquals(3, count($items)); + $this->assertInstanceOf('Item', $items[0]); + } + + public function testWithHasOne() + { + $user = User::create(['name' => 'John Doe']); + Role::create(['type' => 'admin', 'user_id' => $user->_id]); + Role::create(['type' => 'guest', 'user_id' => $user->_id]); + + $user = User::with('role')->find($user->_id); + + $role = $user->getRelation('role'); + $this->assertInstanceOf('Role', $role); + $this->assertEquals('admin', $role->type); + } + + public function testEasyRelation() + { + // Has Many + $user = User::create(['name' => 'John Doe']); + $item = Item::create(['type' => 'knife']); + $user->items()->save($item); + + $user = User::find($user->_id); + $items = $user->items; + $this->assertEquals(1, count($items)); + $this->assertInstanceOf('Item', $items[0]); + $this->assertEquals($user->_id, $items[0]->user_id); + + // Has one + $user = User::create(['name' => 'John Doe']); + $role = Role::create(['type' => 'admin']); + $user->role()->save($role); + + $user = User::find($user->_id); + $role = $user->role; + $this->assertInstanceOf('Role', $role); + $this->assertEquals('admin', $role->type); + $this->assertEquals($user->_id, $role->user_id); + } + + public function testBelongsToMany() + { + $user = User::create(['name' => 'John Doe']); + + // Add 2 clients + $user->clients()->save(new Client(['name' => 'Pork Pies Ltd.'])); + $user->clients()->create(['name' => 'Buffet Bar Inc.']); + + // Refetch + $user = User::with('clients')->find($user->_id); + $client = Client::with('users')->first(); + + // Check for relation attributes + $this->assertTrue(array_key_exists('user_ids', $client->getAttributes())); + $this->assertTrue(array_key_exists('client_ids', $user->getAttributes())); + + $clients = $user->getRelation('clients'); + $users = $client->getRelation('users'); + + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $users); + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $clients); + $this->assertInstanceOf('Client', $clients[0]); + $this->assertInstanceOf('User', $users[0]); + $this->assertCount(2, $user->clients); + $this->assertCount(1, $client->users); + + // Now create a new user to an existing client + $user = $client->users()->create(['name' => 'Jane Doe']); + + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $user->clients); + $this->assertInstanceOf('Client', $user->clients->first()); + $this->assertCount(1, $user->clients); + + // Get user and unattached client + $user = User::where('name', '=', 'Jane Doe')->first(); + $client = Client::Where('name', '=', 'Buffet Bar Inc.')->first(); + + // Check the models are what they should be + $this->assertInstanceOf('Client', $client); + $this->assertInstanceOf('User', $user); + + // Assert they are not attached + $this->assertFalse(in_array($client->_id, $user->client_ids)); + $this->assertFalse(in_array($user->_id, $client->user_ids)); + $this->assertCount(1, $user->clients); + $this->assertCount(1, $client->users); + + // Attach the client to the user + $user->clients()->attach($client); + + // Get the new user model + $user = User::where('name', '=', 'Jane Doe')->first(); + $client = Client::Where('name', '=', 'Buffet Bar Inc.')->first(); + + // Assert they are attached + $this->assertTrue(in_array($client->_id, $user->client_ids)); + $this->assertTrue(in_array($user->_id, $client->user_ids)); + $this->assertCount(2, $user->clients); + $this->assertCount(2, $client->users); + + // Detach clients from user + $user->clients()->sync([]); + + // Get the new user model + $user = User::where('name', '=', 'Jane Doe')->first(); + $client = Client::Where('name', '=', 'Buffet Bar Inc.')->first(); + + // Assert they are not attached + $this->assertFalse(in_array($client->_id, $user->client_ids)); + $this->assertFalse(in_array($user->_id, $client->user_ids)); + $this->assertCount(0, $user->clients); + $this->assertCount(1, $client->users); + } + + public function testBelongsToManyAttachesExistingModels() + { + $user = User::create(['name' => 'John Doe', 'client_ids' => ['1234523']]); + + $clients = [ + Client::create(['name' => 'Pork Pies Ltd.'])->_id, + Client::create(['name' => 'Buffet Bar Inc.'])->_id, + ]; + + $moreClients = [ + Client::create(['name' => 'synced Boloni Ltd.'])->_id, + Client::create(['name' => 'synced Meatballs Inc.'])->_id, + ]; + + // Sync multiple records + $user->clients()->sync($clients); + + $user = User::with('clients')->find($user->_id); + + // Assert non attached ID's are detached succesfully + $this->assertFalse(in_array('1234523', $user->client_ids)); + + // Assert there are two client objects in the relationship + $this->assertCount(2, $user->clients); + + // Add more clients + $user->clients()->sync($moreClients); + + // Refetch + $user = User::with('clients')->find($user->_id); + + // Assert there are now still 2 client objects in the relationship + $this->assertCount(2, $user->clients); + + // Assert that the new relationships name start with synced + $this->assertStringStartsWith('synced', $user->clients[0]->name); + $this->assertStringStartsWith('synced', $user->clients[1]->name); + } + + public function testBelongsToManySync() + { + // create test instances + $user = User::create(['name' => 'John Doe']); + $client1 = Client::create(['name' => 'Pork Pies Ltd.'])->_id; + $client2 = Client::create(['name' => 'Buffet Bar Inc.'])->_id; + + // Sync multiple + $user->clients()->sync([$client1, $client2]); + $this->assertCount(2, $user->clients); + + // Refresh user + $user = User::where('name', '=', 'John Doe')->first(); + + // Sync single + $user->clients()->sync([$client1]); + $this->assertCount(1, $user->clients); + } + + public function testBelongsToManyAttachArray() + { + $user = User::create(['name' => 'John Doe']); + $client1 = Client::create(['name' => 'Test 1'])->_id; + $client2 = Client::create(['name' => 'Test 2'])->_id; + + $user = User::where('name', '=', 'John Doe')->first(); + $user->clients()->attach([$client1, $client2]); + $this->assertCount(2, $user->clients); + } + + public function testBelongsToManySyncAlreadyPresent() + { + $user = User::create(['name' => 'John Doe']); + $client1 = Client::create(['name' => 'Test 1'])->_id; + $client2 = Client::create(['name' => 'Test 2'])->_id; + + $user->clients()->sync([$client1, $client2]); + $this->assertCount(2, $user->clients); + + $user = User::where('name', '=', 'John Doe')->first(); + $user->clients()->sync([$client1]); + $this->assertCount(1, $user->clients); + + $user = User::where('name', '=', 'John Doe')->first()->toArray(); + $this->assertCount(1, $user['client_ids']); + } + + public function testBelongsToManyCustom() + { + $user = User::create(['name' => 'John Doe']); + $group = $user->groups()->create(['name' => 'Admins']); + + // Refetch + $user = User::find($user->_id); + $group = Group::find($group->_id); + + // Check for custom relation attributes + $this->assertTrue(array_key_exists('users', $group->getAttributes())); + $this->assertTrue(array_key_exists('groups', $user->getAttributes())); + + // Assert they are attached + $this->assertTrue(in_array($group->_id, $user->groups->pluck('_id')->toArray())); + $this->assertTrue(in_array($user->_id, $group->users->pluck('_id')->toArray())); + $this->assertEquals($group->_id, $user->groups()->first()->_id); + $this->assertEquals($user->_id, $group->users()->first()->_id); + } + + public function testMorph() + { + $user = User::create(['name' => 'John Doe']); + $client = Client::create(['name' => 'Jane Doe']); + + $photo = Photo::create(['url' => 'http://graph.facebook.com/john.doe/picture']); + $photo = $user->photos()->save($photo); + + $this->assertEquals(1, $user->photos->count()); + $this->assertEquals($photo->id, $user->photos->first()->id); + + $user = User::find($user->_id); + $this->assertEquals(1, $user->photos->count()); + $this->assertEquals($photo->id, $user->photos->first()->id); + + $photo = Photo::create(['url' => 'http://graph.facebook.com/jane.doe/picture']); + $client->photo()->save($photo); + + $this->assertNotNull($client->photo); + $this->assertEquals($photo->id, $client->photo->id); + + $client = Client::find($client->_id); + $this->assertNotNull($client->photo); + $this->assertEquals($photo->id, $client->photo->id); + + $photo = Photo::first(); + $this->assertEquals($photo->imageable->name, $user->name); + + $user = User::with('photos')->find($user->_id); + $relations = $user->getRelations(); + $this->assertTrue(array_key_exists('photos', $relations)); + $this->assertEquals(1, $relations['photos']->count()); + + $photos = Photo::with('imageable')->get(); + $relations = $photos[0]->getRelations(); + $this->assertTrue(array_key_exists('imageable', $relations)); + $this->assertInstanceOf('User', $photos[0]->imageable); + + $relations = $photos[1]->getRelations(); + $this->assertTrue(array_key_exists('imageable', $relations)); + $this->assertInstanceOf('Client', $photos[1]->imageable); + } + + public function testHasManyHas() + { + $author1 = User::create(['name' => 'George R. R. Martin']); + $author1->books()->create(['title' => 'A Game of Thrones', 'rating' => 5]); + $author1->books()->create(['title' => 'A Clash of Kings', 'rating' => 5]); + $author2 = User::create(['name' => 'John Doe']); + $author2->books()->create(['title' => 'My book', 'rating' => 2]); + User::create(['name' => 'Anonymous author']); + Book::create(['title' => 'Anonymous book', 'rating' => 1]); + + $authors = User::has('books')->get(); + $this->assertCount(2, $authors); + $this->assertEquals('George R. R. Martin', $authors[0]->name); + $this->assertEquals('John Doe', $authors[1]->name); + + $authors = User::has('books', '>', 1)->get(); + $this->assertCount(1, $authors); + + $authors = User::has('books', '<', 5)->get(); + $this->assertCount(3, $authors); + + $authors = User::has('books', '>=', 2)->get(); + $this->assertCount(1, $authors); + + $authors = User::has('books', '<=', 1)->get(); + $this->assertCount(2, $authors); + + $authors = User::has('books', '=', 2)->get(); + $this->assertCount(1, $authors); + + $authors = User::has('books', '!=', 2)->get(); + $this->assertCount(2, $authors); + + $authors = User::has('books', '=', 0)->get(); + $this->assertCount(1, $authors); + + $authors = User::has('books', '!=', 0)->get(); + $this->assertCount(2, $authors); + + $authors = User::whereHas('books', function ($query) { + $query->where('rating', 5); + })->get(); + $this->assertCount(1, $authors); + + $authors = User::whereHas('books', function ($query) { + $query->where('rating', '<', 5); + })->get(); + $this->assertCount(1, $authors); + } + + public function testHasOneHas() + { + $user1 = User::create(['name' => 'John Doe']); + $user1->role()->create(['title' => 'admin']); + $user2 = User::create(['name' => 'Jane Doe']); + $user2->role()->create(['title' => 'reseller']); + User::create(['name' => 'Mark Moe']); + Role::create(['title' => 'Customer']); + + $users = User::has('role')->get(); + $this->assertCount(2, $users); + $this->assertEquals('John Doe', $users[0]->name); + $this->assertEquals('Jane Doe', $users[1]->name); + + $users = User::has('role', '=', 0)->get(); + $this->assertCount(1, $users); + + $users = User::has('role', '!=', 0)->get(); + $this->assertCount(2, $users); + } + + public function testNestedKeys() + { + $client = Client::create([ + 'data' => [ + 'client_id' => 35298, + 'name' => 'John Doe', + ], + ]); + + $address = $client->addresses()->create([ + 'data' => [ + 'address_id' => 1432, + 'city' => 'Paris', + ], + ]); + + $client = Client::where('data.client_id', 35298)->first(); + $this->assertEquals(1, $client->addresses->count()); + + $address = $client->addresses->first(); + $this->assertEquals('Paris', $address->data['city']); + + $client = Client::with('addresses')->first(); + $this->assertEquals('Paris', $client->addresses->first()->data['city']); + } + + public function testDoubleSaveOneToMany() + { + $author = User::create(['name' => 'George R. R. Martin']); + $book = Book::create(['title' => 'A Game of Thrones']); + + $author->books()->save($book); + $author->books()->save($book); + $author->save(); + $this->assertEquals(1, $author->books()->count()); + $this->assertEquals($author->_id, $book->author_id); + + $author = User::where('name', 'George R. R. Martin')->first(); + $book = Book::where('title', 'A Game of Thrones')->first(); + $this->assertEquals(1, $author->books()->count()); + $this->assertEquals($author->_id, $book->author_id); + + $author->books()->save($book); + $author->books()->save($book); + $author->save(); + $this->assertEquals(1, $author->books()->count()); + $this->assertEquals($author->_id, $book->author_id); + } + + public function testDoubleSaveManyToMany() + { + $user = User::create(['name' => 'John Doe']); + $client = Client::create(['name' => 'Admins']); + + $user->clients()->save($client); + $user->clients()->save($client); + $user->save(); + + $this->assertEquals(1, $user->clients()->count()); + $this->assertEquals([$user->_id], $client->user_ids); + $this->assertEquals([$client->_id], $user->client_ids); + + $user = User::where('name', 'John Doe')->first(); + $client = Client::where('name', 'Admins')->first(); + $this->assertEquals(1, $user->clients()->count()); + $this->assertEquals([$user->_id], $client->user_ids); + $this->assertEquals([$client->_id], $user->client_ids); + + $user->clients()->save($client); + $user->clients()->save($client); + $user->save(); + $this->assertEquals(1, $user->clients()->count()); + $this->assertEquals([$user->_id], $client->user_ids); + $this->assertEquals([$client->_id], $user->client_ids); + } + + public function testCastAttribute() + { + $user = new User(); + $user->setCasts([ + 'last_seen' => 'UTCDatetime', + 'age' => 'int', + 'name' => 'string', + 'rate' => 'float', + 'birthday' => 'timestamp', + 'isActive' => 'bool', + 'default' => 'default', + ], 'set'); + $user->setCasts([ + 'name' => 'string', + ]); + $carbon = Carbon\Carbon::now(); + $UTCDateTime = new \MongoDB\BSON\UTCDateTime($carbon->timestamp * 1000); + $test = $user->castAttribute('last_seen', $carbon, 'set'); + $this->assertEquals($UTCDateTime, $test); + $test = $user->castAttribute('last_seen', $UTCDateTime, 'set'); + $this->assertEquals($UTCDateTime, $test); + $test = $user->castAttribute('age', '1', 'set'); + $this->assertEquals(1, $test); + $test = $user->castAttribute('name', 40000, 'set'); + $this->assertEquals('40000', $test); + $test = $user->castAttribute('rate', '3.14', 'set'); + $this->assertEquals(3.14, $test); + $test = $user->castAttribute('birthday', $carbon, 'set'); + $this->assertEquals($carbon->timestamp, $test); + $test = $user->castAttribute('isActive', 1, 'set'); + $this->assertEquals(true, $test); + $test = $user->castAttribute('default', 'test', 'set'); + $this->assertEquals('test', $test); + } +} diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 6f7b0f0e2..bb81ab8c3 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -19,7 +19,7 @@ public function testCreateWithCallback() $instance = $this; Schema::create('newcollection', function ($collection) use ($instance) { - $instance->assertInstanceOf('Jenssegers\Mongodb\Schema\Blueprint', $collection); + $instance->assertInstanceOf('Moloquent\Schema\Blueprint', $collection); }); $this->assertTrue(Schema::hasCollection('newcollection')); @@ -37,11 +37,11 @@ public function testBluePrint() $instance = $this; Schema::collection('newcollection', function ($collection) use ($instance) { - $instance->assertInstanceOf('Jenssegers\Mongodb\Schema\Blueprint', $collection); + $instance->assertInstanceOf('Moloquent\Schema\Blueprint', $collection); }); Schema::table('newcollection', function ($collection) use ($instance) { - $instance->assertInstanceOf('Jenssegers\Mongodb\Schema\Blueprint', $collection); + $instance->assertInstanceOf('Moloquent\Schema\Blueprint', $collection); }); } @@ -178,7 +178,7 @@ public function testDummies() public function testSparseUnique() { Schema::collection('newcollection', function ($collection) { - $collection->sparse_and_unique('sparseuniquekey'); + $collection->sparse_and_unique('sparseuniquekey'); }); $index = $this->getIndex('newcollection', 'sparseuniquekey'); diff --git a/tests/SeederTest.php b/tests/SeederTest.php index 9581df3d3..934c892a2 100644 --- a/tests/SeederTest.php +++ b/tests/SeederTest.php @@ -9,7 +9,7 @@ public function tearDown() public function testSeed() { - $seeder = new UserTableSeeder; + $seeder = new UserTableSeeder(); $seeder->run(); $user = User::where('name', 'John Doe')->first(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 63552189f..25819b6c0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,7 +5,7 @@ class TestCase extends Orchestra\Testbench\TestCase /** * Get application providers. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app * * @return array */ @@ -21,21 +21,25 @@ protected function getApplicationProviders($app) /** * Get package providers. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app + * * @return array */ protected function getPackageProviders($app) { return [ - Jenssegers\Mongodb\MongodbServiceProvider::class, - Jenssegers\Mongodb\Auth\PasswordResetServiceProvider::class, + Moloquent\MongodbServiceProvider::class, + Moloquent\Auth\PasswordResetServiceProvider::class, + Moloquent\Passport\PassportServiceProvider::class, + Laravel\Passport\PassportServiceProvider::class, ]; } /** * Define environment setup. * - * @param Illuminate\Foundation\Application $app + * @param Illuminate\Foundation\Application $app + * * @return void */ protected function getEnvironmentSetUp($app) diff --git a/tests/config/database.php b/tests/config/database.php index 4f70869c9..e15a7a02d 100644 --- a/tests/config/database.php +++ b/tests/config/database.php @@ -5,10 +5,10 @@ 'connections' => [ 'mongodb' => [ - 'name' => 'mongodb', - 'driver' => 'mongodb', - 'host' => '127.0.0.1', - 'database' => 'unittest', + 'name' => 'mongodb', + 'driver' => 'mongodb', + 'host' => '127.0.0.1', + 'database' => 'unittest', ], 'mysql' => [ diff --git a/tests/models/Address.php b/tests/models/Address.php index 43adff20a..47ace3a0a 100644 --- a/tests/models/Address.php +++ b/tests/models/Address.php @@ -1,6 +1,6 @@