MongoYii2
This is a set of tools and add-ons built on top of Yii2's own MongoDB extension to further it's capacity and abilities
because I found the default MongoDB extension lacked a lot of features needed in programs I made using MongoDB., (*1)
Features
The formatter, which replaces Yii2's own, provides some standard representations of objects, especially MongoDate
s., (*2)
To use it simply add:, (*3)
'formatter' => ['class' => 'sammaye\mongoyii\Formatter']
to the components
section in your configuration file, for example, in common/config/main.php
., (*4)
After you have added it you can define certain fields as data types. Let's take an example of defining a GridView
's columns
property:, (*5)
'columns' => [
'_id',
'url',
[
'attribute' => 'date',
'format' => 'date'
],
'inc_id',
[
'attribute' => 'updated_at',
'format' => 'date'
],
[
'attribute' => 'created_at',
'format' => 'date'
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{update} {delete}',
'urlCreator' => function($action, $model, $key, $index){
$params = is_array($key) ? $key : ['id' => (string) $key];
$params[0] = 'comic-strip' . '/' . $action;
return Url::toRoute($params);
}
]
]
The date
formats will use the formatter class you included for this extension to ensure that the correct output is used., (*6)
Validators
The most common way to include the validators is to include sammaye\mongoyii\ActiveRecord
instead of yii\mongodb\ActiveRecord
within your models., (*7)
You can also use all validators directly by calling the class, for example:, (*8)
new sammaye\mongoyii\NumberValidator()
or:, (*9)
['field', 'sammaye\mongoyii\NumberValidator'],
The main change in the validators is that they actually change the model's value., (*10)
This behaviour is because MongoDB is not type aware within it's own server environment unlike technologies like SQL where you can
shove a string
into an int
field and it will automatically and magically convert. To complicate things MongoDB IS type aware in it's querying
and you can only query by the exact same types as what is in the document., (*11)
So most validators are based upon the principle of making it easy to format (as well as validate) the return ready for input into MongoDB., (*12)
Validator Map
MongoYii2 contains it's own validator map when you include the ActiveRecord
. This is, currently, what the extra validators look like:, (*13)
public static $builtInValidators = [
'id' => 'sammaye\mongoyii\validators\MongoIdValidator',
'date' => 'sammaye\mongoyii\validators\MongoDateValidator',
'in' => 'sammaye\mongoyii\validators\RangeValidator',
'inInt' => [
'class' => 'sammaye\mongoyii\validators\RangeValidator',
'format' => 'int'
],
'integer' => [
'class' => 'sammaye\mongoyii\validators\NumberValidator',
'integerOnly' => true,
'format' => 'int'
],
'float' => [
'class' => 'sammaye\mongoyii\validators\NumberValidator',
'format' => 'float'
],
'array' => 'sammaye\mongoyii\validators\ArrayValidator',
'number' => 'sammaye\mongoyii\validators\NumberValidator',
];
Array Validator
This is designed to allow you to validate very basic subdocuments, simple 1-dimensional arrays. You can use it like:, (*14)
[
'array_field',
'array',
'max' => 10,
'rules' =>
[
['$', 'intInt', 'range' => array_keys($this->rangeVals())],
]
],
Essentially there are 4 properties you need to know about for this validator:, (*15)
-
min
which defines a minimum number of elements within the array
-
max
which does the opposite
-
required
which defines a required field
-
rules
which defines the rules for the array.
All defined rules have the same structure:, (*16)
['$', rule_name, params],
The $
is always used to denote all elements of the array, currently you cannot target elements directly, for example:, (*17)
[1, rule_name, params],
This validator will currently batch all errors for the field the same as Yii2 standard does for a single field., (*18)
MongoDate Validator
Does exactly the same as Yii2's own DateValidator
except it can also cast to a MongoDate
object if the cast
property is true
, which it is by default., (*19)
MongoId Validator
This checks to see if the value is a MongoId and tries to cast it if you have not set the cast
property to false
., (*20)
NumberValidator
Checks if value is a number and will format it if you fill in format
property. It can take an anon function for the format
property or int
, float
, string
or nothing.
Nothing will result in nothing being done to the value. Example model rules
entry:, (*21)
['int_field', 'Number', 'integerOnly' => true, 'format' => 'int']
This validator can also be called by:, (*22)
These are used as shortcuts for calling the NumberValidator
with format
filled in as either int
or float
., (*23)
RangeValidator
Checks if all values are in a range of other values and will format the range if you fill in the format
property.
It can take an anon function for the format
property or int
, float
, string
or nothing. Nothing will result in nothing being done to the value., (*24)
This validator can also be called by inInt
which is a shortcut for calling ['intRange', 'in', 'range' => [1,2,3], 'format' => 'int']
., (*25)
Active Query Changes
The active query now has the ability to not only stream, via each()
, but also get the raw cursor back:, (*26)
User::find()->where(['cheese' => 'cheddar'])->raw()->info(); // gets cursor info
each()
will get the cursor and, one by one, instantiate the rows as models, using the cursors own batch methods to pull from MongoDB in your configured batchSize()
(normally 100 rows at a time) instead of using the all()
or nothing method., (*27)
This is especially helpful for large scripts where you could easily run out of memory., (*28)
Indexer
The indexer allows you to add indexes to MongoDB from your models., (*29)
Currently it will not manage the indexes for you, dropping of indexes is either all or none in the script and you need to use the mongo
console to do it individually., (*30)
To use it simply copy it to your console controller folder, whether it be console/controllers
or commands
and edit the first line of the file to change it's namespace and
then simply run it: php ./yii index/build
., (*31)
It will tell you what it is doing and what indexes it adds. It will search through your models folder and search for any models with the indexes()
function. Upon reading that
function's return it will make indexes for that model., (*32)
A good example of a model's indexes
function is:, (*33)
public function indexes()
{
return [
// db.c.ensureIndex({email: 1,status: 1})
['email' => 1, 'status' => 1],
// db.c.ensureIndex({email: 1}, {unique: true, sparse: true})
[['email' => 1], ['unique' => true, 'sparse' => true]],
// db.c.ensureIndex({username: 1}, {unique: true, sparse: true})
[['username' => 1], ['unique' => true, 'sparse' => true]],
// db.c.ensureIndex({status: 1})
['status' => 1],
// db.c.ensureIndex({password_reset_token: 1,status: 1})
['password_reset_token' => 1, 'status' => 1],
// db.c.ensureIndex({_id: 1,status: 1})
['_id' => 1, 'status' => 1],
// db.c.ensureIndex({facebook_id: -1})
['facebook_id' => -1],
// db.c.ensureIndex({google_id: -1})
['google_id' => -1],
];
}
Mongo Helper
Contains some helpful functions which I just had to share:, (*34)
- Geo coordinates function so you can get distances within your PHP code too
- MongoDB
DATE()
function to get dates from MongoDate
objects
What's Still Not Working?
Object subdocuments do not work still. So a document of the structure:, (*35)
{
somefield: {
{d: 1, e: 2},
...
}
}
will not work automatically with this extension., (*36)
I thought about it long and hard but everything I came up with kept needing changes so it could be used differently for each scenario and case I had., (*37)
In the end I just ditched what I had and moved on. I added some thoughts on this thread if someone wants to take it up., (*38)
Apart from that most of this extension is about making stuff work., (*39)