$acl->addRole(new Phalcon\Acl\Role('administrator'), 'consultor');
- * $acl->addRole('administrator', 'consultor');
+ *
+ * $acl->addRole(new Phalcon\Acl\Role('administrator'), 'consultor');
+ * $acl->addRole('administrator', 'consultor');
+ *
*
* @param \Phalcon\Acl\Role|string $role
* @param string $accessInherits
@@ -342,8 +350,9 @@ public function dropResourceAccess($resourceName, $accessList)
* @param string $roleName
* @param string $resourceName
* @param array|string $access
+ * @param mixed $func
*/
- public function allow($roleName, $resourceName, $access)
+ public function allow($roleName, $resourceName, $access, $func = null)
{
$this->allowOrDeny($roleName, $resourceName, $access, Acl::ALLOW);
}
@@ -366,9 +375,10 @@ public function allow($roleName, $resourceName, $access)
* @param string $roleName
* @param string $resourceName
* @param array|string $access
+ * @param mixed $func
* @return boolean
*/
- public function deny($roleName, $resourceName, $access)
+ public function deny($roleName, $resourceName, $access, $func = null)
{
$this->allowOrDeny($roleName, $resourceName, $access, Acl::DENY);
}
@@ -386,10 +396,10 @@ public function deny($roleName, $resourceName, $access)
* @param string $role
* @param string $resource
* @param string $access
- *
+ * @param array $parameters
* @return bool
*/
- public function isAllowed($role, $resource, $access)
+ public function isAllowed($role, $resource, $access, array $parameters = null)
{
$sql = implode(' ', [
"SELECT " . $this->connection->escapeIdentifier('allowed') . " FROM {$this->accessList} AS a",
@@ -425,6 +435,28 @@ public function isAllowed($role, $resource, $access)
return $this->_defaultAccess;
}
+ /**
+ * Returns the default ACL access level for no arguments provided
+ * in isAllowed action if there exists func for accessKey
+ *
+ * @return int
+ */
+ public function getNoArgumentsDefaultAction()
+ {
+ return $this->noArgumentsDefaultAction;
+ }
+
+ /**
+ * Sets the default access level for no arguments provided
+ * in isAllowed action if there exists func for accessKey
+ *
+ * @param int $defaultAccess Phalcon\Acl::ALLOW or Phalcon\Acl::DENY
+ */
+ public function setNoArgumentsDefaultAction($defaultAccess)
+ {
+ $this->noArgumentsDefaultAction = intval($defaultAccess);
+ }
+
/**
* Inserts/Updates a permission in the access list
*
diff --git a/Library/Phalcon/Acl/Adapter/Mongo.php b/Library/Phalcon/Acl/Adapter/Mongo.php
index d95785b28..988edf095 100644
--- a/Library/Phalcon/Acl/Adapter/Mongo.php
+++ b/Library/Phalcon/Acl/Adapter/Mongo.php
@@ -37,6 +37,12 @@ class Mongo extends Adapter
*/
protected $options;
+ /**
+ * Default action for no arguments is allow
+ * @var int
+ */
+ protected $noArgumentsDefaultAction = Acl::ALLOW;
+
/**
* Class constructor.
*
@@ -161,8 +167,8 @@ public function isResource($resourceName)
* $acl->addResource(new Phalcon\Acl\Resource('customers'), 'search');
* $acl->addResource('customers', 'search');
* //Add a resource with an access list
- * $acl->addResource(new Phalcon\Acl\Resource('customers'), array('create', 'search'));
- * $acl->addResource('customers', array('create', 'search'));
+ * $acl->addResource(new Phalcon\Acl\Resource('customers'), ['create', 'search']);
+ * $acl->addResource('customers', ['create', 'search']);
*
*
* @param \Phalcon\Acl\Resource $resource
@@ -280,7 +286,7 @@ public function dropResourceAccess($resourceName, $accessList)
* //Allow access to guests to search on customers
* $acl->allow('guests', 'customers', 'search');
* //Allow access to guests to search or create on customers
- * $acl->allow('guests', 'customers', array('search', 'create'));
+ * $acl->allow('guests', 'customers', ['search', 'create']);
* //Allow access to any role to browse on products
* $acl->allow('*', 'products', 'browse');
* //Allow access to any role to browse on any resource
@@ -290,8 +296,9 @@ public function dropResourceAccess($resourceName, $accessList)
* @param string $roleName
* @param string $resourceName
* @param mixed $access
+ * @param mixed $func
*/
- public function allow($roleName, $resourceName, $access)
+ public function allow($roleName, $resourceName, $access, $func = null)
{
$this->allowOrDeny($roleName, $resourceName, $access, Acl::ALLOW);
}
@@ -305,7 +312,7 @@ public function allow($roleName, $resourceName, $access)
* //Deny access to guests to search on customers
* $acl->deny('guests', 'customers', 'search');
* //Deny access to guests to search or create on customers
- * $acl->deny('guests', 'customers', array('search', 'create'));
+ * $acl->deny('guests', 'customers', ['search', 'create']);
* //Deny access to any role to browse on products
* $acl->deny('*', 'products', 'browse');
* //Deny access to any role to browse on any resource
@@ -315,9 +322,10 @@ public function allow($roleName, $resourceName, $access)
* @param string $roleName
* @param string $resourceName
* @param mixed $access
+ * @param mixed $func
* @return boolean
*/
- public function deny($roleName, $resourceName, $access)
+ public function deny($roleName, $resourceName, $access, $func = null)
{
$this->allowOrDeny($roleName, $resourceName, $access, Acl::DENY);
}
@@ -336,9 +344,10 @@ public function deny($roleName, $resourceName, $access)
* @param string $role
* @param string $resource
* @param string $access
+ * @param array $parameters
* @return boolean
*/
- public function isAllowed($role, $resource, $access)
+ public function isAllowed($role, $resource, $access, array $parameters = null)
{
$accessList = $this->getCollection('accessList');
$access = $accessList->findOne([
@@ -367,6 +376,28 @@ public function isAllowed($role, $resource, $access)
return $this->_defaultAccess;
}
+ /**
+ * Returns the default ACL access level for no arguments provided
+ * in isAllowed action if there exists func for accessKey
+ *
+ * @return int
+ */
+ public function getNoArgumentsDefaultAction()
+ {
+ return $this->noArgumentsDefaultAction;
+ }
+
+ /**
+ * Sets the default access level for no arguments provided
+ * in isAllowed action if there exists func for accessKey
+ *
+ * @param int $defaultAccess Phalcon\Acl::ALLOW or Phalcon\Acl::DENY
+ */
+ public function setNoArgumentsDefaultAction($defaultAccess)
+ {
+ $this->noArgumentsDefaultAction = intval($defaultAccess);
+ }
+
/**
* Returns a mongo collection
*
diff --git a/Library/Phalcon/Acl/Adapter/README.md b/Library/Phalcon/Acl/Adapter/README.md
index ba4408cc8..2f2559f0c 100644
--- a/Library/Phalcon/Acl/Adapter/README.md
+++ b/Library/Phalcon/Acl/Adapter/README.md
@@ -74,7 +74,7 @@ $acl->setDefaultAction(Phalcon\Acl::DENY);
$acl->addRole(new Phalcon\Acl\Role('Admins'));
// Create the resource with its accesses
-$acl->addResource('Products', array('insert', 'update', 'delete'));
+$acl->addResource('Products', ['insert', 'update', 'delete']);
// Allow Admins to insert products
$acl->allow('Admin', 'Products', 'insert');
diff --git a/Library/Phalcon/Acl/Adapter/Redis.php b/Library/Phalcon/Acl/Adapter/Redis.php
index e528df1e3..d16e1c4b6 100644
--- a/Library/Phalcon/Acl/Adapter/Redis.php
+++ b/Library/Phalcon/Acl/Adapter/Redis.php
@@ -33,12 +33,22 @@
*/
class Redis extends Adapter
{
- /** @var bool */
+ /**
+ * @var bool
+ */
protected $setNXAccess = true;
- /** @var \Redis */
+ /**
+ * @var \Redis
+ */
protected $redis;
+ /**
+ * Default action for no arguments is allow
+ * @var int
+ */
+ protected $noArgumentsDefaultAction = Acl::ALLOW;
+
public function __construct($redis = null)
{
$this->redis = $redis;
@@ -91,7 +101,7 @@ public function addRole($role, $accessInherits = null)
* {@inheritdoc}
*
* @param string $roleName
- * @param string $roleToInherit
+ * @param \Phalcon\Acl\Role|string $roleToInherit
* @throws \Phalcon\Acl\Exception
*/
public function addInherit($roleName, $roleToInherit)
@@ -104,6 +114,10 @@ public function addInherit($roleName, $roleToInherit)
);
}
+ if ($roleToInherit instanceof Role) {
+ $roleToInherit = $roleToInherit->getName();
+ }
+
$this->redis->sAdd("rolesInherits:$roleName", $roleToInherit);
}
@@ -115,8 +129,8 @@ public function addInherit($roleName, $roleToInherit)
* $acl->addResource(new Phalcon\Acl\Resource('customers'), 'search');
* $acl->addResource('customers', 'search');
* //Add a resource with an access list
- * $acl->addResource(new Phalcon\Acl\Resource('customers'), array('create', 'search'));
- * $acl->addResource('customers', array('create', 'search'));
+ * $acl->addResource(new Phalcon\Acl\Resource('customers'), ['create', 'search']);
+ * $acl->addResource('customers', ['create', 'search']);
*
*
* @param \Phalcon\Acl\Resource|string $resource
@@ -128,7 +142,7 @@ public function addResource($resource, $accessList = null)
if (!is_object($resource)) {
$resource = new Resource($resource, ucwords($resource) . " Resource");
}
- $this->redis->hMset("resources", array($resource->getName() => $resource->getDescription()));
+ $this->redis->hMset("resources", [$resource->getName() => $resource->getDescription()]);
if ($accessList) {
return $this->addResourceAccess($resource->getName(), $accessList);
@@ -193,7 +207,7 @@ public function isResourceAccess($resource, $access)
*/
public function getResources()
{
- $resources = array();
+ $resources = [];
foreach ($this->redis->hGetAll("resources") as $name => $desc) {
$resources[] = new Resource($name, $desc);
@@ -209,7 +223,7 @@ public function getResources()
*/
public function getRoles()
{
- $roles = array();
+ $roles = [];
foreach ($this->redis->hGetAll("roles") as $name => $desc) {
$roles[] = new Role($name, $desc);
@@ -244,7 +258,7 @@ public function dropResourceAccess($resource, $accessList)
$accessList = (array)$accessList;
}
array_unshift($accessList, "resourcesAccesses:$resource");
- call_user_func_array(array($this->redis, 'sRem'), $accessList);
+ call_user_func_array([$this->redis, 'sRem'], $accessList);
}
/**
@@ -255,7 +269,7 @@ public function dropResourceAccess($resource, $accessList)
* //Allow access to guests to search on customers
* $acl->allow('guests', 'customers', 'search');
* //Allow access to guests to search or create on customers
- * $acl->allow('guests', 'customers', array('search', 'create'));
+ * $acl->allow('guests', 'customers', ['search', 'create']);
* //Allow access to any role to browse on products
* $acl->allow('*', 'products', 'browse');
* //Allow access to any role to browse on any resource
@@ -265,8 +279,9 @@ public function dropResourceAccess($resource, $accessList)
* @param string $role
* @param string $resource
* @param array|string $access
+ * @param mixed $func
*/
- public function allow($role, $resource, $access)
+ public function allow($role, $resource, $access, $func = null)
{
if ($role !== '*' && $resource !== '*') {
$this->allowOrDeny($role, $resource, $access, Acl::ALLOW);
@@ -321,7 +336,7 @@ protected function rolePermission($resource, $access, $allowOrDeny)
* //Deny access to guests to search on customers
* $acl->deny('guests', 'customers', 'search');
* //Deny access to guests to search or create on customers
- * $acl->deny('guests', 'customers', array('search', 'create'));
+ * $acl->deny('guests', 'customers', ['search', 'create']);
* //Deny access to any role to browse on products
* $acl->deny('*', 'products', 'browse');
* //Deny access to any role to browse on any resource
@@ -331,9 +346,10 @@ protected function rolePermission($resource, $access, $allowOrDeny)
* @param string $roleName
* @param string $resourceName
* @param array|string $access
+ * @param mixed $func
* @return boolean
*/
- public function deny($role, $resource, $access)
+ public function deny($role, $resource, $access, $func = null)
{
if ($role === '*' || empty($role)) {
$this->rolePermission($resource, $access, Acl::DENY);
@@ -357,10 +373,10 @@ public function deny($role, $resource, $access)
* @param string $role
* @param string $resource
* @param string $access
- *
+ * @param array $parameters
* @return bool
*/
- public function isAllowed($role, $resource, $access)
+ public function isAllowed($role, $resource, $access, array $parameters = null)
{
if ($this->redis->sIsMember("accessList:$role:$resource:" . Acl::ALLOW, $access)) {
return Acl::ALLOW;
@@ -382,6 +398,28 @@ public function isAllowed($role, $resource, $access)
return $this->getDefaultAction();
}
+ /**
+ * Returns the default ACL access level for no arguments provided
+ * in isAllowed action if there exists func for accessKey
+ *
+ * @return int
+ */
+ public function getNoArgumentsDefaultAction()
+ {
+ return $this->noArgumentsDefaultAction;
+ }
+
+ /**
+ * Sets the default access level for no arguments provided
+ * in isAllowed action if there exists func for accessKey
+ *
+ * @param int $defaultAccess Phalcon\Acl::ALLOW or Phalcon\Acl::DENY
+ */
+ public function setNoArgumentsDefaultAction($defaultAccess)
+ {
+ $this->noArgumentsDefaultAction = intval($defaultAccess);
+ }
+
/**
* @param $roleName
* @param $resourceName
@@ -407,7 +445,7 @@ protected function setAccess($roleName, $resourceName, $accessName, $action)
$accessList = "accessList:$roleName:$resourceName";
// remove first if exists
- foreach (array(1, 2) as $act) {
+ foreach ([1, 2] as $act) {
$this->redis->sRem("$accessList:$act", $accessName);
$this->redis->sRem("$accessList:$act", "*");
}
diff --git a/Library/Phalcon/Annotations/Adapter/Aerospike.php b/Library/Phalcon/Annotations/Adapter/Aerospike.php
new file mode 100644
index 000000000..a8cdddccc
--- /dev/null
+++ b/Library/Phalcon/Annotations/Adapter/Aerospike.php
@@ -0,0 +1,137 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Adapter;
+
+use Phalcon\Annotations\Exception;
+use Phalcon\Cache\Frontend\Data as FrontendData;
+use Phalcon\Cache\Backend\Aerospike as BackendAerospike;
+
+/**
+ * Class Aerospike
+ *
+ * Stores the parsed annotations to the Aerospike database.
+ * This adapter is suitable for production.
+ *
+ *
+ * use Phalcon\Annotations\Adapter\Aerospike;
+ *
+ * $annotations = new Aerospike([
+ * 'hosts' => [
+ * ['addr' => '127.0.0.1', 'port' => 3000]
+ * ],
+ * 'persistent' => true,
+ * 'namespace' => 'test',
+ * 'prefix' => 'annotations_',
+ * 'lifetime' => 8600,
+ * 'options' => [
+ * \Aerospike::OPT_CONNECT_TIMEOUT => 1250,
+ * \Aerospike::OPT_WRITE_TIMEOUT => 1500
+ * ]
+ * ]);
+ *
+ *
+ * @package Phalcon\Annotations\Adapter
+ */
+class Aerospike extends Base
+{
+ /**
+ * @var BackendAerospike
+ */
+ protected $aerospike;
+
+ /**
+ * Default Aerospike namespace
+ * @var string
+ */
+ protected $namespace = 'test';
+
+ /**
+ * The Aerospike Set for store sessions
+ * @var string
+ */
+ protected $set = 'annotations';
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param array $options Options array
+ * @throws Exception
+ */
+ public function __construct(array $options = [])
+ {
+ if (!isset($options['hosts']) ||
+ !is_array($options['hosts']) ||
+ !isset($options['hosts'][0]) ||
+ !is_array($options['hosts'][0])
+ ) {
+ throw new Exception('No hosts given in options');
+ }
+
+ if (isset($options['namespace'])) {
+ $this->namespace = $options['namespace'];
+ }
+
+ if (isset($options['set']) && !empty($options['set'])) {
+ $this->set = $options['set'];
+ }
+
+ if (!isset($options['persistent'])) {
+ $options['persistent'] = false;
+ }
+
+ if (!isset($options['options']) || !is_array($options['options'])) {
+ $options['options'] = [];
+ }
+
+ parent::__construct($options);
+
+ $this->aerospike = new BackendAerospike(
+ new FrontendData(['lifetime' => $this->options['lifetime']]),
+ [
+ 'hosts' => $this->options['hosts'],
+ 'namespace' => $this->namespace,
+ 'set' => $this->set,
+ 'prefix' => $this->options['lifetime'],
+ 'persistent' => (bool) $this->options['persistent'],
+ 'options' => $this->options['options'],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return BackendAerospike
+ */
+ protected function getCacheBackend()
+ {
+ return $this->aerospike;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $key
+ * @return string
+ */
+ protected function prepareKey($key)
+ {
+ return strval($key);
+ }
+}
diff --git a/Library/Phalcon/Annotations/Adapter/Base.php b/Library/Phalcon/Annotations/Adapter/Base.php
index 394ffb28f..086fe841c 100644
--- a/Library/Phalcon/Annotations/Adapter/Base.php
+++ b/Library/Phalcon/Annotations/Adapter/Base.php
@@ -22,7 +22,8 @@
/**
* \Phalcon\Annotations\Adapter\Base
- * Base class for \Phalcon\Annotations\Adapter\Memcached and other adapters.
+ *
+ * Base class for annotations adapters.
*
* @package Phalcon\Annotations\Adapter
*/
@@ -111,8 +112,7 @@ abstract protected function prepareKey($key);
/**
* Returns cache backend instance.
*
- * @abstract
- * @@implements \Phalcon\Cache\BackendInterface
+ * @return \Phalcon\Cache\BackendInterface
*/
abstract protected function getCacheBackend();
}
diff --git a/Library/Phalcon/Annotations/Adapter/Memcached.php b/Library/Phalcon/Annotations/Adapter/Memcached.php
index cb2adef60..234488c26 100644
--- a/Library/Phalcon/Annotations/Adapter/Memcached.php
+++ b/Library/Phalcon/Annotations/Adapter/Memcached.php
@@ -20,7 +20,7 @@
use Phalcon\Cache\Backend\Libmemcached as CacheBackend;
use Phalcon\Cache\Frontend\Data as CacheFrontend;
-use Phalcon\Mvc\Model\Exception;
+use Phalcon\Annotations\Exception;
use Memcached as MemcachedGeneric;
use Phalcon\Annotations\Adapter;
@@ -31,7 +31,15 @@
* This adapter is suitable for production.
*
*
- * $annotations = new \Phalcon\Annotations\Adapter\Memcached();
+ * use Phalcon\Annotations\Adapter\Memcached;
+ *
+ * $annotations = new Memcached([
+ * 'lifetime' => 8600,
+ * 'host' => 'localhost',
+ * 'port' => 11211,
+ * 'weight' => 1,
+ * 'prefix' => 'prefix.',
+ * ]);
*
*
* @package Phalcon\Annotations\Adapter
@@ -55,23 +63,19 @@ class Memcached extends Base
/**
* Memcached backend instance.
*
- * @var \Phalcon\Cache\Backend\Libmemcached
+ * @var CacheBackend
*/
protected $memcached = null;
/**
* {@inheritdoc}
*
- * @param null|array $options options array
+ * @param array $options options array
*
- * @throws \Phalcon\Mvc\Model\Exception
+ * @throws Exception
*/
- public function __construct($options = null)
+ public function __construct(array $options)
{
- if (!is_array($options)) {
- throw new Exception('No configuration given');
- }
-
if (!isset($options['host'])) {
throw new Exception('No host given in options');
}
@@ -89,26 +93,27 @@ public function __construct($options = null)
/**
* {@inheritdoc}
- * @return \Phalcon\Cache\Backend\Libmemcached
+ *
+ * @return CacheBackend
*/
protected function getCacheBackend()
{
if (null === $this->memcached) {
$this->memcached = new CacheBackend(
- new CacheFrontend(array('lifetime' => $this->options['lifetime'])),
- array(
- 'servers' => array(
- array(
+ new CacheFrontend(['lifetime' => $this->options['lifetime']]),
+ [
+ 'servers' => [
+ [
'host' => $this->options['host'],
'port' => $this->options['port'],
'weight' => $this->options['weight']
- ),
- ),
- 'client' => array(
+ ],
+ ],
+ 'client' => [
MemcachedGeneric::OPT_HASH => MemcachedGeneric::HASH_MD5,
MemcachedGeneric::OPT_PREFIX_KEY => $this->options['prefix']
- )
- )
+ ]
+ ]
);
}
@@ -120,7 +125,6 @@ protected function getCacheBackend()
* {@inheritdoc}
*
* @param string $key
- *
* @return string
*/
protected function prepareKey($key)
diff --git a/Library/Phalcon/Annotations/Adapter/README.md b/Library/Phalcon/Annotations/Adapter/README.md
index db541e745..1f33f0a9f 100644
--- a/Library/Phalcon/Annotations/Adapter/README.md
+++ b/Library/Phalcon/Annotations/Adapter/README.md
@@ -4,17 +4,62 @@ Usage examples of the adapters available here:
## Memcached
-This adapter uses a Libmemcached backend to store the cached content:
+Stores the parsed annotations to Memcached.
+This adapter uses a `Phalcon\Cache\Backend\Libmemcached` backend to store the cached content:
```php
-$di->set('annotations', function ()
-{
- return new \Phalcon\Annotations\Adapter\Memcached(array(
+use Phalcon\Annotations\Adapter\Memcached;
+
+$di->set('annotations', function () {
+ return new Memcached([
'lifetime' => 8600,
'host' => 'localhost',
'port' => 11211,
'weight' => 1,
'prefix' => 'prefix.',
- ));
+ ]);
+});
+```
+
+## Redis
+
+Stores the parsed annotations to Redis.
+This adapter uses a `Phalcon\Cache\Backend\Redis` backend to store the cached content:
+
+```php
+use Phalcon\Annotations\Adapter\Redis;
+
+$di->set('annotations', function () {
+ return new Redis([
+ 'lifetime' => 8600,
+ 'host' => 'localhost',
+ 'port' => 6379,
+ 'prefix' => 'annotations_',
+ ]);
+});
+```
+
+## Aerospike
+
+Stores the parsed annotations to the Aerospike database.
+This adapter uses a `Phalcon\Cache\Backend\Aerospike` backend to store the cached content:
+
+```php
+use Phalcon\Annotations\Adapter\Aerospike;
+
+$di->set('annotations', function () {
+ return new Aerospike([
+ 'hosts' => [
+ ['addr' => '127.0.0.1', 'port' => 3000]
+ ],
+ 'persistent' => true,
+ 'namespace' => 'test',
+ 'prefix' => 'annotations_',
+ 'lifetime' => 8600,
+ 'options' => [
+ \Aerospike::OPT_CONNECT_TIMEOUT => 1250,
+ \Aerospike::OPT_WRITE_TIMEOUT => 1500
+ ]
+ ]);
});
-```
\ No newline at end of file
+```
diff --git a/Library/Phalcon/Annotations/Adapter/Redis.php b/Library/Phalcon/Annotations/Adapter/Redis.php
new file mode 100644
index 000000000..860221ce4
--- /dev/null
+++ b/Library/Phalcon/Annotations/Adapter/Redis.php
@@ -0,0 +1,97 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Adapter;
+
+use Phalcon\Cache\Backend\Redis as BackendRedis;
+use Phalcon\Cache\Frontend\Data as FrontendData;
+
+/**
+ * Class Redis
+ *
+ * Stores the parsed annotations to the Redis database.
+ * This adapter is suitable for production.
+ *
+ *
+ * use Phalcon\Annotations\Adapter\Redis;
+ *
+ * $annotations = new Redis([
+ * 'lifetime' => 8600,
+ * 'host' => 'localhost',
+ * 'port' => 6379,
+ * 'prefix' => 'annotations_',
+ * ]);
+ *
+ *
+ * @package Phalcon\Annotations\Adapter
+ */
+class Redis extends Base
+{
+ /**
+ * @var BackendRedis
+ */
+ protected $redis;
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param array $options Options array
+ */
+ public function __construct(array $options = [])
+ {
+ if (!isset($options['host'])) {
+ $options['host'] = '127.0.0.1';
+ }
+
+ if (!isset($options['port'])) {
+ $options['port'] = 6379;
+ }
+
+ if (!isset($options['persistent'])) {
+ $options['persistent'] = false;
+ }
+
+ parent::__construct($options);
+
+ $this->redis = new BackendRedis(
+ new FrontendData(['lifetime' => $this->options['lifetime']]),
+ $options
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return BackendRedis
+ */
+ protected function getCacheBackend()
+ {
+ return $this->redis;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $key
+ * @return string
+ */
+ protected function prepareKey($key)
+ {
+ return strval($key);
+ }
+}
diff --git a/Library/Phalcon/Cache/Backend/Aerospike.php b/Library/Phalcon/Cache/Backend/Aerospike.php
index d0a92c494..1956b001c 100644
--- a/Library/Phalcon/Cache/Backend/Aerospike.php
+++ b/Library/Phalcon/Cache/Backend/Aerospike.php
@@ -19,7 +19,6 @@
namespace Phalcon\Cache\Backend;
-use Aerospike as AerospikeDb;
use Phalcon\Cache\FrontendInterface;
use Phalcon\Cache\Exception;
use Phalcon\Cache\Backend;
@@ -67,7 +66,7 @@ class Aerospike extends Backend implements BackendInterface
/**
* The Aerospike DB
- * @var AerospikeDb
+ * @var \Aerospike
*/
protected $db;
@@ -78,7 +77,7 @@ class Aerospike extends Backend implements BackendInterface
protected $namespace = 'test';
/**
- * The Aerospike Set for store sessions
+ * The Aerospike Set for store cache
* @var string
*/
protected $set = 'cache';
@@ -98,12 +97,18 @@ public function __construct(FrontendInterface $frontend, array $options)
if (isset($options['namespace'])) {
$this->namespace = $options['namespace'];
+ unset($options['namespace']);
}
if (isset($options['prefix'])) {
$this->_prefix = $options['prefix'];
}
+ if (isset($options['set']) && !empty($options['set'])) {
+ $this->set = $options['set'];
+ unset($options['set']);
+ }
+
$persistent = false;
if (isset($options['persistent'])) {
$persistent = (bool) $options['persistent'];
@@ -114,11 +119,11 @@ public function __construct(FrontendInterface $frontend, array $options)
$opts = $options['options'];
}
- $this->db = new AerospikeDb(['hosts' => $options['hosts']], $persistent, $opts);
+ $this->db = new \Aerospike(['hosts' => $options['hosts']], $persistent, $opts);
if (!$this->db->isConnected()) {
throw new Exception(
- sprintf("Aerospike failed to connect [%s]: %s", $this->db->errorno(), $this->db->error())
+ sprintf('Aerospike failed to connect [%s]: %s', $this->db->errorno(), $this->db->error())
);
}
@@ -128,7 +133,7 @@ public function __construct(FrontendInterface $frontend, array $options)
/**
* Gets the Aerospike instance.
*
- * @return AerospikeDb
+ * @return \Aerospike
*/
public function getDb()
{
@@ -142,6 +147,7 @@ public function getDb()
* @param string $content
* @param int $lifetime
* @param bool $stopBuffer
+ * @return bool
*
* @throws Exception
*/
@@ -183,10 +189,10 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
$aKey,
$bins,
$lifetime,
- [AerospikeDb::OPT_POLICY_KEY => AerospikeDb::POLICY_KEY_SEND]
+ [\Aerospike::OPT_POLICY_KEY => \Aerospike::POLICY_KEY_SEND]
);
- if (AerospikeDb::OK != $status) {
+ if (\Aerospike::OK != $status) {
throw new Exception(
sprintf('Failed storing data in Aerospike: %s', $this->db->error()),
$this->db->errorno()
@@ -202,6 +208,8 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
}
$this->_started = false;
+
+ return \Aerospike::OK == $status;
}
/**
@@ -247,7 +255,7 @@ public function get($keyName, $lifetime = null)
$status = $this->db->get($aKey, $cache);
- if ($status != AerospikeDb::OK) {
+ if ($status != \Aerospike::OK) {
return null;
}
@@ -274,7 +282,7 @@ public function delete($keyName)
$status = $this->db->remove($aKey);
- return $status == AerospikeDb::OK;
+ return $status == \Aerospike::OK;
}
/**
@@ -299,7 +307,7 @@ public function exists($keyName = null, $lifetime = null)
$aKey = $this->buildKey($prefixedKey);
$status = $this->db->get($aKey, $cache);
- return $status == AerospikeDb::OK;
+ return $status == \Aerospike::OK;
}
/**
@@ -332,7 +340,7 @@ public function increment($keyName = null, $value = null)
$status = $this->db->get($aKey, $cache);
- if ($status != AerospikeDb::OK) {
+ if ($status != \Aerospike::OK) {
return false;
}
diff --git a/Library/Phalcon/Cache/Backend/Database.php b/Library/Phalcon/Cache/Backend/Database.php
index bf4b77d34..5908e194a 100644
--- a/Library/Phalcon/Cache/Backend/Database.php
+++ b/Library/Phalcon/Cache/Backend/Database.php
@@ -114,7 +114,9 @@ public function get($keyName, $lifetime = null)
* @param string $content
* @param int $lifetime
* @param bool $stopBuffer
- * @throws \Phalcon\Cache\Exception
+ * @return bool
+ *
+ * @throws Exception
*/
public function save($keyName = null, $content = null, $lifetime = null, $stopBuffer = true)
{
@@ -145,16 +147,16 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
// Check if the cache already exist
$sql = "SELECT data, lifetime FROM {$this->table} WHERE key_name = ?";
- $cache = $this->db->fetchOne($sql, Db::FETCH_ASSOC, array($prefixedKey));
+ $cache = $this->db->fetchOne($sql, Db::FETCH_ASSOC, [$prefixedKey]);
if (!$cache) {
- $this->db->execute("INSERT INTO {$this->table} VALUES (?, ?, ?)", [
+ $status = $this->db->execute("INSERT INTO {$this->table} VALUES (?, ?, ?)", [
$prefixedKey,
$frontend->beforeStore($cachedContent),
$lifetime
]);
} else {
- $this->db->execute(
+ $status = $this->db->execute(
"UPDATE {$this->table} SET data = ?, lifetime = ? WHERE key_name = ?",
[
$frontend->beforeStore($cachedContent),
@@ -164,6 +166,10 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
);
}
+ if (!$status) {
+ throw new Exception('Failed storing data in database');
+ }
+
if ($stopBuffer) {
$frontend->stop();
}
@@ -173,6 +179,8 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
}
$this->_started = false;
+
+ return $status;
}
/**
diff --git a/Library/Phalcon/Cache/Backend/Prefixable.php b/Library/Phalcon/Cache/Backend/Prefixable.php
index a1ad985c3..f700963a8 100644
--- a/Library/Phalcon/Cache/Backend/Prefixable.php
+++ b/Library/Phalcon/Cache/Backend/Prefixable.php
@@ -23,6 +23,7 @@
*
* Trait for backend cache adapters with support of "prefix" option.
*
+ * @property string _prefix
* @package Phalcon\Cache\Backend
*/
trait Prefixable
diff --git a/Library/Phalcon/Cache/Backend/README.md b/Library/Phalcon/Cache/Backend/README.md
index 61df27fa9..ffe59df88 100644
--- a/Library/Phalcon/Cache/Backend/README.md
+++ b/Library/Phalcon/Cache/Backend/README.md
@@ -40,31 +40,30 @@ $di->set('cache', function () {
This adapter uses a database backend to store the cached content:
```php
+use Phalcon\Cache\Backend\Database;
+use Phalcon\Cache\Frontend\Data;
+use Phalcon\Db\Adapter\Pdo\Mysql;
$di->set('cache', function() {
-
// Create a connection
- $connection = new \Phalcon\Db\Adapter\Pdo\Mysql(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "cache_db"
- ));
-
- //Create a Data frontend and set a default lifetime to 1 hour
- $frontend = new Phalcon\Cache\Frontend\Data(array(
- 'lifetime' => 3600
- ));
-
- //Create the cache passing the connection
- $cache = new Phalcon\Cache\Backend\Database($frontend, array(
- 'db' => $connection,
- 'table' => 'cache_data'
- ));
+ $connection = new Mysql([
+ 'host' => 'localhost',
+ 'username' => 'root',
+ 'password' => 'secret',
+ 'dbname' => 'cache_db'
+ ]);
+
+ // Create a Data frontend and set a default lifetime to 1 hour
+ $frontend = new Data(['lifetime' => 3600]);
+
+ // Create the cache passing the connection
+ $cache = new Database($frontend, [
+ 'db' => $connection,
+ 'table' => 'cache_data'
+ ]);
return $cache;
});
-
```
This adapter uses the following table to store the data:
diff --git a/Library/Phalcon/Cache/Backend/Wincache.php b/Library/Phalcon/Cache/Backend/Wincache.php
index a78c327b2..7780d448e 100644
--- a/Library/Phalcon/Cache/Backend/Wincache.php
+++ b/Library/Phalcon/Cache/Backend/Wincache.php
@@ -58,11 +58,13 @@ public function get($keyName, $lifetime = null)
/**
* {@inheritdoc}
*
- * @param string $keyName
- * @param string $content
- * @param integer $lifetime
- * @param boolean $stopBuffer
- * @throws \Phalcon\Cache\Exception
+ * @param string $keyName
+ * @param string $content
+ * @param int $lifetime
+ * @param bool $stopBuffer
+ * @return bool
+ *
+ * @throws Exception
*/
public function save($keyName = null, $content = null, $lifetime = null, $stopBuffer = true)
{
@@ -99,7 +101,11 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
$ttl = $lifetime;
}
- wincache_ucache_set($lastKey, $preparedContent, $ttl);
+ $status = wincache_ucache_set($lastKey, $preparedContent, $ttl);
+
+ if (!$status) {
+ throw new Exception('Failed storing data by using wincache');
+ }
$isBuffering = $frontend->isBuffering();
@@ -112,6 +118,8 @@ public function save($keyName = null, $content = null, $lifetime = null, $stopBu
}
$this->_started = false;
+
+ return $status;
}
/**
@@ -134,7 +142,7 @@ public function delete($keyName)
public function queryKeys($prefix = null)
{
$info = wincache_ucache_info();
- $entries = array();
+ $entries = [];
if (!$prefix) {
$prefix = $this->_prefix;
diff --git a/Library/Phalcon/Cache/Frontend/Msgpack.php b/Library/Phalcon/Cache/Frontend/Msgpack.php
deleted file mode 100644
index 0f0a41b44..000000000
--- a/Library/Phalcon/Cache/Frontend/Msgpack.php
+++ /dev/null
@@ -1,36 +0,0 @@
-set('cache', function() {
-
- //Create a Data frontend and set a default lifetime to 1 hour
- $frontend = new Phalcon\Cache\Frontend\Msgpack(array(
- 'lifetime' => 3600
- ));
-
- $cache = new Phalcon\Cache\Backend\Memory($frontend);
-
- return $cache;
-});
-
-```
diff --git a/Library/Phalcon/Cli/Console/Extended.php b/Library/Phalcon/Cli/Console/Extended.php
index 0e69845ee..1972e4833 100644
--- a/Library/Phalcon/Cli/Console/Extended.php
+++ b/Library/Phalcon/Cli/Console/Extended.php
@@ -32,7 +32,7 @@
class Extended extends ConsoleApp
{
private $tasksDir = '';
- private $documentation = array();
+ private $documentation = [];
/**
* Handle the whole command-line tasks
@@ -44,12 +44,12 @@ class Extended extends ConsoleApp
*/
public function handle(array $arguments = null)
{
- if (isset($arguments['task']) && in_array($arguments['task'], array('-h', '--help', 'help'))) {
+ if (isset($arguments['task']) && in_array($arguments['task'], ['-h', '--help', 'help'])) {
$this->setTasksDir();
$this->createHelp();
$this->showHelp();
return;
- } elseif (isset($arguments['action']) && in_array($arguments['action'], array('-h', '--help', 'help'))) {
+ } elseif (isset($arguments['action']) && in_array($arguments['action'], ['-h', '--help', 'help'])) {
$this->setTasksDir();
$this->createHelp();
$this->showTaskHelp($arguments['task']);
@@ -75,7 +75,7 @@ private function setTasksDir()
private function createHelp()
{
- $scannedTasksDir = array_diff(scandir($this->tasksDir), array('..', '.'));
+ $scannedTasksDir = array_diff(scandir($this->tasksDir), ['..', '.']);
$config = $this->getDI()->get('config');
$dispatcher = $this->getDI()->getShared('dispatcher');
@@ -97,7 +97,7 @@ private function createHelp()
$taskClass = ($namespace ? $namespace . '\\' : '') . $taskFileInfo["filename"];
$taskName = strtolower(str_replace('Task', '', $taskFileInfo["filename"]));
- $this->documentation[$taskName] = array('description'=>array(''), 'actions'=>array());
+ $this->documentation[$taskName] = ['description' => [''], 'actions' => []];
$reflector = $reader->get($taskClass);
@@ -128,7 +128,7 @@ private function createHelp()
$actionName = strtolower(str_replace('Action', '', $action));
- $this->documentation[$taskName]['actions'][$actionName]=array();
+ $this->documentation[$taskName]['actions'][$actionName] = [];
$actionAnnotations = $collection->getAnnotations();
diff --git a/Library/Phalcon/Config/Adapter/Xml.php b/Library/Phalcon/Config/Adapter/Xml.php
new file mode 100644
index 000000000..ddd875c2a
--- /dev/null
+++ b/Library/Phalcon/Config/Adapter/Xml.php
@@ -0,0 +1,93 @@
+ |
+ | Serghei Iakovlev
+ *
+ *
+ *
+ * /phalcon/
+ *
+ *
+ * memory
+ *
+ *
+ *
+ * here
+ *
+ *
+ *
+ *
+ *
+ * You can read it as follows:
+ *
+ *
+ * use Phalcon\Config\Adapter\Xml;
+ * $config = new Xml("path/config.xml");
+ * echo $config->phalcon->baseuri;
+ * echo $config->nested->config->parameter;
+ *
+ *
+ * @package Phalcon\Config\Adapter
+ */
+class Xml extends Config
+{
+ /**
+ * Phalcon\Config\Adapter\Xml constructor
+ *
+ * @param string $filePath
+ * @throws Exception
+ */
+ public function __construct($filePath)
+ {
+ if (!extension_loaded('SimpleXML')) {
+ throw new Exception("SimpleXML extension not loaded");
+ }
+
+ libxml_use_internal_errors(true);
+ $data = simplexml_load_file($filePath, 'SimpleXMLElement', LIBXML_NOCDATA);
+
+ foreach (libxml_get_errors() as $error) {
+ /** @var \LibXMLError $error */
+ switch ($error->code) {
+ case LIBXML_ERR_WARNING:
+ trigger_error($error->message, E_USER_WARNING);
+ break;
+ default:
+ throw new Exception($error->message);
+ }
+ }
+
+ libxml_use_internal_errors(false);
+
+ parent::__construct(json_decode(json_encode((array) $data), true));
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/Mongo/Collection.php b/Library/Phalcon/Db/Adapter/Mongo/Collection.php
index 6ac1ce4e0..87246ea94 100644
--- a/Library/Phalcon/Db/Adapter/Mongo/Collection.php
+++ b/Library/Phalcon/Db/Adapter/Mongo/Collection.php
@@ -32,37 +32,37 @@ public function __get($name)
return $this->db->selectCollection($name);
}
- public function find($query = array(), $fields = array())
+ public function find($query = [], $fields = [])
{
return $this->findAsObject('Sonucu\Mongo\Document', $query, $fields);
}
- public function findAsObject($className, $query = array(), $fields = array())
+ public function findAsObject($className, $query = [], $fields = [])
{
return new Cursor($this, $className, $query, $fields);
}
- public function findOne($query = array(), $fields = array())
+ public function findOne($query = [], $fields = [])
{
return $this->findOneAsObject('Sonucu\Mongo\Document', $query, $fields);
}
- public function findOneAsObject($className, $query = array(), $fields = array())
+ public function findOneAsObject($className, $query = [], $fields = [])
{
return new $className($this, parent::findOne($query, $fields));
}
- public function insert(Document $doc, $options = array())
+ public function insert(Document $doc, $options = [])
{
//TODO: iterate props and create db refs
}
- public function batchInsert(array $col, $options = array())
+ public function batchInsert(array $col, $options = [])
{
//TODO: iterate props and create db refs
}
- public function save(Document $doc, $options = array())
+ public function save(Document $doc, $options = [])
{
//TODO: iterate props and create db refs
}
diff --git a/Library/Phalcon/Db/Adapter/Mongo/Cursor.php b/Library/Phalcon/Db/Adapter/Mongo/Cursor.php
index a6eb25d13..f97a0a37b 100644
--- a/Library/Phalcon/Db/Adapter/Mongo/Cursor.php
+++ b/Library/Phalcon/Db/Adapter/Mongo/Cursor.php
@@ -25,8 +25,8 @@ class Cursor extends \MongoCursor
public function __construct(
$collection,
$className = 'Phalcon\Db\Adapter\Mongo\Cursor',
- $query = array(),
- $fields = array()
+ $query = [],
+ $fields = []
) {
$ns = $collection->db->name . '.' . $collection->getName();
parent::__construct($collection->db->conn, $ns, $query, $fields);
diff --git a/Library/Phalcon/Db/Adapter/Mongo/Db.php b/Library/Phalcon/Db/Adapter/Mongo/Db.php
index 92463e228..6bb23751a 100644
--- a/Library/Phalcon/Db/Adapter/Mongo/Db.php
+++ b/Library/Phalcon/Db/Adapter/Mongo/Db.php
@@ -39,7 +39,7 @@ public function selectCollection($name)
return new Collection($this, $name);
}
- public function createCollection($name, $options = array())
+ public function createCollection($name, $options = [])
{
parent::createCollection($name, $options);
diff --git a/Library/Phalcon/Db/Adapter/Mongo/Document.php b/Library/Phalcon/Db/Adapter/Mongo/Document.php
index e9eed238e..5eb98bd34 100644
--- a/Library/Phalcon/Db/Adapter/Mongo/Document.php
+++ b/Library/Phalcon/Db/Adapter/Mongo/Document.php
@@ -21,7 +21,7 @@ class Document
{
protected $collection;
- public function __construct($collection, $doc = array())
+ public function __construct($collection, $doc = [])
{
$this->collection = $collection;
$this->extract(new \RecursiveArrayIterator($doc));
@@ -30,7 +30,7 @@ public function __construct($collection, $doc = array())
private function extract($iterator, $className = null)
{
if (is_numeric($iterator->key())) {
- $container = array();
+ $container = [];
} else {
if (empty($className)) {
$container = $this;
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/BulkWriteResult.php b/Library/Phalcon/Db/Adapter/MongoDB/BulkWriteResult.php
new file mode 100755
index 000000000..09a8da332
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/BulkWriteResult.php
@@ -0,0 +1,173 @@
+writeResult =$writeResult;
+ $this->insertedIds =$insertedIds;
+ $this->isAcknowledged=$writeResult->isAcknowledged();
+ }
+
+ /**
+ * Return the number of documents that were deleted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see BulkWriteResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getDeletedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getDeletedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the number of documents that were inserted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see BulkWriteResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getInsertedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getInsertedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return a map of the inserted documents' IDs.
+ *
+ * The index of each ID in the map corresponds to the document's position in
+ * the bulk operation. If the document had an ID prior to insertion (i.e.
+ * the driver did not generate an ID), this will contain its "_id" field
+ * value. Any driver-generated ID will be an MongoDB\BSON\ObjectID instance.
+ *
+ * @return mixed[]
+ */
+ public function getInsertedIds()
+ {
+ return $this->insertedIds;
+ }
+
+ /**
+ * Return the number of documents that were matched by the filter.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see BulkWriteResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getMatchedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getMatchedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the number of documents that were modified.
+ *
+ * This value is undefined (i.e. null) if the write executed as a legacy
+ * operation instead of command.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see BulkWriteResult::isAcknowledged()
+ * @return integer|null
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getModifiedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getModifiedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the number of documents that were upserted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see BulkWriteResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getUpsertedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getUpsertedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return a map of the upserted documents' IDs.
+ *
+ * The index of each ID in the map corresponds to the document's position
+ * in bulk operation. If the document had an ID prior to upserting (i.e. the
+ * server did not need to generate an ID), this will contain its "_id". Any
+ * server-generated ID will be an MongoDB\BSON\ObjectID instance.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see BulkWriteResult::isAcknowledged()
+ * @return mixed[]
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getUpsertedIds()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getUpsertedIds();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return whether this update was acknowledged by the server.
+ *
+ * If the update was not acknowledged, other fields from the WriteResult
+ * (e.g. matchedCount) will be undefined.
+ *
+ * @return boolean
+ */
+ public function isAcknowledged()
+ {
+ return $this->isAcknowledged;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Client.php b/Library/Phalcon/Db/Adapter/MongoDB/Client.php
new file mode 100755
index 000000000..b0e45ec91
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Client.php
@@ -0,0 +1,177 @@
+'Phalcon\Db\Adapter\MongoDB\Model\BSONArray',
+ 'document'=>'Phalcon\Db\Adapter\MongoDB\Model\BSONDocument',
+ 'root' =>'Phalcon\Db\Adapter\MongoDB\Model\BSONDocument',
+ ];
+
+ private $manager;
+ private $uri;
+ private $typeMap;
+
+ /**
+ * Constructs a new Client instance.
+ *
+ * This is the preferred class for connecting to a MongoDB server or
+ * cluster of servers. It serves as a gateway for accessing individual
+ * databases and collections.
+ *
+ * Supported driver-specific options:
+ *
+ * * typeMap (array): Default type map for cursors and BSON documents.
+ *
+ * Other options are documented in MongoDB\Driver\Manager::__construct().
+ *
+ * @see http://docs.mongodb.org/manual/reference/connection-string/
+ * @see http://php.net/manual/en/mongodb-driver-manager.construct.php
+ * @see http://php.net/manual/en/mongodb.persistence.php#mongodb.persistence.typemaps
+ *
+ * @param string $uri MongoDB connection string
+ * @param array $uriOptions Additional connection string options
+ * @param array $driverOptions Driver-specific options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($uri = 'mongodb://localhost:27017', array $uriOptions = [], array $driverOptions = [])
+ {
+ $driverOptions+=['typeMap'=>self::$defaultTypeMap];
+
+ if (isset($driverOptions['typeMap'])&&!is_array($driverOptions['typeMap'])) {
+ throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array');
+ }
+
+ $this->manager=new Manager($uri, $uriOptions, $driverOptions);
+ $this->uri =(string)$uri;
+ $this->typeMap=isset($driverOptions['typeMap'])?$driverOptions['typeMap']:null;
+ }
+
+ /**
+ * Return internal properties for debugging purposes.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return [
+ 'manager'=>$this->manager,
+ 'uri' =>$this->uri,
+ 'typeMap'=>$this->typeMap,
+ ];
+ }
+
+ /**
+ * Select a database.
+ *
+ * Note: databases whose names contain special characters (e.g. "-") may
+ * be selected with complex syntax (e.g. $client->{"that-database"}) or
+ * {@link selectDatabase()}.
+ *
+ * @see http://php.net/oop5.overloading#object.get
+ * @see http://php.net/types.string#language.types.string.parsing.complex
+ *
+ * @param string $databaseName Name of the database to select
+ *
+ * @return Database
+ */
+ public function __get($databaseName)
+ {
+ return $this->selectDatabase($databaseName);
+ }
+
+ /**
+ * Return the connection string (i.e. URI).
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->uri;
+ }
+
+ /**
+ * Drop a database.
+ *
+ * @see DropDatabase::__construct() for supported options
+ *
+ * @param string $databaseName Database name
+ * @param array $options Additional options
+ *
+ * @return array|object Command result document
+ */
+ public function dropDatabase($databaseName, array $options = [])
+ {
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DropDatabase($databaseName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * List databases.
+ *
+ * @see ListDatabases::__construct() for supported options
+ * @return DatabaseInfoIterator
+ */
+ public function listDatabases(array $options = [])
+ {
+ $operation=new ListDatabases($options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Select a collection.
+ *
+ * @see Collection::__construct() for supported options
+ *
+ * @param string $databaseName Name of the database containing the collection
+ * @param string $collectionName Name of the collection to select
+ * @param array $options Collection constructor options
+ *
+ * @return Collection
+ */
+ public function selectCollection($databaseName, $collectionName, array $options = [])
+ {
+ $options+=['typeMap'=>$this->typeMap];
+
+ return new Collection($this->manager, $databaseName, $collectionName, $options);
+ }
+
+ /**
+ * Select a database.
+ *
+ * @see Database::__construct() for supported options
+ *
+ * @param string $databaseName Name of the database to select
+ * @param array $options Database constructor options
+ *
+ * @return Database
+ */
+ public function selectDatabase($databaseName, array $options = [])
+ {
+ $options+=['typeMap'=>$this->typeMap];
+
+ return new Database($this->manager, $databaseName, $options);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Collection.php b/Library/Phalcon/Db/Adapter/MongoDB/Collection.php
new file mode 100755
index 000000000..04e1b85fc
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Collection.php
@@ -0,0 +1,806 @@
+'Phalcon\Db\Adapter\MongoDB\Model\BSONArray',
+ 'document'=>'Phalcon\Db\Adapter\MongoDB\Model\BSONDocument',
+ 'root' =>'Phalcon\Db\Adapter\MongoDB\Model\BSONDocument',
+ ];
+ private static $wireVersionForFindAndModifyWriteConcern=4;
+
+ private $collectionName;
+ private $databaseName;
+ private $manager;
+ private $readConcern;
+ private $readPreference;
+ private $typeMap;
+ private $writeConcern;
+
+ /**
+ * Constructs new Collection instance.
+ *
+ * This class provides methods for collection-specific operations, such as
+ * CRUD (i.e. create, read, update, and delete) and index management.
+ *
+ * Supported options:
+ *
+ * * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
+ * use for collection operations. Defaults to the Manager's read concern.
+ *
+ * * readPreference (MongoDB\Driver\ReadPreference): The default read
+ * preference to use for collection operations. Defaults to the Manager's
+ * read preference.
+ *
+ * * typeMap (array): Default type map for cursors and BSON documents.
+ *
+ * * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
+ * to use for collection operations. Defaults to the Manager's write
+ * concern.
+ *
+ * @param Manager $manager Manager instance from the driver
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array $options Collection options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct(Manager $manager, $databaseName, $collectionName, array $options = [])
+ {
+ if (strlen($databaseName)<1) {
+ throw new InvalidArgumentException('$databaseName is invalid: '.$databaseName);
+ }
+
+ if (strlen($collectionName)<1) {
+ throw new InvalidArgumentException('$collectionName is invalid: '.$collectionName);
+ }
+
+ if (isset($options['readConcern'])&&!$options['readConcern'] instanceof ReadConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"readConcern" option',
+ $options['readConcern'],
+ 'MongoDB\Driver\ReadConcern'
+ );
+ }
+
+ if (isset($options['readPreference'])&&!$options['readPreference'] instanceof ReadPreference) {
+ throw InvalidArgumentException::invalidType(
+ '"readPreference" option',
+ $options['readPreference'],
+ 'MongoDB\Driver\ReadPreference'
+ );
+ }
+
+ if (isset($options['typeMap'])&&!is_array($options['typeMap'])) {
+ throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ $this->manager =$manager;
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->readConcern =isset($options['readConcern'])?$options['readConcern']:$this->manager->getReadConcern();
+ $this->readPreference=isset($options['readPreference'])
+ ?$options['readPreference']
+ :$this->manager->getReadPreference();
+ $this->typeMap =isset($options['typeMap'])?$options['typeMap']:self::$defaultTypeMap;
+ $this->writeConcern =isset($options['writeConcern'])
+ ?$options['writeConcern']
+ :$this->manager->getWriteConcern();
+ }
+
+ /**
+ * Return internal properties for debugging purposes.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return [
+ 'collectionName'=>$this->collectionName,
+ 'databaseName' =>$this->databaseName,
+ 'manager' =>$this->manager,
+ 'readConcern' =>$this->readConcern,
+ 'readPreference'=>$this->readPreference,
+ 'typeMap' =>$this->typeMap,
+ 'writeConcern' =>$this->writeConcern,
+ ];
+ }
+
+ /**
+ * Return the collection namespace (e.g. "db.collection").
+ *
+ * @see https://docs.mongodb.org/manual/faq/developers/#faq-dev-namespace
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->databaseName.'.'.$this->collectionName;
+ }
+
+ /**
+ * Executes an aggregation framework pipeline on the collection.
+ *
+ * Note: this method's return value depends on the MongoDB server version
+ * and the "useCursor" option. If "useCursor" is true, a Cursor will be
+ * returned; otherwise, an ArrayIterator is returned, which wraps the
+ * "result" array from the command response document.
+ *
+ * Note: BSON deserialization of inline aggregation results (i.e. not using
+ * a command cursor) does not yet support a custom type map
+ * (depends on: https://jira.mongodb.org/browse/PHPC-314).
+ *
+ * @see Aggregate::__construct() for supported options
+ *
+ * @param array $pipeline List of pipeline operations
+ * @param array $options Command options
+ *
+ * @return Traversable
+ */
+ public function aggregate(array $pipeline, array $options = [])
+ {
+ $hasOutStage=Functions::isLastPipelineOperatorOut($pipeline);
+
+ /* A "majority" read concern is not compatible with the $out stage, so
+ * avoid providing the Collection's read concern if it would conflict.
+ */
+ if (!isset($options['readConcern'])&&!($hasOutStage&&$this->readConcern->getLevel()===ReadConcern::MAJORITY)) {
+ $options['readConcern']=$this->readConcern;
+ }
+
+ if (!isset($options['readPreference'])) {
+ $options['readPreference']=$this->readPreference;
+ }
+
+ if ($hasOutStage) {
+ $options['readPreference']=new ReadPreference(ReadPreference::RP_PRIMARY);
+ }
+
+ if (!isset($options['typeMap'])&&(!isset($options['useCursor'])||$options['useCursor'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
+ $server =$this->manager->selectServer($options['readPreference']);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Executes multiple write operations.
+ *
+ * @see BulkWrite::__construct() for supported options
+ *
+ * @param array[] $operations List of write operations
+ * @param array $options Command options
+ *
+ * @return BulkWriteResult
+ */
+ public function bulkWrite(array $operations, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Gets the number of documents matching the filter.
+ *
+ * @see Count::__construct() for supported options
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Command options
+ *
+ * @return integer
+ */
+ public function count($filter = [], array $options = [])
+ {
+ if (!isset($options['readConcern'])) {
+ $options['readConcern']=$this->readConcern;
+ }
+
+ if (!isset($options['readPreference'])) {
+ $options['readPreference']=$this->readPreference;
+ }
+
+ $operation=new Count($this->databaseName, $this->collectionName, $filter, $options);
+ $server =$this->manager->selectServer($options['readPreference']);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Create a single index for the collection.
+ *
+ * @see Collection::createIndexes()
+ *
+ * @param array|object $key Document containing fields mapped to values,
+ * which denote order or an index type
+ * @param array $options Index options
+ *
+ * @return string The name of the created index
+ */
+ public function createIndex($key, array $options = [])
+ {
+ return current($this->createIndexes([['key'=>$key]+$options]));
+ }
+
+ /**
+ * Create one or more indexes for the collection.
+ *
+ * Each element in the $indexes array must have a "key" document, which
+ * contains fields mapped to an order or type. Other options may follow.
+ * For example:
+ *
+ * $indexes = [
+ * // Create a unique index on the "username" field
+ * [ 'key' => [ 'username' => 1 ], 'unique' => true ],
+ * // Create a 2dsphere index on the "loc" field with a custom name
+ * [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
+ * ];
+ *
+ * If the "name" option is unspecified, a name will be generated from the
+ * "key" document.
+ *
+ * @see http://docs.mongodb.org/manual/reference/command/createIndexes/
+ * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
+ *
+ * @param array[] $indexes List of index specifications
+ *
+ * @return string[] The names of the created indexes
+ * @throws InvalidArgumentException if an index specification is invalid
+ */
+ public function createIndexes(array $indexes)
+ {
+ $operation=new CreateIndexes($this->databaseName, $this->collectionName, $indexes);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Deletes all documents matching the filter.
+ *
+ * @see DeleteMany::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/delete/
+ *
+ * @param array|object $filter Query by which to delete documents
+ * @param array $options Command options
+ *
+ * @return DeleteResult
+ */
+ public function deleteMany($filter, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Deletes at most one document matching the filter.
+ *
+ * @see DeleteOne::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/delete/
+ *
+ * @param array|object $filter Query by which to delete documents
+ * @param array $options Command options
+ *
+ * @return DeleteResult
+ */
+ public function deleteOne($filter, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Finds the distinct values for a specified field across the collection.
+ *
+ * @see Distinct::__construct() for supported options
+ *
+ * @param string $fieldName Field for which to return distinct values
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Command options
+ *
+ * @return mixed[]
+ */
+ public function distinct($fieldName, $filter = [], array $options = [])
+ {
+ if (!isset($options['readConcern'])) {
+ $options['readConcern']=$this->readConcern;
+ }
+
+ if (!isset($options['readPreference'])) {
+ $options['readPreference']=$this->readPreference;
+ }
+
+ $operation=new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);
+ $server =$this->manager->selectServer($options['readPreference']);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Drop this collection.
+ *
+ * @see DropCollection::__construct() for supported options
+ *
+ * @param array $options Additional options
+ *
+ * @return array|object Command result document
+ */
+ public function drop(array $options = [])
+ {
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DropCollection($this->databaseName, $this->collectionName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Drop a single index in the collection.
+ *
+ * @see DropIndexes::__construct() for supported options
+ *
+ * @param string $indexName Index name
+ * @param array $options Additional options
+ *
+ * @return array|object Command result document
+ * @throws InvalidArgumentException if $indexName is an empty string or "*"
+ */
+ public function dropIndex($indexName, array $options = [])
+ {
+ $indexName=(string)$indexName;
+
+ if ($indexName==='*') {
+ throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
+ }
+
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Drop all indexes in the collection.
+ *
+ * @see DropIndexes::__construct() for supported options
+ *
+ * @param array $options Additional options
+ *
+ * @return array|object Command result document
+ */
+ public function dropIndexes(array $options = [])
+ {
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DropIndexes($this->databaseName, $this->collectionName, '*', $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Finds documents matching the query.
+ *
+ * @see Find::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Additional options
+ *
+ * @return Cursor
+ */
+ public function find($filter = [], array $options = [])
+ {
+ if (!isset($options['readConcern'])) {
+ $options['readConcern']=$this->readConcern;
+ }
+
+ if (!isset($options['readPreference'])) {
+ $options['readPreference']=$this->readPreference;
+ }
+
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new Find($this->databaseName, $this->collectionName, $filter, $options);
+ $server =$this->manager->selectServer($options['readPreference']);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Finds a single document matching the query.
+ *
+ * @see FindOne::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Additional options
+ *
+ * @return array|object|null
+ */
+ public function findOne($filter = [], array $options = [])
+ {
+ if (!isset($options['readConcern'])) {
+ $options['readConcern']=$this->readConcern;
+ }
+
+ if (!isset($options['readPreference'])) {
+ $options['readPreference']=$this->readPreference;
+ }
+
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new FindOne($this->databaseName, $this->collectionName, $filter, $options);
+ $server =$this->manager->selectServer($options['readPreference']);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Finds a single document and deletes it, returning the original.
+ *
+ * The document to return may be null if no document matched the filter.
+ *
+ * Note: BSON deserialization of the returned document does not yet support
+ * a custom type map (depends on: https://jira.mongodb.org/browse/PHPC-314).
+ *
+ * @see FindOneAndDelete::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Command options
+ *
+ * @return object|null
+ */
+ public function findOneAndDelete($filter, array $options = [])
+ {
+ $server=$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ if (!isset($options['writeConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForFindAndModifyWriteConcern
+ )
+ ) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Finds a single document and replaces it, returning either the original or
+ * the replaced document.
+ *
+ * The document to return may be null if no document matched the filter. By
+ * default, the original document is returned. Specify
+ * FindOneAndReplace::RETURN_DOCUMENT_AFTER for the "returnDocument" option
+ * to return the updated document.
+ *
+ * Note: BSON deserialization of the returned document does not yet support
+ * a custom type map (depends on: https://jira.mongodb.org/browse/PHPC-314).
+ *
+ * @see FindOneAndReplace::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $replacement Replacement document
+ * @param array $options Command options
+ *
+ * @return object|null
+ */
+ public function findOneAndReplace($filter, $replacement, array $options = [])
+ {
+ $server=$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ if (!isset($options['writeConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForFindAndModifyWriteConcern
+ )
+ ) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Finds a single document and updates it, returning either the original or
+ * the updated document.
+ *
+ * The document to return may be null if no document matched the filter. By
+ * default, the original document is returned. Specify
+ * FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the "returnDocument" option
+ * to return the updated document.
+ *
+ * Note: BSON deserialization of the returned document does not yet support
+ * a custom type map (depends on: https://jira.mongodb.org/browse/PHPC-314).
+ *
+ * @see FindOneAndReplace::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $update Update to apply to the matched document
+ * @param array $options Command options
+ *
+ * @return object|null
+ */
+ public function findOneAndUpdate($filter, $update, array $options = [])
+ {
+ $server=$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ if (!isset($options['writeConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForFindAndModifyWriteConcern
+ )
+ ) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Return the collection name.
+ *
+ * @return string
+ */
+ public function getCollectionName()
+ {
+ return $this->collectionName;
+ }
+
+ /**
+ * Return the database name.
+ *
+ * @return string
+ */
+ public function getDatabaseName()
+ {
+ return $this->databaseName;
+ }
+
+ /**
+ * Return the collection namespace.
+ *
+ * @see https://docs.mongodb.org/manual/reference/glossary/#term-namespace
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->databaseName.'.'.$this->collectionName;
+ }
+
+ /**
+ * Inserts multiple documents.
+ *
+ * @see InsertMany::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/insert/
+ *
+ * @param array[]|object[] $documents The documents to insert
+ * @param array $options Command options
+ *
+ * @return InsertManyResult
+ */
+ public function insertMany(array $documents, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Inserts one document.
+ *
+ * @see InsertOne::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/insert/
+ *
+ * @param array|object $document The document to insert
+ * @param array $options Command options
+ *
+ * @return InsertOneResult
+ */
+ public function insertOne($document, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new InsertOne($this->databaseName, $this->collectionName, $document, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Returns information for all indexes for the collection.
+ *
+ * @see ListIndexes::__construct() for supported options
+ * @return IndexInfoIterator
+ */
+ public function listIndexes(array $options = [])
+ {
+ $operation=new ListIndexes($this->databaseName, $this->collectionName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Replaces at most one document matching the filter.
+ *
+ * @see ReplaceOne::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/update/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $replacement Replacement document
+ * @param array $options Command options
+ *
+ * @return UpdateResult
+ */
+ public function replaceOne($filter, $replacement, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Updates all documents matching the filter.
+ *
+ * @see UpdateMany::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/update/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $update Update to apply to the matched documents
+ * @param array $options Command options
+ *
+ * @return UpdateResult
+ */
+ public function updateMany($filter, $update, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Updates at most one document matching the filter.
+ *
+ * @see UpdateOne::__construct() for supported options
+ * @see http://docs.mongodb.org/manual/reference/command/update/
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $update Update to apply to the matched document
+ * @param array $options Command options
+ *
+ * @return UpdateResult
+ */
+ public function updateOne($filter, $update, array $options = [])
+ {
+ if (!isset($options['writeConcern'])) {
+ $options['writeConcern']=$this->writeConcern;
+ }
+
+ $operation=new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Get a clone of this collection with different options.
+ *
+ * @see Collection::__construct() for supported options
+ *
+ * @param array $options Collection constructor options
+ *
+ * @return Collection
+ */
+ public function withOptions(array $options = [])
+ {
+ $options+=[
+ 'readConcern' =>$this->readConcern,
+ 'readPreference'=>$this->readPreference,
+ 'typeMap' =>$this->typeMap,
+ 'writeConcern' =>$this->writeConcern,
+ ];
+
+ return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Database.php b/Library/Phalcon/Db/Adapter/MongoDB/Database.php
new file mode 100755
index 000000000..6c55190bf
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Database.php
@@ -0,0 +1,318 @@
+'Phalcon\Db\Adapter\MongoDB\Model\BSONArray',
+ 'document'=>'Phalcon\Db\Adapter\MongoDB\Model\BSONDocument',
+ 'root' =>'Phalcon\Db\Adapter\MongoDB\Model\BSONDocument',
+ ];
+
+ private $databaseName;
+ private $manager;
+ private $readConcern;
+ private $readPreference;
+ private $typeMap;
+ private $writeConcern;
+
+ /**
+ * Constructs new Database instance.
+ *
+ * This class provides methods for database-specific operations and serves
+ * as a gateway for accessing collections.
+ *
+ * Supported options:
+ *
+ * * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
+ * use for database operations and selected collections. Defaults to the
+ * Manager's read concern.
+ *
+ * * readPreference (MongoDB\Driver\ReadPreference): The default read
+ * preference to use for database operations and selected collections.
+ * Defaults to the Manager's read preference.
+ *
+ * * typeMap (array): Default type map for cursors and BSON documents.
+ *
+ * * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
+ * to use for database operations and selected collections. Defaults to
+ * the Manager's write concern.
+ *
+ * @param Manager $manager Manager instance from the driver
+ * @param string $databaseName Database name
+ * @param array $options Database options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct(Manager $manager, $databaseName, array $options = [])
+ {
+ if (strlen($databaseName)<1) {
+ throw new InvalidArgumentException('$databaseName is invalid: '.$databaseName);
+ }
+
+ if (isset($options['readConcern'])&&!$options['readConcern'] instanceof ReadConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"readConcern" option',
+ $options['readConcern'],
+ 'MongoDB\Driver\ReadConcern'
+ );
+ }
+
+ if (isset($options['readPreference'])&&!$options['readPreference'] instanceof ReadPreference) {
+ throw InvalidArgumentException::invalidType(
+ '"readPreference" option',
+ $options['readPreference'],
+ 'MongoDB\Driver\ReadPreference'
+ );
+ }
+
+ if (isset($options['typeMap'])&&!is_array($options['typeMap'])) {
+ throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ $this->manager =$manager;
+ $this->databaseName =(string)$databaseName;
+ $this->readConcern =isset($options['readConcern'])?$options['readConcern']:$this->manager->getReadConcern();
+ $this->readPreference=isset($options['readPreference'])
+ ?$options['readPreference']
+ :$this->manager->getReadPreference();
+ $this->typeMap =isset($options['typeMap'])?$options['typeMap']:self::$defaultTypeMap;
+ $this->writeConcern =isset($options['writeConcern'])
+ ?$options['writeConcern']
+ :$this->manager->getWriteConcern();
+ }
+
+ /**
+ * Return internal properties for debugging purposes.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return [
+ 'databaseName' =>$this->databaseName,
+ 'manager' =>$this->manager,
+ 'readConcern' =>$this->readConcern,
+ 'readPreference'=>$this->readPreference,
+ 'typeMap' =>$this->typeMap,
+ 'writeConcern' =>$this->writeConcern,
+ ];
+ }
+
+ /**
+ * Select a collection within this database.
+ *
+ * Note: collections whose names contain special characters (e.g. ".") may
+ * be selected with complex syntax (e.g. $database->{"system.profile"}) or
+ * {@link selectCollection()}.
+ *
+ * @see http://php.net/oop5.overloading#object.get
+ * @see http://php.net/types.string#language.types.string.parsing.complex
+ *
+ * @param string $collectionName Name of the collection to select
+ *
+ * @return Collection
+ */
+ public function __get($collectionName)
+ {
+ return $this->selectCollection($collectionName);
+ }
+
+ /**
+ * Return the database name.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->databaseName;
+ }
+
+ /**
+ * Execute a command on this database.
+ *
+ * @see DatabaseCommand::__construct() for supported options
+ *
+ * @param array|object $command Command document
+ * @param array $options Options for command execution
+ *
+ * @return Cursor
+ * @throws InvalidArgumentException
+ */
+ public function command($command, array $options = [])
+ {
+ if (!isset($options['readPreference'])) {
+ $options['readPreference']=$this->readPreference;
+ }
+
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DatabaseCommand($this->databaseName, $command, $options);
+ $server =$this->manager->selectServer($options['readPreference']);
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Create a new collection explicitly.
+ *
+ * @see CreateCollection::__construct() for supported options
+ *
+ * @param string $collectionName
+ * @param array $options
+ *
+ * @return array|object Command result document
+ */
+ public function createCollection($collectionName, array $options = [])
+ {
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new CreateCollection($this->databaseName, $collectionName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Drop this database.
+ *
+ * @see DropDatabase::__construct() for supported options
+ *
+ * @param array $options Additional options
+ *
+ * @return array|object Command result document
+ */
+ public function drop(array $options = [])
+ {
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DropDatabase($this->databaseName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Drop a collection within this database.
+ *
+ * @see DropCollection::__construct() for supported options
+ *
+ * @param string $collectionName Collection name
+ * @param array $options Additional options
+ *
+ * @return array|object Command result document
+ */
+ public function dropCollection($collectionName, array $options = [])
+ {
+ if (!isset($options['typeMap'])) {
+ $options['typeMap']=$this->typeMap;
+ }
+
+ $operation=new DropCollection($this->databaseName, $collectionName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Returns the database name.
+ *
+ * @return string
+ */
+ public function getDatabaseName()
+ {
+ return $this->databaseName;
+ }
+
+ /**
+ * Returns information for all collections in this database.
+ *
+ * @see ListCollections::__construct() for supported options
+ *
+ * @param array $options
+ *
+ * @return CollectionInfoIterator
+ */
+ public function listCollections(array $options = [])
+ {
+ $operation=new ListCollections($this->databaseName, $options);
+ $server =$this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+ return $operation->execute($server);
+ }
+
+ /**
+ * Select a collection within this database.
+ *
+ * @see Collection::__construct() for supported options
+ *
+ * @param string $collectionName Name of the collection to select
+ * @param array $options Collection constructor options
+ *
+ * @return Collection
+ */
+ public function selectCollection($collectionName, array $options = [])
+ {
+ $options+=[
+ 'readConcern' =>$this->readConcern,
+ 'readPreference'=>$this->readPreference,
+ 'typeMap' =>$this->typeMap,
+ 'writeConcern' =>$this->writeConcern,
+ ];
+
+ return new Collection($this->manager, $this->databaseName, $collectionName, $options);
+ }
+
+ /**
+ * Get a clone of this database with different options.
+ *
+ * @see Database::__construct() for supported options
+ *
+ * @param array $options Database constructor options
+ *
+ * @return Database
+ */
+ public function withOptions(array $options = [])
+ {
+ $options+=[
+ 'readConcern' =>$this->readConcern,
+ 'readPreference'=>$this->readPreference,
+ 'typeMap' =>$this->typeMap,
+ 'writeConcern' =>$this->writeConcern,
+ ];
+
+ return new Database($this->manager, $this->databaseName, $options);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/DeleteResult.php b/Library/Phalcon/Db/Adapter/MongoDB/DeleteResult.php
new file mode 100755
index 000000000..f27286a8e
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/DeleteResult.php
@@ -0,0 +1,57 @@
+writeResult =$writeResult;
+ $this->isAcknowledged=$writeResult->isAcknowledged();
+ }
+
+ /**
+ * Return the number of documents that were deleted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see DeleteResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getDeletedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getDeletedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return whether this delete was acknowledged by the server.
+ *
+ * If the delete was not acknowledged, other fields from the WriteResult
+ * (e.g. deletedCount) will be undefined.
+ *
+ * @return boolean
+ */
+ public function isAcknowledged()
+ {
+ return $this->isAcknowledged;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Exception/BadMethodCallException.php b/Library/Phalcon/Db/Adapter/MongoDB/Exception/BadMethodCallException.php
new file mode 100755
index 000000000..1c2f69215
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Exception/BadMethodCallException.php
@@ -0,0 +1,30 @@
+bsonSerialize());
+ }
+
+ return is_array($document)?$document['_id']:$document->_id;
+ }
+
+ /**
+ * Generate an index name from a key specification.
+ *
+ * @internal
+ *
+ * @param array|object $document Document containing fields mapped to values,
+ * which denote order or an index type
+ *
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ public static function generateIndexName($document)
+ {
+ if (is_object($document)) {
+ $document=get_object_vars($document);
+ }
+
+ if (!is_array($document)) {
+ throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
+ }
+
+ $name='';
+
+ foreach ($document as $field => $type) {
+ $name.=($name!=''?'_':'').$field.'_'.$type;
+ }
+
+ return $name;
+ }
+
+ /**
+ * Return whether the first key in the document starts with a "$" character.
+ *
+ * This is used for differentiating update and replacement documents.
+ *
+ * @internal
+ *
+ * @param array|object $document Update or replacement document
+ *
+ * @return boolean
+ * @throws InvalidArgumentException
+ */
+ public static function isFirstKeyOperator($document)
+ {
+ if (is_object($document)) {
+ $document=get_object_vars($document);
+ }
+
+ if (!is_array($document)) {
+ throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
+ }
+
+ $firstKey=(string)key($document);
+
+ return (isset($firstKey[0])&&$firstKey[0]=='$');
+ }
+
+ /**
+ * Return whether the aggregation pipeline ends with an $out operator.
+ *
+ * This is used for determining whether the aggregation pipeline msut be
+ * executed against a primary server.
+ *
+ * @internal
+ *
+ * @param array $pipeline List of pipeline operations
+ *
+ * @return boolean
+ */
+ public static function isLastPipelineOperatorOut(array $pipeline)
+ {
+ $lastOp=end($pipeline);
+
+ if ($lastOp===false) {
+ return false;
+ }
+
+ $lastOp=(array)$lastOp;
+
+ return key($lastOp)==='$out';
+ }
+
+ /**
+ * Converts a ReadConcern instance to a stdClass for use in a BSON document.
+ *
+ * @internal
+ * @see https://jira.mongodb.org/browse/PHPC-498
+ *
+ * @param ReadConcern $readConcern Read concern
+ *
+ * @return stdClass
+ */
+ public static function readConcernAsDocument(ReadConcern $readConcern)
+ {
+ $document=[];
+
+ if ($readConcern->getLevel()!==null) {
+ $document['level']=$readConcern->getLevel();
+ }
+
+ return (object)$document;
+ }
+
+ /**
+ * Return whether the server supports a particular feature.
+ *
+ * @internal
+ *
+ * @param Server $server Server to check
+ * @param integer $feature Feature constant (i.e. wire protocol version)
+ *
+ * @return boolean
+ */
+ public static function serverSupportsFeature(Server $server, $feature)
+ {
+ $info =$server->getInfo();
+ $maxWireVersion=isset($info['maxWireVersion'])?(integer)$info['maxWireVersion']:0;
+ $minWireVersion=isset($info['minWireVersion'])?(integer)$info['minWireVersion']:0;
+
+ return ($minWireVersion<=$feature&&$maxWireVersion>=$feature);
+ }
+
+ /**
+ * @param $input
+ *
+ * @return bool
+ */
+ public static function isStringArray($input)
+ {
+ if (!is_array($input)) {
+ return false;
+ }
+ foreach ($input as $item) {
+ if (!is_string($item)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/GridFS/Bucket.php b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/Bucket.php
new file mode 100755
index 000000000..d3ff850fe
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/Bucket.php
@@ -0,0 +1,460 @@
+'fs',
+ 'chunkSizeBytes'=>self::$defaultChunkSizeBytes,
+ ];
+
+ if (isset($options['bucketName'])&&!is_string($options['bucketName'])) {
+ throw InvalidArgumentException::invalidType('"bucketName" option', $options['bucketName'], 'string');
+ }
+
+ if (isset($options['chunkSizeBytes'])&&!is_integer($options['chunkSizeBytes'])) {
+ throw InvalidArgumentException::invalidType(
+ '"chunkSizeBytes" option',
+ $options['chunkSizeBytes'],
+ 'integer'
+ );
+ }
+
+ if (isset($options['readPreference'])&&!$options['readPreference'] instanceof ReadPreference) {
+ throw InvalidArgumentException::invalidType(
+ '"readPreference" option',
+ $options['readPreference'],
+ 'MongoDB\Driver\ReadPreference'
+ );
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ $this->databaseName=(string)$databaseName;
+ $this->options =$options;
+
+ $collectionOptions=array_intersect_key($options, ['readPreference'=>1,'writeConcern'=>1]);
+
+ $this->collectionWrapper=new CollectionWrapper(
+ $manager,
+ $databaseName,
+ $options['bucketName'],
+ $collectionOptions
+ );
+ $this->registerStreamWrapper();
+ }
+
+ /**
+ * Delete a file from the GridFS bucket.
+ *
+ * If the files collection document is not found, this method will still
+ * attempt to delete orphaned chunks.
+ *
+ * @param mixed $id File ID
+ *
+ * @throws FileNotFoundException
+ */
+ public function delete($id)
+ {
+ $file=$this->collectionWrapper->findFileById($id);
+ $this->collectionWrapper->deleteFileAndChunksById($id);
+
+ if ($file===null) {
+ throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+ }
+ }
+
+ /**
+ * Writes the contents of a GridFS file to a writable stream.
+ *
+ * @param mixed $id File ID
+ * @param resource $destination Writable Stream
+ *
+ * @throws FileNotFoundException
+ */
+ public function downloadToStream($id, $destination)
+ {
+ $file=$this->collectionWrapper->findFileById($id);
+
+ if ($file===null) {
+ throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+ }
+
+ $stream=new ReadableStream($this->collectionWrapper, $file);
+ $stream->downloadToStream($destination);
+ }
+
+ /**
+ * Writes the contents of a GridFS file, which is selected by name and
+ * revision, to a writable stream.
+ *
+ * Supported options:
+ *
+ * * revision (integer): Which revision (i.e. documents with the same
+ * filename and different uploadDate) of the file to retrieve. Defaults
+ * to -1 (i.e. the most recent revision).
+ *
+ * Revision numbers are defined as follows:
+ *
+ * * 0 = the original stored file
+ * * 1 = the first revision
+ * * 2 = the second revision
+ * * etc…
+ * * -2 = the second most recent revision
+ * * -1 = the most recent revision
+ *
+ * @param string $filename Filename
+ * @param resource $destination Writable Stream
+ * @param array $options Download options
+ *
+ * @throws FileNotFoundException
+ */
+ public function downloadToStreamByName($filename, $destination, array $options = [])
+ {
+ $options+=['revision'=>-1];
+
+ $file=$this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);
+
+ if ($file===null) {
+ throw FileNotFoundException::byFilenameAndRevision(
+ $filename,
+ $options['revision'],
+ $this->getFilesNamespace()
+ );
+ }
+
+ $stream=new ReadableStream($this->collectionWrapper, $file);
+ $stream->downloadToStream($destination);
+ }
+
+ /**
+ * Drops the files and chunks collections associated with this GridFS
+ * bucket.
+ */
+ public function drop()
+ {
+ $this->collectionWrapper->dropCollections();
+ }
+
+ /**
+ * Finds documents from the GridFS bucket's files collection matching the
+ * query.
+ *
+ * @see Find::__construct() for supported options
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Additional options
+ *
+ * @return Cursor
+ */
+ public function find($filter, array $options = [])
+ {
+ return $this->collectionWrapper->findFiles($filter, $options);
+ }
+
+ public function getCollectionWrapper()
+ {
+ return $this->collectionWrapper;
+ }
+
+ public function getDatabaseName()
+ {
+ return $this->databaseName;
+ }
+
+ /**
+ * Gets the ID of the GridFS file associated with a stream.
+ *
+ * @param resource $stream GridFS stream
+ *
+ * @return mixed
+ */
+ public function getIdFromStream($stream)
+ {
+ $metadata=stream_get_meta_data($stream);
+
+ if ($metadata['wrapper_data'] instanceof StreamWrapper) {
+ return $metadata['wrapper_data']->getId();
+ }
+
+ // TODO: Throw if we cannot access the ID
+ }
+
+ /**
+ * Opens a readable stream for reading a GridFS file.
+ *
+ * @param mixed $id File ID
+ *
+ * @return resource
+ * @throws FileNotFoundException
+ */
+ public function openDownloadStream($id)
+ {
+ $file=$this->collectionWrapper->findFileById($id);
+
+ if ($file===null) {
+ throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+ }
+
+ return $this->openDownloadStreamByFile($file);
+ }
+
+ /**
+ * Opens a readable stream stream to read a GridFS file, which is selected
+ * by name and revision.
+ *
+ * Supported options:
+ *
+ * * revision (integer): Which revision (i.e. documents with the same
+ * filename and different uploadDate) of the file to retrieve. Defaults
+ * to -1 (i.e. the most recent revision).
+ *
+ * Revision numbers are defined as follows:
+ *
+ * * 0 = the original stored file
+ * * 1 = the first revision
+ * * 2 = the second revision
+ * * etc…
+ * * -2 = the second most recent revision
+ * * -1 = the most recent revision
+ *
+ * @param string $filename Filename
+ * @param array $options Download options
+ *
+ * @return resource
+ * @throws FileNotFoundException
+ */
+ public function openDownloadStreamByName($filename, array $options = [])
+ {
+ $options+=['revision'=>-1];
+
+ $file=$this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);
+
+ if ($file===null) {
+ throw FileNotFoundException::byFilenameAndRevision(
+ $filename,
+ $options['revision'],
+ $this->getFilesNamespace()
+ );
+ }
+
+ return $this->openDownloadStreamByFile($file);
+ }
+
+ /**
+ * Opens a writable stream for writing a GridFS file.
+ *
+ * Supported options:
+ *
+ * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
+ * bucket's chunk size.
+ *
+ * @param string $filename Filename
+ * @param array $options Upload options
+ *
+ * @return resource
+ */
+ public function openUploadStream($filename, array $options = [])
+ {
+ $options+=['chunkSizeBytes'=>$this->options['chunkSizeBytes']];
+
+ $path =$this->createPathForUpload();
+ $context=stream_context_create([
+ self::$streamWrapperProtocol=>[
+ 'collectionWrapper'=>$this->collectionWrapper,
+ 'filename' =>$filename,
+ 'options' =>$options,
+ ],
+ ]);
+
+ return fopen($path, 'w', false, $context);
+ }
+
+ /**
+ * Renames the GridFS file with the specified ID.
+ *
+ * @param mixed $id File ID
+ * @param string $newFilename New filename
+ *
+ * @throws FileNotFoundException
+ */
+ public function rename($id, $newFilename)
+ {
+ $updateResult=$this->collectionWrapper->updateFilenameForId($id, $newFilename);
+
+ if ($updateResult->getModifiedCount()===1) {
+ return;
+ }
+
+ /* If the update resulted in no modification, it's possible that the
+ * file did not exist, in which case we must raise an error. Checking
+ * the write result's matched count will be most efficient, but fall
+ * back to a findOne operation if necessary (i.e. legacy writes).
+ */
+ $found=$updateResult->getMatchedCount()!==null
+ ?$updateResult->getMatchedCount()===1
+ :$this->collectionWrapper->findFileById($id)!==null;
+
+ if (!$found) {
+ throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+ }
+ }
+
+ /**
+ * Writes the contents of a readable stream to a GridFS file.
+ *
+ * Supported options:
+ *
+ * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
+ * bucket's chunk size.
+ *
+ * @param string $filename Filename
+ * @param resource $source Readable stream
+ * @param array $options Stream options
+ *
+ * @return ObjectId ID of the newly created GridFS file
+ * @throws InvalidArgumentException
+ */
+ public function uploadFromStream($filename, $source, array $options = [])
+ {
+ $options+=['chunkSizeBytes'=>$this->options['chunkSizeBytes']];
+
+ $stream=new WritableStream($this->collectionWrapper, $filename, $options);
+
+ return $stream->uploadFromStream($source);
+ }
+
+ /**
+ * Creates a path for an existing GridFS file.
+ *
+ * @param stdClass $file GridFS file document
+ *
+ * @return string
+ */
+ private function createPathForFile(stdClass $file)
+ {
+ if (!is_object($file->_id)||method_exists($file->_id, '__toString')) {
+ $id=(string)$file->_id;
+ } else {
+ $id=\MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id'=>$file->_id]));
+ }
+
+ return sprintf(
+ '%s://%s/%s.files/%s',
+ self::$streamWrapperProtocol,
+ urlencode($this->databaseName),
+ urlencode($this->options['bucketName']),
+ urlencode($id)
+ );
+ }
+
+ /**
+ * Creates a path for a new GridFS file, which does not yet have an ID.
+ *
+ * @return string
+ */
+ private function createPathForUpload()
+ {
+ return sprintf(
+ '%s://%s/%s.files',
+ self::$streamWrapperProtocol,
+ urlencode($this->databaseName),
+ urlencode($this->options['bucketName'])
+ );
+ }
+
+ /**
+ * Returns the names of the files collection.
+ *
+ * @return string
+ */
+ private function getFilesNamespace()
+ {
+ return sprintf('%s.%s.files', $this->databaseName, $this->options['bucketName']);
+ }
+
+ /**
+ * Opens a readable stream for the GridFS file.
+ *
+ * @param stdClass $file GridFS file document
+ *
+ * @return resource
+ */
+ private function openDownloadStreamByFile(stdClass $file)
+ {
+ $path =$this->createPathForFile($file);
+ $context=stream_context_create([
+ self::$streamWrapperProtocol=>[
+ 'collectionWrapper'=>$this->collectionWrapper,
+ 'file' =>$file,
+ ],
+ ]);
+
+ return fopen($path, 'r', false, $context);
+ }
+
+ /**
+ * Registers the GridFS stream wrapper if it is not already registered.
+ */
+ private function registerStreamWrapper()
+ {
+ if (in_array(self::$streamWrapperProtocol, stream_get_wrappers())) {
+ return;
+ }
+
+ StreamWrapper::register(self::$streamWrapperProtocol);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/GridFS/CollectionWrapper.php b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/CollectionWrapper.php
new file mode 100755
index 000000000..142372b60
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/CollectionWrapper.php
@@ -0,0 +1,283 @@
+filesCollection =new Collection(
+ $manager,
+ $databaseName,
+ sprintf('%s.files', $bucketName),
+ $collectionOptions
+ );
+ $this->chunksCollection=new Collection(
+ $manager,
+ $databaseName,
+ sprintf('%s.chunks', $bucketName),
+ $collectionOptions
+ );
+ }
+
+ /**
+ * Deletes all GridFS chunks for a given file ID.
+ *
+ * @param mixed $id
+ */
+ public function deleteChunksByFilesId($id)
+ {
+ $this->chunksCollection->deleteMany(['files_id'=>$id]);
+ }
+
+ /**
+ * Deletes a GridFS file and related chunks by ID.
+ *
+ * @param mixed $id
+ */
+ public function deleteFileAndChunksById($id)
+ {
+ $this->filesCollection->deleteOne(['_id'=>$id]);
+ $this->chunksCollection->deleteMany(['files_id'=>$id]);
+ }
+
+ /**
+ * Drops the GridFS files and chunks collections.
+ */
+ public function dropCollections()
+ {
+ $this->filesCollection->drop();
+ $this->chunksCollection->drop();
+ }
+
+ /**
+ * Finds a GridFS file document for a given filename and revision.
+ *
+ * Revision numbers are defined as follows:
+ *
+ * * 0 = the original stored file
+ * * 1 = the first revision
+ * * 2 = the second revision
+ * * etc…
+ * * -2 = the second most recent revision
+ * * -1 = the most recent revision
+ *
+ * @see Bucket::downloadToStreamByName()
+ * @see Bucket::openDownloadStreamByName()
+ *
+ * @param string $filename
+ * @param integer $revision
+ *
+ * @return stdClass|null
+ */
+ public function findFileByFilenameAndRevision($filename, $revision)
+ {
+ $filename=(string)$filename;
+ $revision=(integer)$revision;
+
+ if ($revision<0) {
+ $skip =abs($revision)-1;
+ $sortOrder=-1;
+ } else {
+ $skip =$revision;
+ $sortOrder=1;
+ }
+
+ return $this->filesCollection->findOne(['filename'=>$filename], [
+ 'skip' =>$skip,
+ 'sort' =>['uploadDate'=>$sortOrder],
+ 'typeMap'=>['root'=>'stdClass'],
+ ]);
+ }
+
+ /**
+ * Finds a GridFS file document for a given ID.
+ *
+ * @param mixed $id
+ *
+ * @return stdClass|null
+ */
+ public function findFileById($id)
+ {
+ return $this->filesCollection->findOne(['_id'=>$id], ['typeMap'=>['root'=>'stdClass']]);
+ }
+
+ /**
+ * Finds documents from the GridFS bucket's files collection.
+ *
+ * @see Find::__construct() for supported options
+ *
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Additional options
+ *
+ * @return Cursor
+ */
+ public function findFiles($filter, array $options = [])
+ {
+ return $this->filesCollection->find($filter, $options);
+ }
+
+ // TODO: Remove this
+ public function getChunksCollection()
+ {
+ return $this->chunksCollection;
+ }
+
+ /**
+ * Returns a chunks iterator for a given file ID.
+ *
+ * @param mixed $id
+ *
+ * @return IteratorIterator
+ */
+ public function getChunksIteratorByFilesId($id)
+ {
+ $cursor=$this->chunksCollection->find(['files_id'=>$id], [
+ 'sort' =>['n'=>1],
+ 'typeMap'=>['root'=>'stdClass'],
+ ]);
+
+ return new IteratorIterator($cursor);
+ }
+
+ // TODO: Remove this
+ public function getFilesCollection()
+ {
+ return $this->filesCollection;
+ }
+
+ /**
+ * Inserts a document into the chunks collection.
+ *
+ * @param array|object $chunk Chunk document
+ */
+ public function insertChunk($chunk)
+ {
+ if (!$this->checkedIndexes) {
+ $this->ensureIndexes();
+ }
+
+ $this->chunksCollection->insertOne($chunk);
+ }
+
+ /**
+ * Inserts a document into the files collection.
+ *
+ * The file document should be inserted after all chunks have been inserted.
+ *
+ * @param array|object $file File document
+ */
+ public function insertFile($file)
+ {
+ if (!$this->checkedIndexes) {
+ $this->ensureIndexes();
+ }
+
+ $this->filesCollection->insertOne($file);
+ }
+
+ /**
+ * Updates the filename field in the file document for a given ID.
+ *
+ * @param mixed $id
+ * @param string $filename
+ *
+ * @return UpdateResult
+ */
+ public function updateFilenameForId($id, $filename)
+ {
+ return $this->filesCollection->updateOne(['_id'=>$id], ['$set'=>['filename'=>(string)$filename]]);
+ }
+
+ /**
+ * Create an index on the chunks collection if it does not already exist.
+ */
+ private function ensureChunksIndex()
+ {
+ foreach ($this->chunksCollection->listIndexes() as $index) {
+ if ($index->isUnique()&&$index->getKey()===['files_id'=>1,'n'=>1]) {
+ return;
+ }
+ }
+
+ $this->chunksCollection->createIndex(['files_id'=>1,'n'=>1], ['unique'=>true]);
+ }
+
+ /**
+ * Create an index on the files collection if it does not already exist.
+ */
+ private function ensureFilesIndex()
+ {
+ foreach ($this->filesCollection->listIndexes() as $index) {
+ if ($index->getKey()===['filename'=>1,'uploadDate'=>1]) {
+ return;
+ }
+ }
+
+ $this->filesCollection->createIndex(['filename'=>1,'uploadDate'=>1]);
+ }
+
+ /**
+ * Ensure indexes on the files and chunks collections exist.
+ *
+ * This method is called once before the first write operation on a GridFS
+ * bucket. Indexes are only be created if the files collection is empty.
+ */
+ private function ensureIndexes()
+ {
+ if ($this->checkedIndexes) {
+ return;
+ }
+
+ $this->checkedIndexes=true;
+
+ if (!$this->isFilesCollectionEmpty()) {
+ return;
+ }
+
+ $this->ensureFilesIndex();
+ $this->ensureChunksIndex();
+ }
+
+ /**
+ * Returns whether the files collection is empty.
+ *
+ * @return boolean
+ */
+ private function isFilesCollectionEmpty()
+ {
+ return null===$this->filesCollection->findOne([], [
+ 'readPreference'=>new ReadPreference(ReadPreference::RP_PRIMARY),
+ 'projection' =>['_id'=>1],
+ ]);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/GridFS/Exception/CorruptFileException.php b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/Exception/CorruptFileException.php
new file mode 100755
index 000000000..e5008377d
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/Exception/CorruptFileException.php
@@ -0,0 +1,46 @@
+$id]));
+
+ return new static(sprintf('File "%s" not found in "%s"', $json, $namespace));
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/GridFS/ReadableStream.php b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/ReadableStream.php
new file mode 100755
index 000000000..93cbafb27
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/ReadableStream.php
@@ -0,0 +1,183 @@
+file=$file;
+
+ $this->chunksIterator=$collectionWrapper->getChunksIteratorByFilesId($this->file->_id);
+ $this->numChunks =($file->length>=0)?ceil($file->length/$file->chunkSize):0;
+ $this->initEmptyBuffer();
+ }
+
+ public function close()
+ {
+ fclose($this->buffer);
+ }
+
+ /**
+ * Read bytes from the stream.
+ *
+ * Note: this method may return a string smaller than the requested length
+ * if data is not available to be read.
+ *
+ * @param integer $numBytes Number of bytes to read
+ *
+ * @return string
+ */
+ public function downloadNumBytes($numBytes)
+ {
+ if ($this->bufferFresh) {
+ rewind($this->buffer);
+ $this->bufferFresh=false;
+ }
+
+ // TODO: Should we be checking for fread errors here?
+ $output=fread($this->buffer, $numBytes);
+
+ if (strlen($output)==$numBytes) {
+ return $output;
+ }
+
+ $this->initEmptyBuffer();
+
+ $bytesLeft=$numBytes-strlen($output);
+
+ while (strlen($output)<$numBytes&&$this->advanceChunks()) {
+ $bytesLeft=$numBytes-strlen($output);
+ $output.=substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft);
+ }
+
+ if (!
+ $this->iteratorEmpty &&
+ $this->file->length > 0 &&
+ $bytesLeft < strlen($this->chunksIterator->current()->data->getData())
+ ) {
+ fwrite($this->buffer, substr($this->chunksIterator->current()->data->getData(), $bytesLeft));
+ $this->bufferEmpty=false;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Writes the contents of this GridFS file to a writable stream.
+ *
+ * @param resource $destination Writable stream
+ *
+ * @throws InvalidArgumentException
+ */
+ public function downloadToStream($destination)
+ {
+ if (!is_resource($destination)||get_resource_type($destination)!="stream") {
+ throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
+ }
+
+ while ($this->advanceChunks()) {
+ // TODO: Should we be checking for fwrite errors here?
+ fwrite($destination, $this->chunksIterator->current()->data->getData());
+ }
+ }
+
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ public function getId()
+ {
+ return $this->file->_id;
+ }
+
+ public function getSize()
+ {
+ return $this->file->length;
+ }
+
+ public function isEOF()
+ {
+ return ($this->iteratorEmpty&&$this->bufferEmpty);
+ }
+
+ private function advanceChunks()
+ {
+ if ($this->chunkOffset>=$this->numChunks) {
+ $this->iteratorEmpty=true;
+
+ return false;
+ }
+
+ if ($this->firstCheck) {
+ $this->chunksIterator->rewind();
+ $this->firstCheck=false;
+ } else {
+ $this->chunksIterator->next();
+ }
+
+ if (!$this->chunksIterator->valid()) {
+ throw CorruptFileException::missingChunk($this->chunkOffset);
+ }
+
+ if ($this->chunksIterator->current()->n!=$this->chunkOffset) {
+ throw CorruptFileException::unexpectedIndex($this->chunksIterator->current()->n, $this->chunkOffset);
+ }
+
+ $actualChunkSize=strlen($this->chunksIterator->current()->data->getData());
+
+ $expectedChunkSize=($this->chunkOffset==$this->numChunks-1)
+ ?($this->file->length-$this->bytesSeen)
+ :$this->file->chunkSize;
+
+ if ($actualChunkSize!=$expectedChunkSize) {
+ throw CorruptFileException::unexpectedSize($actualChunkSize, $expectedChunkSize);
+ }
+
+ $this->bytesSeen+=$actualChunkSize;
+ $this->chunkOffset++;
+
+ return true;
+ }
+
+ private function initEmptyBuffer()
+ {
+ if (isset($this->buffer)) {
+ fclose($this->buffer);
+ }
+
+ $this->buffer =fopen("php://temp", "w+");
+ $this->bufferEmpty=true;
+ $this->bufferFresh=true;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/GridFS/StreamWrapper.php b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/StreamWrapper.php
new file mode 100755
index 000000000..373b5be6e
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/StreamWrapper.php
@@ -0,0 +1,238 @@
+stream->getId();
+ }
+
+ /**
+ * Register the GridFS stream wrapper.
+ *
+ * @param string $protocol Protocol to use for stream_wrapper_register()
+ */
+ public static function register($protocol = 'gridfs')
+ {
+ if (in_array($protocol, stream_get_wrappers())) {
+ stream_wrapper_unregister($protocol);
+ }
+
+ stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL);
+ }
+
+ /**
+ * Closes the stream.
+ *
+ * @see http://php.net/manual/en/streamwrapper.stream-close.php
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_close()
+ {
+ // @codingStandardsIgnoreEnd
+ $this->stream->close();
+ }
+
+ /**
+ * Returns whether the file pointer is at the end of the stream.
+ *
+ * @see http://php.net/manual/en/streamwrapper.stream-eof.php
+ * @return boolean
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_eof()
+ {
+ // @codingStandardsIgnoreEnd
+ return $this->stream->isEOF();
+ }
+
+ /**
+ * Opens the stream.
+ *
+ * @see http://php.net/manual/en/streamwrapper.stream-open.php
+ *
+ * @param string $path Path to the file resource
+ * @param string $mode Mode used to open the file (only "r" and "w" are supported)
+ * @param integer $options Additional flags set by the streams API
+ * @param string $openedPath Not used
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_open($path, $mode, $options, &$openedPath)
+ {
+ // @codingStandardsIgnoreEnd
+ $this->initProtocol($path);
+ $this->mode=$mode;
+
+ if ($mode==='r') {
+ return $this->initReadableStream();
+ }
+
+ if ($mode==='w') {
+ return $this->initWritableStream();
+ }
+
+ return false;
+ }
+
+ /**
+ * Read bytes from the stream.
+ *
+ * Note: this method may return a string smaller than the requested length
+ * if data is not available to be read.
+ *
+ * @see http://php.net/manual/en/streamwrapper.stream-read.php
+ *
+ * @param integer $count Number of bytes to read
+ *
+ * @return string
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_read($count)
+ {
+ // @codingStandardsIgnoreEnd
+ // TODO: Ensure that $this->stream is a ReadableStream
+ return $this->stream->downloadNumBytes($count);
+ }
+
+ /**
+ * Return information about the stream.
+ *
+ * @see http://php.net/manual/en/streamwrapper.stream-stat.php
+ * @return array
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_stat()
+ {
+ // @codingStandardsIgnoreEnd
+ $stat=$this->getStatTemplate();
+
+ $stat[2]=$stat['mode']=$this->mode;
+ $stat[7]=$stat['size']=$this->stream->getSize();
+
+ return $stat;
+ }
+
+ /**
+ * Write bytes to the stream.
+ *
+ * @see http://php.net/manual/en/streamwrapper.stream-write.php
+ *
+ * @param string $data Data to write
+ *
+ * @return integer The number of bytes successfully stored
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_write($data)
+ {
+ // @codingStandardsIgnoreEnd
+ // TODO: Ensure that $this->stream is a WritableStream
+ $this->stream->insertChunks($data);
+
+ return strlen($data);
+ }
+
+ /**
+ * Returns a stat template with default values.
+ *
+ * @return array
+ */
+ private function getStatTemplate()
+ {
+ return [
+ 0 =>0,
+ 'dev' =>0,
+ 1 =>0,
+ 'ino' =>0,
+ 2 =>0,
+ 'mode' =>0,
+ 3 =>0,
+ 'nlink' =>0,
+ 4 =>0,
+ 'uid' =>0,
+ 5 =>0,
+ 'gid' =>0,
+ 6 =>-1,
+ 'rdev' =>-1,
+ 7 =>0,
+ 'size' =>0,
+ 8 =>0,
+ 'atime' =>0,
+ 9 =>0,
+ 'mtime' =>0,
+ 10 =>0,
+ 'ctime' =>0,
+ 11 =>-1,
+ 'blksize'=>-1,
+ 12 =>-1,
+ 'blocks' =>-1,
+ ];
+ }
+
+ /**
+ * Initialize the protocol from the given path.
+ *
+ * @see StreamWrapper::stream_open()
+ *
+ * @param string $path
+ */
+ private function initProtocol($path)
+ {
+ $parts =explode('://', $path, 2);
+ $this->protocol=$parts[0]?:'gridfs';
+ }
+
+ /**
+ * Initialize the internal stream for reading.
+ *
+ * @see StreamWrapper::stream_open()
+ * @return boolean
+ */
+ private function initReadableStream()
+ {
+ $context=stream_context_get_options($this->context);
+
+ $this->stream=new ReadableStream(
+ $context[ $this->protocol ]['collectionWrapper'],
+ $context[ $this->protocol ]['file']
+ );
+
+ return true;
+ }
+
+ /**
+ * Initialize the internal stream for writing.
+ *
+ * @see StreamWrapper::stream_open()
+ * @return boolean
+ */
+ private function initWritableStream()
+ {
+ $context=stream_context_get_options($this->context);
+
+ $this->stream=new WritableStream(
+ $context[ $this->protocol ]['collectionWrapper'],
+ $context[ $this->protocol ]['filename'],
+ $context[ $this->protocol ]['options']
+ );
+
+ return true;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/GridFS/WritableStream.php b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/WritableStream.php
new file mode 100755
index 000000000..07ce1ddde
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/GridFS/WritableStream.php
@@ -0,0 +1,262 @@
+new ObjectId,
+ 'chunkSizeBytes'=>self::$defaultChunkSizeBytes,
+ ];
+
+ if (isset($options['aliases'])&&!Functions::isStringArray($options['aliases'])) {
+ throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings');
+ }
+
+ if (isset($options['chunkSizeBytes'])&&!is_integer($options['chunkSizeBytes'])) {
+ throw InvalidArgumentException::invalidType(
+ '"chunkSizeBytes" option',
+ $options['chunkSizeBytes'],
+ 'integer'
+ );
+ }
+
+ if (isset($options['contentType'])&&!is_string($options['contentType'])) {
+ throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string');
+ }
+
+ if (isset($options['metadata'])&&!is_array($options['metadata'])&&!is_object($options['metadata'])) {
+ throw InvalidArgumentException::invalidType('"metadata" option', $options['metadata'], 'array or object');
+ }
+
+ $this->chunkSize =$options['chunkSizeBytes'];
+ $this->collectionWrapper=$collectionWrapper;
+ $this->buffer =fopen('php://temp', 'w+');
+ $this->ctx =hash_init('md5');
+
+ $this->file=[
+ '_id' =>$options['_id'],
+ 'chunkSize' =>$this->chunkSize,
+ 'filename' =>(string)$filename,
+ // TODO: This is necessary until PHPC-536 is implemented
+ 'uploadDate'=>new UTCDateTime(floor(microtime(true)*1000)),
+ ]+array_intersect_key($options, ['aliases'=>1,'contentType'=>1,'metadata'=>1]);
+ }
+
+ /**
+ * Closes an active stream and flushes all buffered data to GridFS.
+ */
+ public function close()
+ {
+ if ($this->isClosed) {
+ // TODO: Should this be an error condition? e.g. BadMethodCallException
+ return;
+ }
+
+ rewind($this->buffer);
+ $cached=stream_get_contents($this->buffer);
+
+ if (strlen($cached)>0) {
+ $this->insertChunk($cached);
+ }
+
+ fclose($this->buffer);
+ $this->fileCollectionInsert();
+ $this->isClosed=true;
+ }
+
+ public function getChunkSize()
+ {
+ return $this->chunkSize;
+ }
+
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ public function getId()
+ {
+ return $this->file['_id'];
+ }
+
+ public function getLength()
+ {
+ return $this->length;
+ }
+
+ public function getSize()
+ {
+ return $this->length;
+ }
+
+ /**
+ * Inserts binary data into GridFS via chunks.
+ *
+ * Data will be buffered internally until chunkSizeBytes are accumulated, at
+ * which point a chunk's worth of data will be inserted and the buffer
+ * reset.
+ *
+ * @param string $toWrite Binary data to write
+ *
+ * @return integer
+ */
+ public function insertChunks($toWrite)
+ {
+ if ($this->isClosed) {
+ // TODO: Should this be an error condition? e.g. BadMethodCallException
+ return;
+ }
+
+ $readBytes=0;
+
+ while ($readBytes!=strlen($toWrite)) {
+ $addToBuffer=substr($toWrite, $readBytes, $this->chunkSize-$this->bufferLength);
+ fwrite($this->buffer, $addToBuffer);
+ $readBytes+=strlen($addToBuffer);
+ $this->bufferLength+=strlen($addToBuffer);
+
+ if ($this->bufferLength==$this->chunkSize) {
+ rewind($this->buffer);
+ $this->insertChunk(stream_get_contents($this->buffer));
+ ftruncate($this->buffer, 0);
+ $this->bufferLength=0;
+ }
+ }
+
+ return $readBytes;
+ }
+
+ public function isEOF()
+ {
+ return $this->isClosed;
+ }
+
+ /**
+ * Writes the contents of a readable stream to a GridFS file.
+ *
+ * @param resource $source Readable stream
+ *
+ * @return ObjectId
+ * @throws InvalidArgumentException
+ */
+ public function uploadFromStream($source)
+ {
+ if (!is_resource($source)||get_resource_type($source)!="stream") {
+ throw InvalidArgumentException::invalidType('$source', $source, 'resource');
+ }
+
+ while ($data=$this->readChunk($source)) {
+ $this->insertChunk($data);
+ }
+
+ return $this->fileCollectionInsert();
+ }
+
+ private function abort()
+ {
+ $this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
+ $this->isClosed=true;
+ }
+
+ private function fileCollectionInsert()
+ {
+ if ($this->isClosed) {
+ // TODO: Should this be an error condition? e.g. BadMethodCallException
+ return;
+ }
+
+ $md5=hash_final($this->ctx);
+
+ $this->file['length']=$this->length;
+ $this->file['md5'] =$md5;
+
+ $this->collectionWrapper->insertFile($this->file);
+
+ return $this->file['_id'];
+ }
+
+ private function insertChunk($data)
+ {
+ if ($this->isClosed) {
+ // TODO: Should this be an error condition? e.g. BadMethodCallException
+ return;
+ }
+
+ $toUpload=[
+ 'files_id'=>$this->file['_id'],
+ 'n' =>$this->chunkOffset,
+ 'data' =>new Binary($data, Binary::TYPE_GENERIC),
+ ];
+
+ hash_update($this->ctx, $data);
+
+ $this->collectionWrapper->insertChunk($toUpload);
+ $this->length+=strlen($data);
+ $this->chunkOffset++;
+ }
+
+ private function readChunk($source)
+ {
+ try {
+ $data=fread($source, $this->chunkSize);
+ } catch (DriverException $e) {
+ $this->abort();
+ throw $e;
+ }
+
+ return $data;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/InsertManyResult.php b/Library/Phalcon/Db/Adapter/MongoDB/InsertManyResult.php
new file mode 100755
index 000000000..a12c86fcb
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/InsertManyResult.php
@@ -0,0 +1,75 @@
+writeResult =$writeResult;
+ $this->insertedIds =$insertedIds;
+ $this->isAcknowledged=$writeResult->isAcknowledged();
+ }
+
+ /**
+ * Return the number of documents that were inserted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see InsertManyResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getInsertedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getInsertedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return a map of the inserted documents' IDs.
+ *
+ * The index of each ID in the map corresponds to the document's position in
+ * the bulk operation. If the document had an ID prior to insertion (i.e.
+ * the driver did not generate an ID), this will contain its "_id" field
+ * value. Any driver-generated ID will be an MongoDB\BSON\ObjectID instance.
+ *
+ * @return mixed[]
+ */
+ public function getInsertedIds()
+ {
+ return $this->insertedIds;
+ }
+
+ /**
+ * Return whether this insert result was acknowledged by the server.
+ *
+ * If the insert was not acknowledged, other fields from the WriteResult
+ * (e.g. insertedCount) will be undefined.
+ *
+ * @return boolean
+ */
+ public function isAcknowledged()
+ {
+ return $this->writeResult->isAcknowledged();
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/InsertOneResult.php b/Library/Phalcon/Db/Adapter/MongoDB/InsertOneResult.php
new file mode 100755
index 000000000..a06e1051c
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/InsertOneResult.php
@@ -0,0 +1,78 @@
+writeResult =$writeResult;
+ $this->insertedId =$insertedId;
+ $this->isAcknowledged=$writeResult->isAcknowledged();
+ }
+
+ /**
+ * Return the number of documents that were inserted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see InsertOneResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getInsertedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getInsertedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the inserted document's ID.
+ *
+ * If the document already an ID prior to insertion (i.e. the driver did not
+ * need to generate an ID), this will contain its "_id". Any
+ * driver-generated ID will be an MongoDB\BSON\ObjectID instance.
+ *
+ * @return mixed
+ */
+ public function getInsertedId()
+ {
+ return $this->insertedId;
+ }
+
+ /**
+ * Return whether this insert was acknowledged by the server.
+ *
+ * If the insert was not acknowledged, other fields from the WriteResult
+ * (e.g. insertedCount) will be undefined.
+ *
+ * If the insert was not acknowledged, other fields from the WriteResult
+ * (e.g. insertedCount) will be undefined and their getter methods should
+ * not be invoked.
+ *
+ * @return boolean
+ */
+ public function isAcknowledged()
+ {
+ return $this->writeResult->isAcknowledged();
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/BSONArray.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/BSONArray.php
new file mode 100755
index 000000000..951a16fd1
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/BSONArray.php
@@ -0,0 +1,62 @@
+exchangeArray($properties);
+
+ return $array;
+ }
+
+ /**
+ * Serialize the array to BSON.
+ *
+ * The array data will be numerically reindexed to ensure that it is stored
+ * as a BSON array.
+ *
+ * @see http://php.net/mongodb-bson-serializable.bsonserialize
+ * @return array
+ */
+ public function bsonSerialize()
+ {
+ return array_values($this->getArrayCopy());
+ }
+
+ /**
+ * Unserialize the document to BSON.
+ *
+ * @see http://php.net/mongodb-bson-unserializable.bsonunserialize
+ *
+ * @param array $data Array data
+ */
+ public function bsonUnserialize(array $data)
+ {
+ self::__construct($data);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/BSONDocument.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/BSONDocument.php
new file mode 100755
index 000000000..cfceca74a
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/BSONDocument.php
@@ -0,0 +1,72 @@
+exchangeArray($properties);
+
+ return $document;
+ }
+
+ /**
+ * Serialize the document to BSON.
+ *
+ * @see http://php.net/mongodb-bson-serializable.bsonserialize
+ * @return object
+ */
+ public function bsonSerialize()
+ {
+ return (object)$this->getArrayCopy();
+ }
+
+ /**
+ * Unserialize the document to BSON.
+ *
+ * @see http://php.net/mongodb-bson-unserializable.bsonunserialize
+ *
+ * @param array $data Array data
+ */
+ public function bsonUnserialize(array $data)
+ {
+ parent::__construct($data, ArrayObject::ARRAY_AS_PROPS);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/CollectionInfo.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/CollectionInfo.php
new file mode 100755
index 000000000..d61abfe2c
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/CollectionInfo.php
@@ -0,0 +1,90 @@
+info=$info;
+ }
+
+ /**
+ * Return the collection info as an array.
+ *
+ * @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return $this->info;
+ }
+
+ /**
+ * Return the maximum number of documents to keep in the capped collection.
+ *
+ * @return integer|null
+ */
+ public function getCappedMax()
+ {
+ return isset($this->info['options']['max'])?(integer)$this->info['options']['max']:null;
+ }
+
+ /**
+ * Return the maximum size (in bytes) of the capped collection.
+ *
+ * @return integer|null
+ */
+ public function getCappedSize()
+ {
+ return isset($this->info['options']['size'])?(integer)$this->info['options']['size']:null;
+ }
+
+ /**
+ * Return the collection name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return (string)$this->info['name'];
+ }
+
+ /**
+ * Return the collection options.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return isset($this->info['options'])?(array)$this->info['options']:[];
+ }
+
+ /**
+ * Return whether the collection is a capped collection.
+ *
+ * @return boolean
+ */
+ public function isCapped()
+ {
+ return !empty($this->info['options']['capped']);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/CollectionInfoCommandIterator.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/CollectionInfoCommandIterator.php
new file mode 100755
index 000000000..a31552cd7
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/CollectionInfoCommandIterator.php
@@ -0,0 +1,31 @@
+info=$info;
+ }
+
+ /**
+ * Return the collection info as an array.
+ *
+ * @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return $this->info;
+ }
+
+ /**
+ * Return the database name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return (string)$this->info['name'];
+ }
+
+ /**
+ * Return the databases size on disk (in bytes).
+ *
+ * @return integer
+ */
+ public function getSizeOnDisk()
+ {
+ return (integer)$this->info['sizeOnDisk'];
+ }
+
+ /**
+ * Return whether the database is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return (boolean)$this->info['empty'];
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/DatabaseInfoIterator.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/DatabaseInfoIterator.php
new file mode 100755
index 000000000..031a9ba90
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/DatabaseInfoIterator.php
@@ -0,0 +1,23 @@
+databases=$databases;
+ }
+
+ /**
+ * Return the current element as a DatabaseInfo instance.
+ *
+ * @see DatabaseInfoIterator::current()
+ * @see http://php.net/iterator.current
+ * @return DatabaseInfo
+ */
+ public function current()
+ {
+ return new DatabaseInfo(current($this->databases));
+ }
+
+ /**
+ * Return the key of the current element.
+ *
+ * @see http://php.net/iterator.key
+ * @return integer
+ */
+ public function key()
+ {
+ return key($this->databases);
+ }
+
+ /**
+ * Move forward to next element.
+ *
+ * @see http://php.net/iterator.next
+ */
+ public function next()
+ {
+ next($this->databases);
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ *
+ * @see http://php.net/iterator.rewind
+ */
+ public function rewind()
+ {
+ reset($this->databases);
+ }
+
+ /**
+ * Checks if current position is valid.
+ *
+ * @see http://php.net/iterator.valid
+ * @return boolean
+ */
+ public function valid()
+ {
+ return key($this->databases)!==null;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/IndexInfo.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/IndexInfo.php
new file mode 100755
index 000000000..fed6e481d
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/IndexInfo.php
@@ -0,0 +1,176 @@
+info=$info;
+ }
+
+ /**
+ * Return the collection info as an array.
+ *
+ * @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return $this->info;
+ }
+
+ /**
+ * Return the index key.
+ *
+ * @return array
+ */
+ public function getKey()
+ {
+ return (array)$this->info['key'];
+ }
+
+ /**
+ * Return the index name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return (string)$this->info['name'];
+ }
+
+ /**
+ * Return the index namespace (e.g. "db.collection").
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return (string)$this->info['ns'];
+ }
+
+ /**
+ * Return the index version.
+ *
+ * @return integer
+ */
+ public function getVersion()
+ {
+ return (integer)$this->info['v'];
+ }
+
+ /**
+ * Return whether this is a sparse index.
+ *
+ * @see http://docs.mongodb.org/manual/core/index-sparse/
+ * @return boolean
+ */
+ public function isSparse()
+ {
+ return !empty($this->info['sparse']);
+ }
+
+ /**
+ * Return whether this is a TTL index.
+ *
+ * @see http://docs.mongodb.org/manual/core/index-ttl/
+ * @return boolean
+ */
+ public function isTtl()
+ {
+ return array_key_exists('expireAfterSeconds', $this->info);
+ }
+
+ /**
+ * Return whether this is a unique index.
+ *
+ * @see http://docs.mongodb.org/manual/core/index-unique/
+ * @return boolean
+ */
+ public function isUnique()
+ {
+ return !empty($this->info['unique']);
+ }
+
+ /**
+ * Check whether a field exists in the index information.
+ *
+ * @see http://php.net/arrayaccess.offsetexists
+ *
+ * @param mixed $key
+ *
+ * @return boolean
+ */
+ public function offsetExists($key)
+ {
+ return array_key_exists($key, $this->info);
+ }
+
+ /**
+ * Return the field's value from the index information.
+ *
+ * This method satisfies the Enumerating Indexes specification's requirement
+ * that index fields be made accessible under their original names. It may
+ * also be used to access fields that do not have a helper method.
+ *
+ * @see http://php.net/arrayaccess.offsetget
+ * @see
+ * https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information
+ *
+ * @param mixed $key
+ *
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ return $this->info[ $key ];
+ }
+
+ /**
+ * Not supported.
+ *
+ * @see http://php.net/arrayaccess.offsetset
+ * @throws BadMethodCallException
+ */
+ public function offsetSet($key, $value)
+ {
+ throw BadMethodCallException::classIsImmutable(__CLASS__);
+ }
+
+ /**
+ * Not supported.
+ *
+ * @see http://php.net/arrayaccess.offsetunset
+ * @throws BadMethodCallException
+ */
+ public function offsetUnset($key)
+ {
+ throw BadMethodCallException::classIsImmutable(__CLASS__);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Model/IndexInfoIterator.php b/Library/Phalcon/Db/Adapter/MongoDB/Model/IndexInfoIterator.php
new file mode 100755
index 000000000..9c9100ca2
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Model/IndexInfoIterator.php
@@ -0,0 +1,23 @@
+ $order) {
+ if (!is_int($order)&&!is_float($order)&&!is_string($order)) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('order value for "%s" field within "key" option', $fieldName),
+ $order,
+ 'numeric or string'
+ );
+ }
+ }
+
+ if (!isset($index['ns'])) {
+ throw new InvalidArgumentException('Required "ns" option is missing from index specification');
+ }
+
+ if (!is_string($index['ns'])) {
+ throw InvalidArgumentException::invalidType('"ns" option', $index['ns'], 'string');
+ }
+
+ if (!isset($index['name'])) {
+ $index['name']=Functions::generateIndexName($index['key']);
+ }
+
+ if (!is_string($index['name'])) {
+ throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string');
+ }
+
+ $this->index=$index;
+ }
+
+ /**
+ * Return the index name.
+ *
+ * @param string
+ */
+ public function __toString()
+ {
+ return $this->index['name'];
+ }
+
+ /**
+ * Serialize the index information to BSON for index creation.
+ *
+ * @see MongoDB\Collection::createIndexes()
+ * @see http://php.net/mongodb-bson-serializable.bsonserialize
+ * @return array
+ */
+ public function bsonSerialize()
+ {
+ return $this->index;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/Aggregate.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Aggregate.php
new file mode 100755
index 000000000..32e233e89
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Aggregate.php
@@ -0,0 +1,258 @@
+= 2.6, this option allows users to turn off cursors if
+ * necessary to aid in mongod/mongos upgrades.
+ *
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array $pipeline List of pipeline operations
+ * @param array $options Command options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($databaseName, $collectionName, array $pipeline, array $options = [])
+ {
+ if (empty($pipeline)) {
+ throw new InvalidArgumentException('$pipeline is empty');
+ }
+
+ $expectedIndex=0;
+
+ foreach ($pipeline as $i => $operation) {
+ if ($i!==$expectedIndex) {
+ throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
+ }
+
+ if (!is_array($operation)&&!is_object($operation)) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$pipeline[%d]', $i),
+ $operation,
+ 'array or object'
+ );
+ }
+
+ $expectedIndex+=1;
+ }
+
+ $options+=[
+ 'allowDiskUse'=>false,
+ 'useCursor' =>true,
+ ];
+
+ if (!is_bool($options['allowDiskUse'])) {
+ throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean');
+ }
+
+ if (isset($options['batchSize'])&&!is_integer($options['batchSize'])) {
+ throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer');
+ }
+
+ if (isset($options['bypassDocumentValidation'])&&!is_bool($options['bypassDocumentValidation'])) {
+ throw InvalidArgumentException::invalidType(
+ '"bypassDocumentValidation" option',
+ $options['bypassDocumentValidation'],
+ 'boolean'
+ );
+ }
+
+ if (isset($options['maxTimeMS'])&&!is_integer($options['maxTimeMS'])) {
+ throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+ }
+
+ if (isset($options['readConcern'])&&!$options['readConcern'] instanceof ReadConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"readConcern" option',
+ $options['readConcern'],
+ 'MongoDB\Driver\ReadConcern'
+ );
+ }
+
+ if (isset($options['readPreference'])&&!$options['readPreference'] instanceof ReadPreference) {
+ throw InvalidArgumentException::invalidType(
+ '"readPreference" option',
+ $options['readPreference'],
+ 'MongoDB\Driver\ReadPreference'
+ );
+ }
+
+ if (isset($options['typeMap'])&&!is_array($options['typeMap'])) {
+ throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+ }
+
+ if (!is_bool($options['useCursor'])) {
+ throw InvalidArgumentException::invalidType('"useCursor" option', $options['useCursor'], 'boolean');
+ }
+
+ if (isset($options['batchSize'])&&!$options['useCursor']) {
+ throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
+ }
+
+ if (isset($options['typeMap'])&&!$options['useCursor']) {
+ throw new InvalidArgumentException('"typeMap" option should not be used if "useCursor" is false');
+ }
+
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->pipeline =$pipeline;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return Traversable
+ * @throws UnexpectedValueException if the command response was malformed
+ */
+ public function execute(Server $server)
+ {
+ $isCursorSupported=Functions::serverSupportsFeature($server, self::$wireVersionForCursor);
+ $readPreference =isset($this->options['readPreference'])?$this->options['readPreference']:null;
+
+ $command=$this->createCommand($server, $isCursorSupported);
+ $cursor =$server->executeCommand($this->databaseName, $command, $readPreference);
+
+ if ($isCursorSupported&&$this->options['useCursor']) {
+ /* The type map can only be applied to command cursors until
+ * https://jira.mongodb.org/browse/PHPC-314 is implemented.
+ */
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return $cursor;
+ }
+
+ $result=current($cursor->toArray());
+
+ if (!isset($result->result)||!is_array($result->result)) {
+ throw new UnexpectedValueException('aggregate command did not return a "result" array');
+ }
+
+ return new ArrayIterator($result->result);
+ }
+
+ /**
+ * Create the aggregate command.
+ *
+ * @param Server $server
+ * @param boolean $isCursorSupported
+ *
+ * @return Command
+ */
+ private function createCommand(Server $server, $isCursorSupported)
+ {
+ $cmd=[
+ 'aggregate'=>$this->collectionName,
+ 'pipeline' =>$this->pipeline,
+ ];
+
+ // Servers < 2.6 do not support any command options
+ if (!$isCursorSupported) {
+ return new Command($cmd);
+ }
+
+ $cmd['allowDiskUse']=$this->options['allowDiskUse'];
+
+ if (isset($this->options['bypassDocumentValidation'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForDocumentLevelValidation
+ )
+ ) {
+ $cmd['bypassDocumentValidation']=$this->options['bypassDocumentValidation'];
+ }
+
+ if (isset($this->options['maxTimeMS'])) {
+ $cmd['maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ if (isset($this->options['readConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForReadConcern
+ )
+ ) {
+ $cmd['readConcern']=Functions::readConcernAsDocument($this->options['readConcern']);
+ }
+
+ if ($this->options['useCursor']) {
+ $cmd['cursor']=isset($this->options["batchSize"])?['batchSize'=>$this->options["batchSize"]]:new stdClass;
+ }
+
+ return new Command($cmd);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/BulkWrite.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/BulkWrite.php
new file mode 100755
index 000000000..9f48057bd
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/BulkWrite.php
@@ -0,0 +1,321 @@
+ [ $filter ] ],
+ * [ 'deleteOne' => [ $filter ] ],
+ * [ 'insertOne' => [ $document ] ],
+ * [ 'replaceOne' => [ $filter, $replacement, $options ] ],
+ * [ 'updateMany' => [ $filter, $update, $options ] ],
+ * [ 'updateOne' => [ $filter, $update, $options ] ],
+ * ]
+ *
+ * Arguments correspond to the respective Operation classes; however, the
+ * writeConcern option is specified for the top-level bulk write operation
+ * instead of each individual operation.
+ *
+ * Supported options for replaceOne, updateMany, and updateOne operations:
+ *
+ * * upsert (boolean): When true, a new document is created if no document
+ * matches the query. The default is false.
+ *
+ * Supported options for the bulk write operation:
+ *
+ * * bypassDocumentValidation (boolean): If true, allows the write to opt
+ * out of document level validation.
+ *
+ * * ordered (boolean): If true, when an insert fails, return without
+ * performing the remaining writes. If false, when a write fails,
+ * continue with the remaining writes, if any. The default is true.
+ *
+ * * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+ *
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array[] $operations List of write operations
+ * @param array $options Command options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($databaseName, $collectionName, array $operations, array $options = [])
+ {
+ if (empty($operations)) {
+ throw new InvalidArgumentException('$operations is empty');
+ }
+
+ $expectedIndex=0;
+
+ foreach ($operations as $i => $operation) {
+ if ($i!==$expectedIndex) {
+ throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i));
+ }
+
+ if (!is_array($operation)) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]', $i),
+ $operation,
+ 'array'
+ );
+ }
+
+ if (count($operation)!==1) {
+ throw new InvalidArgumentException(
+ sprintf('Expected one element in $operation[%d], actually: %d', $i, count($operation))
+ );
+ }
+
+ $type=key($operation);
+ $args=current($operation);
+
+ if (!isset($args[0])&&!array_key_exists(0, $args)) {
+ throw new InvalidArgumentException(
+ sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type)
+ );
+ }
+
+ if (!is_array($args[0])&&!is_object($args[0])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][0]', $i, $type),
+ $args[0],
+ 'array or object'
+ );
+ }
+
+ switch ($type) {
+ case self::INSERT_ONE:
+ break;
+
+ case self::DELETE_MANY:
+ case self::DELETE_ONE:
+ $operations[ $i ][ $type ][1]=['limit'=>($type===self::DELETE_ONE?1:0)];
+
+ break;
+
+ case self::REPLACE_ONE:
+ if (!isset($args[1])&&!array_key_exists(1, $args)) {
+ throw new InvalidArgumentException(
+ sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)
+ );
+ }
+
+ if (!is_array($args[1])&&!is_object($args[1])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][1]', $i, $type),
+ $args[1],
+ 'array or object'
+ );
+ }
+
+
+ if (Functions::isFirstKeyOperator($args[1])) {
+ throw new InvalidArgumentException(
+ sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type)
+ );
+ }
+
+ if (!isset($args[2])) {
+ $args[2]=[];
+ }
+
+ if (!is_array($args[2])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][2]', $i, $type),
+ $args[2],
+ 'array'
+ );
+ }
+
+ $args[2]['multi']=false;
+ $args[2]+=['upsert'=>false];
+
+ if (!is_bool($args[2]['upsert'])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type),
+ $args[2]['upsert'],
+ 'boolean'
+ );
+ }
+
+ $operations[ $i ][ $type ][2]=$args[2];
+
+ break;
+
+ case self::UPDATE_MANY:
+ case self::UPDATE_ONE:
+ if (!isset($args[1])&&!array_key_exists(1, $args)) {
+ throw new InvalidArgumentException(
+ sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)
+ );
+ }
+
+ if (!is_array($args[1])&&!is_object($args[1])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][1]', $i, $type),
+ $args[1],
+ 'array or object'
+ );
+ }
+
+ if (!Functions::isFirstKeyOperator($args[1])) {
+ throw new InvalidArgumentException(
+ sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type)
+ );
+ }
+
+ if (!isset($args[2])) {
+ $args[2]=[];
+ }
+
+ if (!is_array($args[2])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][2]', $i, $type),
+ $args[2],
+ 'array'
+ );
+ }
+
+ $args[2]['multi']=($type===self::UPDATE_MANY);
+ $args[2]+=['upsert'=>false];
+
+ if (!is_bool($args[2]['upsert'])) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type),
+ $args[2]['upsert'],
+ 'boolean'
+ );
+ }
+
+ $operations[ $i ][ $type ][2]=$args[2];
+
+ break;
+
+ default:
+ throw new InvalidArgumentException(
+ sprintf('Unknown operation type "%s" in $operations[%d]', $type, $i)
+ );
+ }
+
+ $expectedIndex+=1;
+ }
+
+ $options+=['ordered'=>true];
+
+ if (isset($options['bypassDocumentValidation'])&&!is_bool($options['bypassDocumentValidation'])) {
+ throw InvalidArgumentException::invalidType(
+ '"bypassDocumentValidation" option',
+ $options['bypassDocumentValidation'],
+ 'boolean'
+ );
+ }
+
+ if (!is_bool($options['ordered'])) {
+ throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->operations =$operations;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return BulkWriteResult
+ */
+ public function execute(Server $server)
+ {
+ $options=['ordered'=>$this->options['ordered']];
+
+ if (isset($this->options['bypassDocumentValidation'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForDocumentLevelValidation
+ )
+ ) {
+ $options['bypassDocumentValidation']=$this->options['bypassDocumentValidation'];
+ }
+
+ $bulk =new Bulk($options);
+ $insertedIds=[];
+
+ foreach ($this->operations as $i => $operation) {
+ $type=key($operation);
+ $args=current($operation);
+
+ switch ($type) {
+ case self::DELETE_MANY:
+ case self::DELETE_ONE:
+ $bulk->delete($args[0], $args[1]);
+ break;
+
+ case self::INSERT_ONE:
+ $insertedId=$bulk->insert($args[0]);
+
+ if ($insertedId!==null) {
+ $insertedIds[ $i ]=$insertedId;
+ } else {
+ $insertedIds[ $i ]=Functions::extractIdFromInsertedDocument($args[0]);
+ }
+
+ break;
+
+ case self::REPLACE_ONE:
+ case self::UPDATE_MANY:
+ case self::UPDATE_ONE:
+ $bulk->update($args[0], $args[1], $args[2]);
+ }
+ }
+
+ $writeConcern=isset($this->options['writeConcern'])?$this->options['writeConcern']:null;
+ $writeResult =$server->executeBulkWrite($this->databaseName.'.'.$this->collectionName, $bulk, $writeConcern);
+
+ return new BulkWriteResult($writeResult, $insertedIds);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/Count.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Count.php
new file mode 100755
index 000000000..d37bb6a25
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Count.php
@@ -0,0 +1,169 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->filter =$filter;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return integer
+ * @throws UnexpectedValueException if the command response was malformed
+ */
+ public function execute(Server $server)
+ {
+ $readPreference=isset($this->options['readPreference'])?$this->options['readPreference']:null;
+
+ $cursor=$server->executeCommand($this->databaseName, $this->createCommand($server), $readPreference);
+ $result=current($cursor->toArray());
+
+ // Older server versions may return a float
+ if (!isset($result->n)||!(is_integer($result->n)||is_float($result->n))) {
+ throw new UnexpectedValueException('count command did not return a numeric "n" value');
+ }
+
+ return (integer)$result->n;
+ }
+
+ /**
+ * Create the count command.
+ *
+ * @param Server $server
+ *
+ * @return Command
+ */
+ private function createCommand(Server $server)
+ {
+ $cmd=['count'=>$this->collectionName];
+
+ if (!empty($this->filter)) {
+ $cmd['query']=(object)$this->filter;
+ }
+
+ foreach (['hint','limit','maxTimeMS','skip'] as $option) {
+ if (isset($this->options[ $option ])) {
+ $cmd[ $option ]=$this->options[ $option ];
+ }
+ }
+
+ if (isset($this->options['readConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForReadConcern
+ )
+ ) {
+ $cmd['readConcern']=Functions::readConcernAsDocument($this->options['readConcern']);
+ }
+
+ return new Command($cmd);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/CreateCollection.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/CreateCollection.php
new file mode 100755
index 000000000..27840abcd
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/CreateCollection.php
@@ -0,0 +1,208 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return array|object Command result document
+ */
+ public function execute(Server $server)
+ {
+ $cursor=$server->executeCommand($this->databaseName, $this->createCommand());
+
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return current($cursor->toArray());
+ }
+
+ /**
+ * Create the create command.
+ *
+ * @return Command
+ */
+ private function createCommand()
+ {
+ $cmd=['create'=>$this->collectionName];
+
+ foreach ([
+ 'autoIndexId',
+ 'capped',
+ 'flags',
+ 'max',
+ 'maxTimeMS',
+ 'size',
+ 'validationAction',
+ 'validationLevel'
+ ] as $option) {
+ if (isset($this->options[ $option ])) {
+ $cmd[ $option ]=$this->options[ $option ];
+ }
+ }
+
+ if (isset($this->options['indexOptionDefaults'])) {
+ $cmd['indexOptionDefaults']=(object)$this->options['indexOptionDefaults'];
+ }
+
+ if (isset($this->options['storageEngine'])) {
+ $cmd['storageEngine']=(object)$this->options['storageEngine'];
+ }
+
+ if (isset($this->options['validator'])) {
+ $cmd['validator']=(object)$this->options['validator'];
+ }
+
+ return new Command($cmd);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/CreateIndexes.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/CreateIndexes.php
new file mode 100755
index 000000000..9e3de81e2
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/CreateIndexes.php
@@ -0,0 +1,125 @@
+ $index) {
+ if ($i!==$expectedIndex) {
+ throw new InvalidArgumentException(sprintf('$indexes is not a list (unexpected index: "%s")', $i));
+ }
+
+ if (!is_array($index)) {
+ throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array');
+ }
+
+ if (!isset($index['ns'])) {
+ $index['ns']=$databaseName.'.'.$collectionName;
+ }
+
+ $this->indexes[]=new IndexInput($index);
+
+ $expectedIndex+=1;
+ }
+
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * For servers < 2.6, this will actually perform an insert operation on the
+ * database's "system.indexes" collection.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return string[] The names of the created indexes
+ */
+ public function execute(Server $server)
+ {
+ if (Functions::serverSupportsFeature($server, self::$wireVersionForCommand)) {
+ $this->executeCommand($server);
+ } else {
+ $this->executeLegacy($server);
+ }
+
+ return array_map(function (IndexInput $index) {
+ return (string)$index;
+ }, $this->indexes);
+ }
+
+ /**
+ * Create one or more indexes for the collection using the createIndexes
+ * command.
+ *
+ * @param Server $server
+ */
+ private function executeCommand(Server $server)
+ {
+ $command=new Command([
+ 'createIndexes'=>$this->collectionName,
+ 'indexes' =>$this->indexes,
+ ]);
+
+ $server->executeCommand($this->databaseName, $command);
+ }
+
+ /**
+ * Create one or more indexes for the collection by inserting into the
+ * "system.indexes" collection (MongoDB <2.6).
+ *
+ * @param Server $server
+ */
+ private function executeLegacy(Server $server)
+ {
+ $bulk=new Bulk(['ordered'=>true]);
+
+ foreach ($this->indexes as $index) {
+ $bulk->insert($index);
+ }
+
+ $server->executeBulkWrite($this->databaseName.'.system.indexes', $bulk, new WriteConcern(1));
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/DatabaseCommand.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DatabaseCommand.php
new file mode 100755
index 000000000..6ccf73114
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DatabaseCommand.php
@@ -0,0 +1,86 @@
+databaseName=(string)$databaseName;
+ $this->command =($command instanceof Command)?$command:new Command($command);
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return integer
+ */
+ public function execute(Server $server)
+ {
+ $readPreference=isset($this->options['readPreference'])?$this->options['readPreference']:null;
+
+ $cursor=$server->executeCommand($this->databaseName, $this->command, $readPreference);
+
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return $cursor;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/Delete.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Delete.php
new file mode 100755
index 000000000..ac809ce5a
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Delete.php
@@ -0,0 +1,89 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->filter =$filter;
+ $this->limit =$limit;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return DeleteResult
+ */
+ public function execute(Server $server)
+ {
+ $bulk=new Bulk();
+ $bulk->delete($this->filter, ['limit'=>$this->limit]);
+
+ $writeConcern=isset($this->options['writeConcern'])?$this->options['writeConcern']:null;
+ $writeResult =$server->executeBulkWrite($this->databaseName.'.'.$this->collectionName, $bulk, $writeConcern);
+
+ return new DeleteResult($writeResult);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/DeleteMany.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DeleteMany.php
new file mode 100755
index 000000000..c6930a1b8
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DeleteMany.php
@@ -0,0 +1,52 @@
+delete=new Delete($databaseName, $collectionName, $filter, 0, $options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return DeleteResult
+ */
+ public function execute(Server $server)
+ {
+ return $this->delete->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/DeleteOne.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DeleteOne.php
new file mode 100755
index 000000000..57bc16259
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DeleteOne.php
@@ -0,0 +1,52 @@
+delete=new Delete($databaseName, $collectionName, $filter, 1, $options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return DeleteResult
+ */
+ public function execute(Server $server)
+ {
+ return $this->delete->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/Distinct.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Distinct.php
new file mode 100755
index 000000000..ce5afc4e1
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Distinct.php
@@ -0,0 +1,142 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->fieldName =(string)$fieldName;
+ $this->filter =$filter;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return mixed[]
+ * @throws UnexpectedValueException if the command response was malformed
+ */
+ public function execute(Server $server)
+ {
+ $readPreference=isset($this->options['readPreference'])?$this->options['readPreference']:null;
+
+ $cursor=$server->executeCommand($this->databaseName, $this->createCommand($server), $readPreference);
+ $result=current($cursor->toArray());
+
+ if (!isset($result->values)||!is_array($result->values)) {
+ throw new UnexpectedValueException('distinct command did not return a "values" array');
+ }
+
+ return $result->values;
+ }
+
+ /**
+ * Create the distinct command.
+ *
+ * @param Server $server
+ *
+ * @return Command
+ */
+ private function createCommand(Server $server)
+ {
+ $cmd=[
+ 'distinct'=>$this->collectionName,
+ 'key' =>$this->fieldName,
+ ];
+
+ if (!empty($this->filter)) {
+ $cmd['query']=(object)$this->filter;
+ }
+
+ if (isset($this->options['maxTimeMS'])) {
+ $cmd['maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ if (isset($this->options['readConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForReadConcern
+ )
+ ) {
+ $cmd['readConcern']=Functions::readConcernAsDocument($this->options['readConcern']);
+ }
+
+ return new Command($cmd);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropCollection.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropCollection.php
new file mode 100755
index 000000000..23a77883b
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropCollection.php
@@ -0,0 +1,80 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return array|object Command result document
+ */
+ public function execute(Server $server)
+ {
+ try {
+ $cursor=$server->executeCommand($this->databaseName, new Command(['drop'=>$this->collectionName]));
+ } catch (RuntimeException $e) {
+ /* The server may return an error if the collection does not exist.
+ * Check for an error message (unfortunately, there isn't a code)
+ * and NOP instead of throwing.
+ */
+ if ($e->getMessage()===self::$errorMessageNamespaceNotFound) {
+ return (object)['ok'=>0,'errmsg'=>self::$errorMessageNamespaceNotFound];
+ }
+
+ throw $e;
+ }
+
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return current($cursor->toArray());
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropDatabase.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropDatabase.php
new file mode 100755
index 000000000..29606dc8e
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropDatabase.php
@@ -0,0 +1,62 @@
+databaseName=(string)$databaseName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return array|object Command result document
+ */
+ public function execute(Server $server)
+ {
+ $cursor=$server->executeCommand($this->databaseName, new Command(['dropDatabase'=>1]));
+
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return current($cursor->toArray());
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropIndexes.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropIndexes.php
new file mode 100755
index 000000000..9865ab92a
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/DropIndexes.php
@@ -0,0 +1,80 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->indexName =$indexName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return array|object Command result document
+ */
+ public function execute(Server $server)
+ {
+ $cmd=[
+ 'dropIndexes'=>$this->collectionName,
+ 'index' =>$this->indexName,
+ ];
+
+ $cursor=$server->executeCommand($this->databaseName, new Command($cmd));
+
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return current($cursor->toArray());
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/Executable.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Executable.php
new file mode 100755
index 000000000..0bcbd0a25
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Executable.php
@@ -0,0 +1,25 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->filter =$filter;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return Cursor
+ */
+ public function execute(Server $server)
+ {
+ $readPreference=isset($this->options['readPreference'])?$this->options['readPreference']:null;
+
+ $cursor=$server->executeQuery(
+ $this->databaseName.'.'.$this->collectionName,
+ $this->createQuery(),
+ $readPreference
+ );
+
+ if (isset($this->options['typeMap'])) {
+ $cursor->setTypeMap($this->options['typeMap']);
+ }
+
+ return $cursor;
+ }
+
+ /**
+ * Create the find query.
+ *
+ * @return Query
+ */
+ private function createQuery()
+ {
+ $options=[];
+
+ if (!empty($this->options['allowPartialResults'])) {
+ $options['partial']=true;
+ }
+
+ if (isset($this->options['cursorType'])) {
+ if ($this->options['cursorType']===self::TAILABLE) {
+ $options['tailable']=true;
+ }
+ if ($this->options['cursorType']===self::TAILABLE_AWAIT) {
+ $options['tailable'] =true;
+ $options['awaitData']=true;
+ }
+ }
+
+ foreach ([
+ 'batchSize',
+ 'limit',
+ 'skip',
+ 'sort',
+ 'noCursorTimeout',
+ 'oplogReplay',
+ 'projection',
+ 'readConcern'
+ ] as $option) {
+ if (isset($this->options[ $option ])) {
+ $options[ $option ]=$this->options[ $option ];
+ }
+ }
+
+ $modifiers=empty($this->options['modifiers'])?[]:(array)$this->options['modifiers'];
+
+ if (isset($this->options['comment'])) {
+ $modifiers['$comment']=$this->options['comment'];
+ }
+
+ if (isset($this->options['maxTimeMS'])) {
+ $modifiers['$maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ if (!empty($modifiers)) {
+ $options['modifiers']=$modifiers;
+ }
+
+ return new Query($this->filter, $options);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindAndModify.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindAndModify.php
new file mode 100755
index 000000000..0395f7217
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindAndModify.php
@@ -0,0 +1,223 @@
+= 3.2.
+ *
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array $options Command options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($databaseName, $collectionName, array $options)
+ {
+ $options+=[
+ 'new' =>false,
+ 'remove'=>false,
+ 'upsert'=>false,
+ ];
+
+ if (isset($options['bypassDocumentValidation'])&&!is_bool($options['bypassDocumentValidation'])) {
+ throw InvalidArgumentException::invalidType(
+ '"bypassDocumentValidation" option',
+ $options['bypassDocumentValidation'],
+ 'boolean'
+ );
+ }
+
+ if (isset($options['fields'])&&!is_array($options['fields'])&&!is_object($options['fields'])) {
+ throw InvalidArgumentException::invalidType('"fields" option', $options['fields'], 'array or object');
+ }
+
+ if (isset($options['maxTimeMS'])&&!is_integer($options['maxTimeMS'])) {
+ throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+ }
+
+ if (!is_bool($options['new'])) {
+ throw InvalidArgumentException::invalidType('"new" option', $options['new'], 'boolean');
+ }
+
+ if (isset($options['query'])&&!is_array($options['query'])&&!is_object($options['query'])) {
+ throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object');
+ }
+
+ if (!is_bool($options['remove'])) {
+ throw InvalidArgumentException::invalidType('"remove" option', $options['remove'], 'boolean');
+ }
+
+ if (isset($options['sort'])&&!is_array($options['sort'])&&!is_object($options['sort'])) {
+ throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object');
+ }
+
+ if (isset($options['update'])&&!is_array($options['update'])&&!is_object($options['update'])) {
+ throw InvalidArgumentException::invalidType('"update" option', $options['update'], 'array or object');
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ if (!is_bool($options['upsert'])) {
+ throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
+ }
+
+ if (!(isset($options['update']) xor $options['remove'])) {
+ throw new InvalidArgumentException(
+ 'The "remove" option must be true or an "update" document must be specified, but not both'
+ );
+ }
+
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return object|null
+ * @throws UnexpectedValueException if the command response was malformed
+ */
+ public function execute(Server $server)
+ {
+ $cursor=$server->executeCommand($this->databaseName, $this->createCommand($server));
+ $result=current($cursor->toArray());
+
+ if (!isset($result->value)) {
+ return null;
+ }
+
+ /* Prior to 3.0, findAndModify returns an empty document instead of null
+ * when an upsert is performed and the pre-modified document was
+ * requested.
+ */
+ if ($this->options['upsert']&&
+ !$this->options['new']&&
+ isset($result->lastErrorObject->updatedExisting)&&
+ !$result->lastErrorObject->updatedExisting
+ ) {
+ return null;
+ }
+
+ if (!is_object($result->value)) {
+ throw new UnexpectedValueException('findAndModify command did not return a "value" document');
+ }
+
+ return $result->value;
+ }
+
+ /**
+ * Create the findAndModify command.
+ *
+ * @param Server $server
+ *
+ * @return Command
+ */
+ private function createCommand(Server $server)
+ {
+ $cmd=['findAndModify'=>$this->collectionName];
+
+ if ($this->options['remove']) {
+ $cmd['remove']=true;
+ } else {
+ $cmd['new'] =$this->options['new'];
+ $cmd['upsert']=$this->options['upsert'];
+ }
+
+ foreach (['fields','query','sort','update'] as $option) {
+ if (isset($this->options[ $option ])) {
+ $cmd[ $option ]=(object)$this->options[ $option ];
+ }
+ }
+
+ if (isset($this->options['maxTimeMS'])) {
+ $cmd['maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ if (isset($this->options['bypassDocumentValidation'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForDocumentLevelValidation
+ )
+ ) {
+ $cmd['bypassDocumentValidation']=$this->options['bypassDocumentValidation'];
+ }
+
+ if (isset($this->options['writeConcern'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForWriteConcern
+ )
+ ) {
+ $cmd['writeConcern']=$this->options['writeConcern'];
+ }
+
+ return new Command($cmd);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOne.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOne.php
new file mode 100755
index 000000000..f362f4ea2
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOne.php
@@ -0,0 +1,84 @@
+find=new Find($databaseName, $collectionName, $filter, ['limit'=>1]+$options);
+
+ $this->options=$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return array|object|null
+ */
+ public function execute(Server $server)
+ {
+ $cursor =$this->find->execute($server);
+ $document=current($cursor->toArray());
+
+ return ($document===false)?null:$document;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndDelete.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndDelete.php
new file mode 100755
index 000000000..9329fc9df
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndDelete.php
@@ -0,0 +1,82 @@
+= 3.2.
+ *
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array|object $filter Query by which to filter documents
+ * @param array $options Command options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($databaseName, $collectionName, $filter, array $options = [])
+ {
+ if (!is_array($filter)&&!is_object($filter)) {
+ throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+ }
+
+ if (isset($options['projection'])&&!is_array($options['projection'])&&!is_object($options['projection'])) {
+ throw InvalidArgumentException::invalidType(
+ '"projection" option',
+ $options['projection'],
+ 'array or object'
+ );
+ }
+
+ if (isset($options['projection'])) {
+ $options['fields']=$options['projection'];
+ }
+
+ unset($options['projection']);
+
+ $this->findAndModify=new FindAndModify($databaseName, $collectionName, ['query' =>$filter,
+ 'remove'=>true
+ ]+$options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return object|null
+ */
+ public function execute(Server $server)
+ {
+ return $this->findAndModify->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndReplace.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndReplace.php
new file mode 100755
index 000000000..1178a0c20
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndReplace.php
@@ -0,0 +1,130 @@
+= 3.2.
+ *
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $replacement Replacement document
+ * @param array $options Command options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = [])
+ {
+ if (!is_array($filter)&&!is_object($filter)) {
+ throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+ }
+
+ if (!is_array($replacement)&&!is_object($replacement)) {
+ throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object');
+ }
+
+ if (Functions::isFirstKeyOperator($replacement)) {
+ throw new InvalidArgumentException('First key in $replacement argument is an update operator');
+ }
+
+ $options+=[
+ 'returnDocument'=>self::RETURN_DOCUMENT_BEFORE,
+ 'upsert' =>false,
+ ];
+
+ if (isset($options['projection'])&&!is_array($options['projection'])&&!is_object($options['projection'])) {
+ throw InvalidArgumentException::invalidType(
+ '"projection" option',
+ $options['projection'],
+ 'array or object'
+ );
+ }
+
+ if (!is_integer($options['returnDocument'])) {
+ throw InvalidArgumentException::invalidType(
+ '"returnDocument" option',
+ $options['returnDocument'],
+ 'integer'
+ );
+ }
+
+ if ($options['returnDocument']!==self::RETURN_DOCUMENT_AFTER&&
+ $options['returnDocument']!==self::RETURN_DOCUMENT_BEFORE
+ ) {
+ throw new InvalidArgumentException(
+ 'Invalid value for "returnDocument" option: '.$options['returnDocument']
+ );
+ }
+
+ if (isset($options['projection'])) {
+ $options['fields']=$options['projection'];
+ }
+
+ $options['new']=$options['returnDocument']===self::RETURN_DOCUMENT_AFTER;
+
+ unset($options['projection'],$options['returnDocument']);
+
+ $this->findAndModify=new FindAndModify($databaseName, $collectionName, ['query' =>$filter,
+ 'update'=>$replacement
+ ]+$options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return object|null
+ */
+ public function execute(Server $server)
+ {
+ return $this->findAndModify->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndUpdate.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndUpdate.php
new file mode 100755
index 000000000..afaa1d172
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/FindOneAndUpdate.php
@@ -0,0 +1,130 @@
+= 3.2.
+ *
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array|object $filter Query by which to filter documents
+ * @param array|object $update Update to apply to the matched document
+ * @param array $options Command options
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
+ {
+ if (!is_array($filter)&&!is_object($filter)) {
+ throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+ }
+
+ if (!is_array($update)&&!is_object($update)) {
+ throw InvalidArgumentException::invalidType('$update', $update, 'array or object');
+ }
+
+ if (!Functions::isFirstKeyOperator($update)) {
+ throw new InvalidArgumentException('First key in $update argument is not an update operator');
+ }
+
+ $options+=[
+ 'returnDocument'=>self::RETURN_DOCUMENT_BEFORE,
+ 'upsert' =>false,
+ ];
+
+ if (isset($options['projection'])&&!is_array($options['projection'])&&!is_object($options['projection'])) {
+ throw InvalidArgumentException::invalidType(
+ '"projection" option',
+ $options['projection'],
+ 'array or object'
+ );
+ }
+
+ if (!is_integer($options['returnDocument'])) {
+ throw InvalidArgumentException::invalidType(
+ '"returnDocument" option',
+ $options['returnDocument'],
+ 'integer'
+ );
+ }
+
+ if ($options['returnDocument']!==self::RETURN_DOCUMENT_AFTER&&
+ $options['returnDocument']!==self::RETURN_DOCUMENT_BEFORE
+ ) {
+ throw new InvalidArgumentException(
+ 'Invalid value for "returnDocument" option: '.$options['returnDocument']
+ );
+ }
+
+ if (isset($options['projection'])) {
+ $options['fields']=$options['projection'];
+ }
+
+ $options['new']=$options['returnDocument']===self::RETURN_DOCUMENT_AFTER;
+
+ unset($options['projection'],$options['returnDocument']);
+
+ $this->findAndModify=new FindAndModify($databaseName, $collectionName, ['query' =>$filter,
+ 'update'=>$update
+ ]+$options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return object|null
+ */
+ public function execute(Server $server)
+ {
+ return $this->findAndModify->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/InsertMany.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/InsertMany.php
new file mode 100755
index 000000000..16e68fd2b
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/InsertMany.php
@@ -0,0 +1,140 @@
+ $document) {
+ if ($i!==$expectedIndex) {
+ throw new InvalidArgumentException(sprintf('$documents is not a list (unexpected index: "%s")', $i));
+ }
+
+ if (!is_array($document)&&!is_object($document)) {
+ throw InvalidArgumentException::invalidType(
+ sprintf('$documents[%d]', $i),
+ $document,
+ 'array or object'
+ );
+ }
+
+ $expectedIndex+=1;
+ }
+
+ $options+=['ordered'=>true];
+
+ if (isset($options['bypassDocumentValidation'])&&!is_bool($options['bypassDocumentValidation'])) {
+ throw InvalidArgumentException::invalidType(
+ '"bypassDocumentValidation" option',
+ $options['bypassDocumentValidation'],
+ 'boolean'
+ );
+ }
+
+ if (!is_bool($options['ordered'])) {
+ throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->documents =$documents;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return InsertManyResult
+ */
+ public function execute(Server $server)
+ {
+ $options=['ordered'=>$this->options['ordered']];
+
+ if (isset($this->options['bypassDocumentValidation'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForDocumentLevelValidation
+ )
+ ) {
+ $options['bypassDocumentValidation']=$this->options['bypassDocumentValidation'];
+ }
+
+ $bulk =new Bulk($options);
+ $insertedIds=[];
+
+ foreach ($this->documents as $i => $document) {
+ $insertedId=$bulk->insert($document);
+
+ if ($insertedId!==null) {
+ $insertedIds[ $i ]=$insertedId;
+ } else {
+ $insertedIds[ $i ]=Functions::extractIdFromInsertedDocument($document);
+ }
+ }
+
+ $writeConcern=isset($this->options['writeConcern'])?$this->options['writeConcern']:null;
+ $writeResult =$server->executeBulkWrite($this->databaseName.'.'.$this->collectionName, $bulk, $writeConcern);
+
+ return new InsertManyResult($writeResult, $insertedIds);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/InsertOne.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/InsertOne.php
new file mode 100755
index 000000000..82f63d560
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/InsertOne.php
@@ -0,0 +1,106 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->document =$document;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return InsertOneResult
+ */
+ public function execute(Server $server)
+ {
+ $options=[];
+
+ if (isset($this->options['bypassDocumentValidation'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForDocumentLevelValidation
+ )
+ ) {
+ $options['bypassDocumentValidation']=$this->options['bypassDocumentValidation'];
+ }
+
+ $bulk =new Bulk($options);
+ $insertedId=$bulk->insert($this->document);
+
+ if ($insertedId===null) {
+ $insertedId=Functions::extractIdFromInsertedDocument($this->document);
+ }
+
+ $writeConcern=isset($this->options['writeConcern'])?$this->options['writeConcern']:null;
+ $writeResult =$server->executeBulkWrite($this->databaseName.'.'.$this->collectionName, $bulk, $writeConcern);
+
+ return new InsertOneResult($writeResult, $insertedId);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListCollections.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListCollections.php
new file mode 100755
index 000000000..3dbc1e991
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListCollections.php
@@ -0,0 +1,128 @@
+databaseName=(string)$databaseName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return CollectionInfoIterator
+ */
+ public function execute(Server $server)
+ {
+ return Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForCommand
+ )?$this->executeCommand($server):$this->executeLegacy($server);
+ }
+
+ /**
+ * Returns information for all collections in this database using the
+ * listCollections command.
+ *
+ * @param Server $server
+ *
+ * @return CollectionInfoCommandIterator
+ */
+ private function executeCommand(Server $server)
+ {
+ $cmd=['listCollections'=>1];
+
+ if (!empty($this->options['filter'])) {
+ $cmd['filter']=(object)$this->options['filter'];
+ }
+
+ if (isset($this->options['maxTimeMS'])) {
+ $cmd['maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ $cursor=$server->executeCommand($this->databaseName, new Command($cmd));
+ $cursor->setTypeMap(['root'=>'array','document'=>'array']);
+
+ return new CollectionInfoCommandIterator($cursor);
+ }
+
+ /**
+ * Returns information for all collections in this database by querying the
+ * "system.namespaces" collection (MongoDB <3.0).
+ *
+ * @param Server $server
+ *
+ * @return CollectionInfoLegacyIterator
+ * @throws InvalidArgumentException if filter.name is not a string.
+ */
+ private function executeLegacy(Server $server)
+ {
+ $filter=empty($this->options['filter'])?[]:(array)$this->options['filter'];
+
+ if (array_key_exists('name', $filter)) {
+ if (!is_string($filter['name'])) {
+ throw InvalidArgumentException::invalidType('filter name for MongoDB <3.0', $filter['name'], 'string');
+ }
+
+ $filter['name']=$this->databaseName.'.'.$filter['name'];
+ }
+
+ $options=isset($this->options['maxTimeMS'])?['modifiers'=>['$maxTimeMS'=>$this->options['maxTimeMS']]]:[];
+
+ $cursor=$server->executeQuery($this->databaseName.'.system.namespaces', new Query($filter, $options));
+ $cursor->setTypeMap(['root'=>'array','document'=>'array']);
+
+ return new CollectionInfoLegacyIterator($cursor);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListDatabases.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListDatabases.php
new file mode 100755
index 000000000..07d9cda19
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListDatabases.php
@@ -0,0 +1,79 @@
+options=$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return DatabaseInfoIterator
+ * @throws UnexpectedValueException if the command response was malformed
+ */
+ public function execute(Server $server)
+ {
+ $cmd=['listDatabases'=>1];
+
+ if (isset($this->options['maxTimeMS'])) {
+ $cmd['maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ $cursor=$server->executeCommand('admin', new Command($cmd));
+ $cursor->setTypeMap(['root'=>'array','document'=>'array']);
+ $result=current($cursor->toArray());
+
+ if (!isset($result['databases'])||!is_array($result['databases'])) {
+ throw new UnexpectedValueException('listDatabases command did not return a "databases" array');
+ }
+
+ /* Return an Iterator instead of an array in case listDatabases is
+ * eventually changed to return a command cursor, like the collection
+ * and index enumeration commands. This makes the "totalSize" command
+ * field inaccessible, but users can manually invoke the command if they
+ * need that value.
+ */
+
+ return new DatabaseInfoLegacyIterator($result['databases']);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListIndexes.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListIndexes.php
new file mode 100755
index 000000000..474c08773
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ListIndexes.php
@@ -0,0 +1,127 @@
+databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return IndexInfoIterator
+ */
+ public function execute(Server $server)
+ {
+ return Functions::serverSupportsFeature($server, self::$wireVersionForCommand)
+ ?$this->executeCommand($server)
+ :$this->executeLegacy($server);
+ }
+
+ /**
+ * Returns information for all indexes for this collection using the
+ * listIndexes command.
+ *
+ * @param Server $server
+ *
+ * @return IndexInfoIteratorIterator
+ */
+ private function executeCommand(Server $server)
+ {
+ $cmd=['listIndexes'=>$this->collectionName];
+
+ if (isset($this->options['maxTimeMS'])) {
+ $cmd['maxTimeMS']=$this->options['maxTimeMS'];
+ }
+
+ try {
+ $cursor=$server->executeCommand($this->databaseName, new Command($cmd));
+ } catch (RuntimeException $e) {
+ /* The server may return an error if the collection does not exist.
+ * Check for possible error codes (see: SERVER-20463) and return an
+ * empty iterator instead of throwing.
+ */
+ if ($e->getCode()===self::$errorCodeNamespaceNotFound||$e->getCode()===self::$errorCodeDatabaseNotFound) {
+ return new IndexInfoIteratorIterator(new EmptyIterator);
+ }
+
+ throw $e;
+ }
+
+ $cursor->setTypeMap(['root'=>'array','document'=>'array']);
+
+ return new IndexInfoIteratorIterator($cursor);
+ }
+
+ /**
+ * Returns information for all indexes for this collection by querying the
+ * "system.indexes" collection (MongoDB <3.0).
+ *
+ * @param Server $server
+ *
+ * @return IndexInfoIteratorIterator
+ */
+ private function executeLegacy(Server $server)
+ {
+ $filter=['ns'=>$this->databaseName.'.'.$this->collectionName];
+
+ $options=isset($this->options['maxTimeMS'])?['modifiers'=>['$maxTimeMS'=>$this->options['maxTimeMS']]]:[];
+
+ $cursor=$server->executeQuery($this->databaseName.'.system.indexes', new Query($filter, $options));
+ $cursor->setTypeMap(['root'=>'array','document'=>'array']);
+
+ return new IndexInfoIteratorIterator($cursor);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/ReplaceOne.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ReplaceOne.php
new file mode 100755
index 000000000..7d618dd6d
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/ReplaceOne.php
@@ -0,0 +1,68 @@
+update=new Update($databaseName, $collectionName, $filter, $replacement, ['multi'=>false]+$options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return UpdateResult
+ */
+ public function execute(Server $server)
+ {
+ return $this->update->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/Update.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Update.php
new file mode 100755
index 000000000..4a148b71b
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/Update.php
@@ -0,0 +1,141 @@
+false,
+ 'upsert'=>false,
+ ];
+
+ if (isset($options['bypassDocumentValidation'])&&!is_bool($options['bypassDocumentValidation'])) {
+ throw InvalidArgumentException::invalidType(
+ '"bypassDocumentValidation" option',
+ $options['bypassDocumentValidation'],
+ 'boolean'
+ );
+ }
+
+ if (!is_bool($options['multi'])) {
+ throw InvalidArgumentException::invalidType('"multi" option', $options['multi'], 'boolean');
+ }
+
+ if ($options['multi']&&!Functions::isFirstKeyOperator($update)) {
+ throw new InvalidArgumentException('"multi" option cannot be true if $update is a replacement document');
+ }
+
+ if (!is_bool($options['upsert'])) {
+ throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
+ }
+
+ if (isset($options['writeConcern'])&&!$options['writeConcern'] instanceof WriteConcern) {
+ throw InvalidArgumentException::invalidType(
+ '"writeConcern" option',
+ $options['writeConcern'],
+ 'MongoDB\Driver\WriteConcern'
+ );
+ }
+
+ $this->databaseName =(string)$databaseName;
+ $this->collectionName=(string)$collectionName;
+ $this->filter =$filter;
+ $this->update =$update;
+ $this->options =$options;
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return UpdateResult
+ */
+ public function execute(Server $server)
+ {
+ $updateOptions=[
+ 'multi' =>$this->options['multi'],
+ 'upsert'=>$this->options['upsert'],
+ ];
+
+ $bulkOptions=[];
+
+ if (isset($this->options['bypassDocumentValidation'])&&Functions::serverSupportsFeature(
+ $server,
+ self::$wireVersionForDocumentLevelValidation
+ )
+ ) {
+ $bulkOptions['bypassDocumentValidation']=$this->options['bypassDocumentValidation'];
+ }
+
+ $bulk=new Bulk($bulkOptions);
+ $bulk->update($this->filter, $this->update, $updateOptions);
+
+ $writeConcern=isset($this->options['writeConcern'])?$this->options['writeConcern']:null;
+ $writeResult =$server->executeBulkWrite($this->databaseName.'.'.$this->collectionName, $bulk, $writeConcern);
+
+ return new UpdateResult($writeResult);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/UpdateMany.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/UpdateMany.php
new file mode 100755
index 000000000..10ab674e5
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/UpdateMany.php
@@ -0,0 +1,68 @@
+update=new Update($databaseName, $collectionName, $filter, $update, ['multi'=>true]+$options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return UpdateResult
+ */
+ public function execute(Server $server)
+ {
+ return $this->update->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/Operation/UpdateOne.php b/Library/Phalcon/Db/Adapter/MongoDB/Operation/UpdateOne.php
new file mode 100755
index 000000000..38ba1e365
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/Operation/UpdateOne.php
@@ -0,0 +1,68 @@
+update=new Update($databaseName, $collectionName, $filter, $update, ['multi'=>false]+$options);
+ }
+
+ /**
+ * Execute the operation.
+ *
+ * @see Executable::execute()
+ *
+ * @param Server $server
+ *
+ * @return UpdateResult
+ */
+ public function execute(Server $server)
+ {
+ return $this->update->execute($server);
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/MongoDB/UpdateResult.php b/Library/Phalcon/Db/Adapter/MongoDB/UpdateResult.php
new file mode 100755
index 000000000..e49d3d7bf
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/MongoDB/UpdateResult.php
@@ -0,0 +1,121 @@
+writeResult =$writeResult;
+ $this->isAcknowledged=$writeResult->isAcknowledged();
+ }
+
+ /**
+ * Return the number of documents that were matched by the filter.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see UpdateResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getMatchedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getMatchedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the number of documents that were modified.
+ *
+ * This value is undefined (i.e. null) if the write executed as a legacy
+ * operation instead of command.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see UpdateResult::isAcknowledged()
+ * @return integer|null
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getModifiedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getModifiedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the number of documents that were upserted.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see UpdateResult::isAcknowledged()
+ * @return integer
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getUpsertedCount()
+ {
+ if ($this->isAcknowledged) {
+ return $this->writeResult->getUpsertedCount();
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return the ID of the document inserted by an upsert operation.
+ *
+ * This value is undefined (i.e. null) if an upsert did not take place.
+ *
+ * This method should only be called if the write was acknowledged.
+ *
+ * @see UpdateResult::isAcknowledged()
+ * @return mixed|null
+ * @throws BadMethodCallException is the write result is unacknowledged
+ */
+ public function getUpsertedId()
+ {
+ if ($this->isAcknowledged) {
+ foreach ($this->writeResult->getUpsertedIds() as $id) {
+ return $id;
+ }
+
+ return null;
+ }
+
+ throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+ }
+
+ /**
+ * Return whether this update was acknowledged by the server.
+ *
+ * If the update was not acknowledged, other fields from the WriteResult
+ * (e.g. matchedCount) will be undefined and their getter methods should not
+ * be invoked.
+ *
+ * @return boolean
+ */
+ public function isAcknowledged()
+ {
+ return $this->isAcknowledged;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/Pdo/Oracle.php b/Library/Phalcon/Db/Adapter/Pdo/Oracle.php
new file mode 100644
index 000000000..7c0efaf7f
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/Pdo/Oracle.php
@@ -0,0 +1,271 @@
+ |
+ | Eduar Carvajal
+ * use Phalcon\Db\Adapter\Pdo\Oracle;
+ *
+ * $connection = new Oracle([
+ * 'dbname' => '//localhost/dbname',
+ * 'username' => 'oracle',
+ * 'password' => 'oracle',
+ * ]);
+ *
+ *
+ * @property \Phalcon\Db\Dialect\Oracle _dialect
+ * @package Phalcon\Db\Adapter\Pdo
+ */
+class Oracle extends Pdo implements AdapterInterface
+{
+ // @codingStandardsIgnoreStart
+ protected $_type = 'oci';
+ protected $_dialectType = 'oracle';
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * This method is automatically called in \Phalcon\Db\Adapter\Pdo constructor.
+ * Call it when you need to restore a database connection.
+ *
+ * @param array $descriptor
+ * @return bool
+ */
+ public function connect(array $descriptor = null)
+ {
+ if (empty($descriptor)) {
+ $descriptor = $this->_descriptor;
+ }
+
+ $status = parent::connect($descriptor);
+
+ if (isset($descriptor['startup']) && $descriptor['startup']) {
+ $startup = $descriptor['startup'];
+ if (!is_array($startup)) {
+ $startup = [$startup];
+ }
+
+ foreach ($startup as $value) {
+ $this->execute($value);
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Returns an array of \Phalcon\Db\Column objects describing a table.
+ *
+ *
+ * var_dump($connection->describeColumns('posts'));
+ *
+ *
+ * @param string $table
+ * @param string $schema
+ * @return ColumnInterface[]
+ */
+ public function describeColumns($table, $schema = null)
+ {
+ $columns = [];
+ $oldColumn = null;
+
+ /**
+ * 0:column_name,
+ * 1:data_type,
+ * 2:data_length,
+ * 3:data_precision,
+ * 4:data_scale,
+ * 5:nullable,
+ * 6:constraint_type,
+ * 7:default,
+ * 8:position;
+ */
+ $sql = $this->_dialect->describeColumns($table, $schema);
+ foreach ($this->fetchAll($sql, Db::FETCH_NUM) as $field) {
+ $definition = ['bindType' => 2];
+ $columnSize = $field[2];
+ $columnPrecision = $field[3];
+ $columnScale = $field[4];
+ $columnType = $field[1];
+
+ /**
+ * Check the column type to get the correct Phalcon type
+ */
+ while (true) {
+ if (false !== strpos($columnType, 'NUMBER')) {
+ $definition['type'] = Column::TYPE_DECIMAL;
+ $definition['isNumeric'] = true;
+ $definition['size'] = $columnPrecision;
+ $definition['scale'] = $columnScale;
+ $definition['bindType'] = 32;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'INTEGER')) {
+ $definition['type'] = Column::TYPE_INTEGER;
+ $definition['isNumeric'] = true;
+ $definition['size'] = $columnPrecision;
+ $definition['bindType'] = 1;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'VARCHAR2')) {
+ $definition['type'] = Column::TYPE_VARCHAR;
+ $definition['size'] = $columnSize;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'FLOAT')) {
+ $definition['type'] = Column::TYPE_FLOAT;
+ $definition['isNumeric'] = true;
+ $definition['size'] = $columnSize;
+ $definition['scale'] = $columnScale;
+ $definition['bindType'] = 32;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'TIMESTAMP')) {
+ $definition['type'] = Column::TYPE_TIMESTAMP;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'DATE')) {
+ $definition['type'] = Column::TYPE_DATE;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'RAW')) {
+ $definition['type'] = Column::TYPE_TEXT;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'BLOB')) {
+ $definition['type'] = Column::TYPE_TEXT;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'CLOB')) {
+ $definition['type'] = Column::TYPE_TEXT;
+ break;
+ }
+
+ if (false !== strpos($columnType, 'CHAR')) {
+ $definition['type'] = Column::TYPE_CHAR;
+ $definition['size'] = $columnSize;
+ break;
+ }
+
+ $definition['type'] = Column::TYPE_TEXT;
+ break;
+ }
+
+ if (null === $oldColumn) {
+ $definition['first'] = true;
+ } else {
+ $definition['after'] = $oldColumn;
+ }
+
+ /**
+ * Check if the field is primary key
+ */
+ if ('P' == $field[6]) {
+ $definition['primary'] = true;
+ }
+
+ /**
+ * Check if the column allows null values
+ */
+ if ('N' == $field[5]) {
+ $definition['notNull'] = true;
+ }
+
+ $columns[] = new Column($field[0], $definition);
+ $oldColumn = $field[0];
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Returns the insert id for the auto_increment/serial column inserted in the latest executed SQL statement.
+ *
+ *
+ * // Inserting a new robot
+ * $success = $connection->insert(
+ * 'robots',
+ * ['Astro Boy', 1952],
+ * ['name', 'year'],
+ * );
+ *
+ * // Getting the generated id
+ * $id = $connection->lastInsertId();
+ *
+ *
+ * @param string $sequenceName
+ * @return int
+ */
+ public function lastInsertId($sequenceName = null)
+ {
+ $sequenceName = $sequenceName ?: 'id';
+
+ return $this->fetchAll('SELECT ' . $sequenceName . '.CURRVAL FROM dual', Db::FETCH_NUM)[0][0];
+ }
+
+ /**
+ * Check whether the database system requires an explicit value for identity columns;
+ *
+ * @return bool
+ */
+ public function useExplicitIdValue()
+ {
+ return false;
+ }
+
+ /**
+ * Return the default identity value to insert in an identity column.
+ *
+ * @return RawValue
+ */
+ public function getDefaultIdValue()
+ {
+ return new RawValue('default');
+ }
+
+ /**
+ * Check whether the database system requires a sequence to produce auto-numeric values.
+ *
+ * @return bool
+ */
+ public function supportSequences()
+ {
+ return true;
+ }
+}
diff --git a/Library/Phalcon/Db/Adapter/README.md b/Library/Phalcon/Db/Adapter/README.md
new file mode 100644
index 000000000..ab45c2bc1
--- /dev/null
+++ b/Library/Phalcon/Db/Adapter/README.md
@@ -0,0 +1,125 @@
+# Phalcon\Db\Adapter
+
+Usage examples of the adapters available here:
+
+## Cacheable\Mysql
+
+Implements an aggressive cache. Every query performed is cached with the same lifetime.
+This adapter is specially suitable for applications with very few inserts/updates/deletes
+and a higher read rate.
+
+```php
+use Pdo;
+use Phalcon\Cache\Backend\File;
+use Phalcon\Cache\Frontend\Data;
+use Phalcon\Db\Adapter\Cacheable\Mysql;
+
+$di->set('db', function() {
+ /** @var \Phalcon\DiInterface $this */
+ $connection = new Mysql([
+ 'host' => $this->getShared('config')->database->host,
+ 'username' => $this->getShared('config')->database->username,
+ 'password' => $this->getShared('config')->database->password,
+ 'dbname' => $this->getShared('config')->database->name,
+ 'options' => [Pdo::ATTR_EMULATE_PREPARES => false]
+ ]);
+
+ $frontCache = new Data(['lifetime' => 2592000]);
+
+ // File backend settings
+ $connection->setCache(new File($frontCache, ['cacheDir' => __DIR__ . '/../../var/db/']));
+
+ return $connection;
+});
+```
+
+## Pdo\Oracle
+
+Specific functions for the Oracle RDBMS.
+
+```php
+use Phalcon\Db\Adapter\Pdo\Oracle;
+
+$di->set('db', function() {
+ /** @var \Phalcon\DiInterface $this */
+ $connection = new Oracle([
+ 'dbname' => $this->getShared('config')->database->dbname,
+ 'username' => $this->getShared('config')->database->username,
+ 'password' => $this->getShared('config')->database->password,
+ ]);
+
+ return $connection;
+});
+```
+
+## Mongo\Client
+
+Extends client class of MongoDb native extension to take advantage of DbRef, Document, Collection, Db.
+
+This will solve cursor and related records problems on the ODM.
+
+```php
+use Phalcon\Db\Adapter\Mongo\Client;
+
+$di->setShared('mongo', function() {
+ /** @var \Phalcon\DiInterface $this */
+ $mongo = new Client();
+
+ return $mongo->selectDB($this->getShared('config')->database->dbname);
+});
+```
+
+## MongoDB\Client
+
+Enables the use of the new MongoDB PHP extension.
+
+This will enable use of a mongo database using PHP7 with Phalcon 2.1
+
+```php
+use Phalcon\Mvc\Collection\Manager;
+use Phalcon\Db\Adapter\MongoDB\Client;
+
+// Initialise the mongo DB connection.
+$di->setShared('mongo', function () {
+ /** @var \Phalcon\DiInterface $this */
+ $config = $this->getShared('config');
+
+ if (!$config->database->mongo->username || !$config->database->mongo->password) {
+ $dsn = 'mongodb://' . $config->database->mongo->host;
+ } else {
+ $dsn = sprintf(
+ 'mongodb://%s:%s@%s'
+ $config->database->mongo->username,
+ $config->database->mongo->password,
+ $config->database->mongo->host
+ );
+ }
+
+ $mongo = new Client($dsn);
+
+ return $mongo->selectDatabase($config->database->mongo->dbname);
+});
+
+// Collection Manager is required for MongoDB
+$di->setShared('collectionManager', function () {
+ return new Manager();
+});
+```
+
+Collection example:
+
+```php
+use Phalcon\Mvc\MongoCollection;
+
+class UserCollection extends MongoCollection
+{
+ public $name;
+ public $email;
+ public $password;
+
+ public function getSource()
+ {
+ return 'users';
+ }
+}
+```
diff --git a/Library/Phalcon/Db/Dialect/MysqlExtended.php b/Library/Phalcon/Db/Dialect/MysqlExtended.php
index 13f83d0f2..278568c42 100644
--- a/Library/Phalcon/Db/Dialect/MysqlExtended.php
+++ b/Library/Phalcon/Db/Dialect/MysqlExtended.php
@@ -20,29 +20,50 @@
namespace Phalcon\Db\Dialect;
+use Phalcon\Db\Exception;
+
/**
- * Phalcon\Db\Adapter\Dialect\MysqlExtended
+ * Phalcon\Db\Dialect\MysqlExtended
+ *
+ * Generates database specific SQL for the MySQL RDBMS.
+ *
+ * This is an extended MySQL dialect that introduces workarounds for some common MySQL-only functions like
+ * search based on FULLTEXT indexes and operations with date intervals.
+ *
+ *
+ * use Phalcon\Db\Adapter\Pdo\Mysql;
+ * use Phalcon\Db\Adapter\Pdo\MysqlExtended;
*
- * Every query executed via this adapter is automatically cached
+ * $connection = new Mysql([
+ * 'host' => 'localhost',
+ * 'username' => 'root',
+ * 'password' => 'secret',
+ * 'dbname' => 'enigma',
+ * 'dialectClass' => MysqlExtended::class
+ * ]);
+ *
+ *
+ * @package Phalcon\Db\Dialect
*/
class MysqlExtended extends Mysql
{
/**
- * Transforms an intermediate representation for a expression into a database system valid expression
+ * Transforms an intermediate representation for a expression into a database system valid expression.
*
- * @param array $expression
+ * @param array $expression
* @param string $escapeChar
- *
+ * @param mixed $bindCounts
* @return string
- * @throws \Exception
+ *
+ * @throws Exception
*/
public function getSqlExpression(array $expression, $escapeChar = null, $bindCounts = null)
{
if ($expression["type"] == 'functionCall') {
- switch ($expression["name"]) {
+ switch (strtoupper($expression["name"])) {
case 'DATE_INTERVAL':
if (count($expression["arguments"]) != 2) {
- throw new \Exception('DATE_INTERVAL requires 2 parameters');
+ throw new Exception('DATE_INTERVAL requires 2 parameters');
}
switch ($expression["arguments"][1]['value']) {
@@ -71,10 +92,10 @@ public function getSqlExpression(array $expression, $escapeChar = null, $bindCou
case 'FULLTEXT_MATCH':
if (count($expression["arguments"]) < 2) {
- throw new \Exception('FULLTEXT_MATCH requires 2 parameters');
+ throw new Exception('FULLTEXT_MATCH requires 2 parameters');
}
- $arguments = array();
+ $arguments = [];
$length = count($expression["arguments"]) - 1;
for ($i = 0; $i < $length; $i++) {
$arguments[] = $this->getSqlExpression($expression["arguments"][$i]);
@@ -82,13 +103,14 @@ public function getSqlExpression(array $expression, $escapeChar = null, $bindCou
return 'MATCH(' . join(', ', $arguments) . ') AGAINST (' .
$this->getSqlExpression($expression["arguments"][$length]) . ')';
+ break;
case 'FULLTEXT_MATCH_BMODE':
if (count($expression["arguments"]) < 2) {
- throw new \Exception('FULLTEXT_MATCH requires 2 parameters');
+ throw new Exception('FULLTEXT_MATCH requires 2 parameters');
}
- $arguments = array();
+ $arguments = [];
$length = count($expression["arguments"]) - 1;
for ($i = 0; $i < $length; $i++) {
$arguments[] = $this->getSqlExpression($expression["arguments"][$i]);
@@ -96,9 +118,19 @@ public function getSqlExpression(array $expression, $escapeChar = null, $bindCou
return 'MATCH(' . join(', ', $arguments) . ') AGAINST (' .
$this->getSqlExpression($expression["arguments"][$length]) . ' IN BOOLEAN MODE)';
+ break;
+
+ case 'REGEXP':
+ if (count($expression['arguments']) != 2) {
+ throw new Exception('REGEXP requires 2 parameters');
+ }
+
+ return $this->getSqlExpression($expression['arguments'][0]) .
+ ' REGEXP (' . $this->getSqlExpression($expression['arguments'][1]) . ')';
+ break;
}
}
- return parent::getSqlExpression($expression, $escapeChar);
+ return parent::getSqlExpression($expression, $escapeChar, $bindCounts);
}
}
diff --git a/Library/Phalcon/Db/Dialect/Oracle.php b/Library/Phalcon/Db/Dialect/Oracle.php
new file mode 100644
index 000000000..a6698e25b
--- /dev/null
+++ b/Library/Phalcon/Db/Dialect/Oracle.php
@@ -0,0 +1,595 @@
+ |
+ | Eduar Carvajal |
+ +------------------------------------------------------------------------+
+ */
+
+namespace Phalcon\Db\Dialect;
+
+use Phalcon\Text;
+use Phalcon\Db\Column;
+use Phalcon\Db\Dialect;
+use Phalcon\Db\Exception;
+use Phalcon\Db\IndexInterface;
+use Phalcon\Db\ColumnInterface;
+use Phalcon\Db\ReferenceInterface;
+
+/**
+ * Phalcon\Db\Dialect\Oracle
+ *
+ * Generates database specific SQL for the Oracle RDBMS.
+ *
+ * @package Phalcon\Db\Dialect
+ */
+class Oracle extends Dialect
+{
+ // @codingStandardsIgnoreStart
+ protected $_escapeChar = "'";
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Generates the SQL for LIMIT clause.
+ *
+ * @param string $sqlQuery
+ * @param mixed $number
+ * @return string
+ */
+ public function limit($sqlQuery, $number)
+ {
+ $offset = 0;
+
+ if (is_array($number)) {
+ if (isset($number[1])) {
+ $offset = intval(trim($number[1], $this->_escapeChar));
+ }
+
+ $limit = intval(trim($number[0], $this->_escapeChar)) + $offset;
+ } else {
+ $limit = intval(trim($number, $this->_escapeChar));
+ }
+
+ $sqlQuery = sprintf(
+ /** @lang text */
+ 'SELECT * FROM (SELECT Z1.*, ROWNUM PHALCON_RN FROM (%s) Z1',
+ $sqlQuery
+ );
+
+ if (0 != $limit) {
+ $sqlQuery .= sprintf(' WHERE ROWNUM <= %d', $limit);
+ }
+
+ $sqlQuery .= ')';
+
+ if (0 != $offset) {
+ $sqlQuery .= sprintf(' WHERE PHALCON_RN >= %d', $offset);
+ }
+
+ return $sqlQuery;
+ }
+
+ /**
+ * Gets the column name in Oracle.
+ *
+ * @param ColumnInterface $column
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function getColumnDefinition(ColumnInterface $column)
+ {
+ $type = $column->getType();
+ $size = $column->getSize();
+
+ switch ($type) {
+ case Column::TYPE_INTEGER:
+ $columnSql = 'INTEGER';
+ break;
+ case Column::TYPE_DATE:
+ $columnSql = 'DATE';
+ break;
+ case Column::TYPE_VARCHAR:
+ $columnSql = 'VARCHAR2(' . $size . ')';
+ break;
+ case Column::TYPE_DECIMAL:
+ $scale = $column->getScale();
+ $columnSql = 'NUMBER(' . $size . ',' . $scale . ')';
+ break;
+ case Column::TYPE_DATETIME:
+ $columnSql = 'TIMESTAMP';
+ break;
+ case Column::TYPE_TIMESTAMP:
+ $columnSql = 'TIMESTAMP';
+ break;
+ case Column::TYPE_CHAR:
+ $columnSql = 'CHAR(' . $size . ')';
+ break;
+ case Column::TYPE_TEXT:
+ $columnSql = 'TEXT';
+ break;
+ case Column::TYPE_FLOAT:
+ $scale = $column->getScale();
+ $columnSql = 'FLOAT(' . $size . ',' . $scale . ')';
+ break;
+ case Column::TYPE_BOOLEAN:
+ $columnSql = 'TINYINT(1)';
+ break;
+ default:
+ throw new Exception('Unrecognized Oracle data type at column ' . $column->getName());
+ }
+
+ return $columnSql;
+ }
+
+ /**
+ * Generates SQL to add a column to a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param ColumnInterface $column
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function addColumn($tableName, $schemaName, ColumnInterface $column)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to modify a column in a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param ColumnInterface $column
+ * @param ColumnInterface|null $current
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function modifyColumn($tableName, $schemaName, ColumnInterface $column, ColumnInterface $current = null)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to delete a column from a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param string $columnName
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function dropColumn($tableName, $schemaName, $columnName)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to add an index to a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param IndexInterface $index
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function addIndex($tableName, $schemaName, IndexInterface $index)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to delete an index from a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param string $indexName
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function dropIndex($tableName, $schemaName, $indexName)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to add the primary key to a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param IndexInterface $index
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function addPrimaryKey($tableName, $schemaName, IndexInterface $index)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to delete primary key from a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function dropPrimaryKey($tableName, $schemaName)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to add an index to a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param ReferenceInterface $reference
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function addForeignKey($tableName, $schemaName, ReferenceInterface $reference)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to delete a foreign key from a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param string $referenceName
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function dropForeignKey($tableName, $schemaName, $referenceName)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to create a table in Oracle.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param array $definition
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function createTable($tableName, $schemaName, array $definition)
+ {
+ throw new Exception('Not implemented yet.');
+ }
+
+ /**
+ * Generates SQL to drop a table.
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @param bool $ifExists
+ * @return string
+ */
+ public function dropTable($tableName, $schemaName, $ifExists = true)
+ {
+ $this->_escapeChar = '';
+
+ $table = $this->prepareTable($tableName, $schemaName);
+ $sql = sprintf(
+ /** @lang text */
+ 'DROP TABLE %s',
+ $table
+ );
+
+ if ($ifExists) {
+ $sql = sprintf(
+ "BEGIN EXECUTE IMMEDIATE '%s'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END",
+ $sql
+ );
+ }
+
+ $this->_escapeChar = "'";
+
+ return $sql;
+ }
+
+ /**
+ * List all tables in database.
+ *
+ *
+ * print_r($dialect->listTables('blog'));
+ *
+ *
+ * @param string $schemaName
+ * @return string
+ */
+ public function listTables($schemaName = null)
+ {
+ $baseQuery = /** @lang text */
+ "SELECT TABLE_NAME, OWNER FROM ALL_TABLES %s ORDER BY OWNER, TABLE_NAME";
+
+ if (!empty($schemaName)) {
+ $schemaName = $this->escapeSchema($schemaName);
+
+ return sprintf($baseQuery . 'WHERE OWNER = %s', Text::upper($schemaName));
+ }
+
+ return sprintf($baseQuery, '');
+ }
+
+ /**
+ * Generates SQL checking for the existence of a schema.table
+ *
+ *
+ * echo $dialect->tableExists('posts', 'blog');
+ * echo $dialect->tableExists('posts');
+ *
+ *
+ * @param string $tableName
+ * @param string $schemaName
+ * @return string
+ */
+ public function tableExists($tableName, $schemaName = null)
+ {
+ $tableName = $this->escape(Text::upper($tableName));
+ $baseQuery = sprintf(
+ /** @lang text */
+ "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END RET FROM ALL_TABLES WHERE TABLE_NAME = %s",
+ $tableName
+ );
+
+ if (!empty($schemaName)) {
+ $schemaName = $this->escapeSchema($schemaName);
+
+ return sprintf("%s AND OWNER = %s", $baseQuery, Text::upper($schemaName));
+ }
+
+ return $baseQuery;
+ }
+
+ /**
+ * Generates SQL to create a view.
+ *
+ * @param string $viewName
+ * @param array $definition
+ * @param string $schemaName
+ * @return string
+ *
+ * @throws Exception
+ */
+ public function createView($viewName, array $definition, $schemaName = null)
+ {
+ if (!isset($definition['sql']) || empty($definition['sql'])) {
+ throw new Exception("The index 'sql' is required in the definition array");
+ }
+
+ return 'CREATE VIEW ' . Text::upper($this->prepareTable($viewName, $schemaName)) . ' AS ' . $definition['sql'];
+ }
+
+ /**
+ * Generates SQL to drop a view.
+ *
+ * @param string $viewName
+ * @param string $schemaName
+ * @param bool $ifExists
+ * @return string
+ */
+ public function dropView($viewName, $schemaName = null, $ifExists = true)
+ {
+ $this->_escapeChar = '';
+
+ $view = Text::upper($this->prepareTable($viewName, $schemaName));
+ $sql = sprintf(
+ /** @lang text */
+ 'DROP VIEW %s',
+ $view
+ );
+
+ if ($ifExists) {
+ $sql = sprintf(
+ "BEGIN FOR i IN (SELECT NULL FROM ALL_VIEWS WHERE VIEW_NAME = '%s') " .
+ "LOOP EXECUTE IMMEDIATE '%s'; END LOOP; END",
+ $view,
+ $sql
+ );
+ }
+
+ $this->_escapeChar = "'";
+
+ return $sql;
+ }
+
+ /**
+ * Generates SQL checking for the existence of a schema.view
+ *
+ * @param string $viewName
+ * @param string $schemaName
+ *
+ * @return string
+ */
+ public function viewExists($viewName, $schemaName = null)
+ {
+ $view = $this->prepareTable($viewName, $schemaName);
+ $baseSql = sprintf(
+ /** @lang text */
+ "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END RET FROM ALL_VIEWS WHERE VIEW_NAME = %s",
+ Text::upper($view)
+ );
+
+ if (!empty($schemaName)) {
+ $schemaName = $this->escapeSchema($schemaName, $this->_escapeChar);
+
+ $baseSql .= sprintf("AND OWNER = %s", Text::upper($schemaName));
+ }
+
+ return $baseSql;
+ }
+
+ /**
+ * Generates the SQL to list all views of a schema or user.
+ *
+ * @param string $schemaName
+ * @return string
+ */
+ public function listViews($schemaName = null)
+ {
+ $baseSql = /** @lang text */
+ 'SELECT VIEW_NAME FROM ALL_VIEWS';
+
+ if (!empty($schemaName)) {
+ $schemaName = $this->escapeSchema($schemaName);
+
+ $baseSql .= sprintf(" WHERE OWNER = %s", Text::upper($schemaName));
+ }
+
+ return $baseSql . ' ORDER BY VIEW_NAME';
+ }
+
+ /**
+ * Generates SQL to describe a table.
+ *
+ * @param string $table
+ * @param string $schema
+ * @return string
+ */
+ public function describeColumns($table, $schema = null)
+ {
+ $table = $this->escape($table);
+ $sql = 'SELECT TC.COLUMN_NAME, TC.DATA_TYPE, TC.DATA_LENGTH, TC.DATA_PRECISION, TC.DATA_SCALE, TC.NULLABLE, ' .
+ 'C.CONSTRAINT_TYPE, TC.DATA_DEFAULT, CC.POSITION FROM ALL_TAB_COLUMNS TC LEFT JOIN ' .
+ '(ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND ' .
+ "CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P')) ON " .
+ 'TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME WHERE TC.TABLE_NAME = %s %s '.
+ 'ORDER BY TC.COLUMN_ID';
+
+ if (!empty($schema)) {
+ $schema = $this->escapeSchema($schema);
+
+ return sprintf($sql, Text::upper($table), 'AND TC.OWNER = ' . Text::upper($schema));
+ }
+
+ return sprintf($sql, Text::upper($table), '');
+ }
+
+ /**
+ * Generates SQL to query indexes on a table.
+ *
+ * @param string $table
+ * @param string $schema
+ * @return string
+ */
+ public function describeIndexes($table, $schema = null)
+ {
+ $table = $this->escape($table);
+ $sql = 'SELECT I.TABLE_NAME, 0 AS C0, I.INDEX_NAME, IC.COLUMN_POSITION, IC.COLUMN_NAME ' .
+ 'FROM ALL_INDEXES I JOIN ALL_IND_COLUMNS IC ON I.INDEX_NAME = IC.INDEX_NAME WHERE I.TABLE_NAME = ' .
+ Text::upper($table);
+
+ if (!empty($schema)) {
+ $schema = $this->escapeSchema($schema);
+
+ $sql .= ' AND IC.INDEX_OWNER = %s'. Text::upper($schema);
+ }
+
+ return $sql;
+ }
+
+ public function describeReferences($table, $schema = null)
+ {
+ $table = $this->escape($table);
+
+ $sql = 'SELECT AC.TABLE_NAME, CC.COLUMN_NAME, AC.CONSTRAINT_NAME, AC.R_OWNER, RCC.TABLE_NAME R_TABLE_NAME, ' .
+ 'RCC.COLUMN_NAME R_COLUMN_NAME FROM ALL_CONSTRAINTS AC JOIN ALL_CONS_COLUMNS CC ' .
+ 'ON AC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME JOIN ALL_CONS_COLUMNS RCC ON AC.R_OWNER = RCC.OWNER ' .
+ "AND AC.R_CONSTRAINT_NAME = RCC.CONSTRAINT_NAME WHERE AC.CONSTRAINT_TYPE='R' ";
+
+ if (!empty($schema)) {
+ $schema = $this->escapeSchema($schema);
+ $sql .= 'AND AC.OWNER = ' . Text::upper($schema) . ' AND AC.TABLE_NAME = ' . Text::upper($table);
+ } else {
+ $sql .= 'AND AC.TABLE_NAME = ' . Text::upper($table);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Generates the SQL to describe the table creation options.
+ *
+ * @param string $table
+ * @param string $schema
+ * @return string
+ */
+ public function tableOptions($table, $schema = null)
+ {
+ return '';
+ }
+
+ /**
+ * Checks whether the platform supports savepoints.
+ *
+ * @return bool
+ */
+ public function supportsSavepoints()
+ {
+ return false;
+ }
+
+ /**
+ * Checks whether the platform supports releasing savepoints.
+ *
+ * @return bool
+ */
+ public function supportsReleaseSavepoints()
+ {
+ return false;
+ }
+
+ /**
+ * Prepares table for this RDBMS.
+ *
+ * @param string $table
+ * @param string $schema
+ * @param string $alias
+ * @param string $escapeChar
+ * @return string
+ */
+ protected function prepareTable($table, $schema = null, $alias = null, $escapeChar = null)
+ {
+ $table = $this->escape($table, $escapeChar);
+
+ // Schema
+ if (!empty($schema)) {
+ $table = $this->escapeSchema($schema, $escapeChar) . '.' . $table;
+ }
+
+ // Alias
+ if (!empty($alias)) {
+ $table .= ' ' . $this->escape($alias, $escapeChar);
+ }
+
+ return $table;
+ }
+}
diff --git a/Library/Phalcon/Db/Dialect/README.md b/Library/Phalcon/Db/Dialect/README.md
new file mode 100644
index 000000000..75a889c12
--- /dev/null
+++ b/Library/Phalcon/Db/Dialect/README.md
@@ -0,0 +1,50 @@
+# Phalcon\Db\Dialect
+
+## MysqlExtended
+
+Generates database specific SQL for the MySQL RDBMS.
+
+This is an extended MySQL dialect that introduces workarounds for some common MySQL-only functions like
+search based on FULLTEXT indexes and operations with date intervals. Since PHQL does not support
+these syntax you can use these functions:
+
+```php
+use Phalcon\Db\Adapter\Pdo\Mysql;
+use Phalcon\Db\Adapter\Pdo\MysqlExtended;
+
+$di->set('db', function() {
+ /** @var \Phalcon\DiInterface $this */
+ return new Mysql([
+ 'host' => $this->getShared('config')->database->host,
+ 'username' => $this->getShared('config')->database->username,
+ 'password' => $this->getShared('config')->database->password,
+ 'dbname' => $this->getShared('config')->database->name,
+ 'dialectClass' => MysqlExtended::class
+ ]);
+});
+```
+
+Usage:
+
+```php
+// SELECT `customers`.`created_at` - INTERVAL 7 DAY FROM `customers`
+$data = $this->modelsManager->executeQuery(
+ 'SELECT created_at - DATE_INTERVAL(7, "DAY") FROM App\Models\Customers'
+);
+
+// SELECT `customers`.`id`, `customers`.`name` FROM `customers`
+// WHERE MATCH(`customers`.`name`, `customers`.`description`) AGAINST ("+CEO")
+$data = $this->modelsManager->executeQuery(
+ 'SELECT id, name FROM App\Models\Customers WHERE FULLTEXT_MATCH(name, description, "+CEO")'
+);
+
+// SELECT `customers`.`id`, `customers`.`name` FROM `customers`
+// WHERE MATCH(`customers`.`name`, `customers`.`description`) AGAINST ("+CEO" IN BOOLEAN MODE)
+$data = $this->modelsManager->executeQuery(
+ 'SELECT id, name FROM App\Models\Customers WHERE FULLTEXT_MATCH_BMODE(name, description, "+CEO")'
+);
+```
+
+## Oracle
+
+Generates database specific SQL for the Oracle RDBMS.
diff --git a/Library/Phalcon/Db/README.md b/Library/Phalcon/Db/README.md
deleted file mode 100644
index f32bd35c7..000000000
--- a/Library/Phalcon/Db/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-Phalcon\Db
-==========
-
-Usage examples of the adapters available here:
-
-Cacheable\Mysql
----------------
-Implements an agressive cache. Every query performed is cached with the same lifetime.
-This adapter is specially suitable for applications with very few inserts/updates/deletes
-and a higher read rate.
-
-```php
-
-$di->set('db', function() use ($config) {
-
- $connection = new \Phalcon\Db\Adapter\Cacheable\Mysql(array(
- "host" => $config->database->host,
- "username" => $config->database->username,
- "password" => $config->database->password,
- "dbname" => $config->database->name,
- "options" => array(
- \PDO::ATTR_EMULATE_PREPARES => false
- )
- ));
-
- $frontCache = new \Phalcon\Cache\Frontend\Data(array(
- "lifetime" => 2592000
- ));
-
- //File backend settings
- $connection->setCache(new \Phalcon\Cache\Backend\File($frontCache, array(
- "cacheDir" => __DIR__ . "/../../var/db/",
- )));
-
- return $connection;
-});
-
-```
-
-Dialect\MysqlExtended
----------------------
-This is an extended MySQL dialect that introduces workarounds for some common MySQL-only functions like
-searchs based on FULLTEXT indexes and operations with date intervals. Since PHQL does not support
-these syntax you can use these functions:
-
-```php
-
-$di->set('db', function() use ($config) {
- return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
- "host" => $config->database->host,
- "username" => $config->database->username,
- "password" => $config->database->password,
- "dbname" => $config->database->name,
- "dialectClass" => '\Phalcon\Db\Dialect\MysqlExtended'
- ));
-});
-
-```
-
-Usage:
-
-```php
-
-// SELECT `customers`.`created_at` - INTERVAL 7 DAY FROM `customers`
-$data = $this->modelsManager->executeQuery(
- 'SELECT created_at - DATE_INTERVAL(7, "DAY") FROM App\Models\Customers'
-);
-
-// SELECT `customers`.`id`, `customers`.`name` FROM `customers` WHERE MATCH(`customers`.`name`, `customers`.`description`) AGAINST ("+CEO")
-$data = $this->modelsManager->executeQuery(
- 'SELECT id, name FROM App\Models\Customers WHERE FULLTEXT_MATCH(name, description, "+CEO")'
-);
-
-// SELECT `customers`.`id`, `customers`.`name` FROM `customers` WHERE MATCH(`customers`.`name`, `customers`.`description`) AGAINST ("+CEO" IN BOOLEAN MODE)
-$data = $this->modelsManager->executeQuery(
- 'SELECT id, name FROM App\Models\Customers WHERE FULLTEXT_MATCH_BMODE(name, description, "+CEO")'
-);
-
-```
-
-Mongo\Client
-------------
-Extends client class of MongoDb native extension to take advantage of DbRef, Document, Collection, Db.
-
-This will solve cursor and related records problems on the ODM.
-
-```php
-$di->set('mongo', function() {
- $mongo = new \Phalcon\Db\Adapter\Mongo\Client();
- return $mongo->selectDB('sitemap');
-});
-```
-
-
diff --git a/Library/Phalcon/Db/Result/Serializable.php b/Library/Phalcon/Db/Result/Serializable.php
index c9747c342..579b3df25 100644
--- a/Library/Phalcon/Db/Result/Serializable.php
+++ b/Library/Phalcon/Db/Result/Serializable.php
@@ -25,7 +25,7 @@
*/
class Serializable
{
- protected $data = array();
+ protected $data = [];
protected $position = 0;
diff --git a/Library/Phalcon/Http/Client/Header.php b/Library/Phalcon/Http/Client/Header.php
index 3fc8ba185..6551906cd 100755
--- a/Library/Phalcon/Http/Client/Header.php
+++ b/Library/Phalcon/Http/Client/Header.php
@@ -122,7 +122,7 @@ public function parse($content)
return false;
}
- $status = array();
+ $status = [];
if (preg_match('%^HTTP/(\d(?:\.\d)?)\s+(\d{3})\s?+(.+)?$%i', $content[0], $status)) {
$this->status = array_shift($content);
$this->version = $status[1];
@@ -149,7 +149,7 @@ public function parse($content)
*/
public function build($flags = 0)
{
- $lines = array();
+ $lines = [];
if (($flags & self::BUILD_STATUS) && StatusCode::message($this->statusCode)) {
$lines[] = 'HTTP/' . $this->version . ' ' .
$this->statusCode . ' ' .
diff --git a/Library/Phalcon/Http/Client/Provider/Curl.php b/Library/Phalcon/Http/Client/Provider/Curl.php
index 70dae91de..5f8c96168 100755
--- a/Library/Phalcon/Http/Client/Provider/Curl.php
+++ b/Library/Phalcon/Http/Client/Provider/Curl.php
@@ -26,6 +26,7 @@
class Curl extends Request
{
private $handle = null;
+ private $responseHeader = '';
public static function isAvailable()
{
@@ -39,6 +40,10 @@ public function __construct()
}
$this->handle = curl_init();
+ if (!is_resource($this->handle)) {
+ throw new HttpException(curl_error($this->handle), 'curl');
+ }
+
$this->initOptions();
parent::__construct();
}
@@ -56,9 +61,16 @@ public function __clone()
return $request;
}
+ public function headerFunction($ch, $headerLine)
+ {
+ $this->responseHeader .= $headerLine;
+
+ return strlen($headerLine);
+ }
+
private function initOptions()
{
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_RETURNTRANSFER => true,
CURLOPT_AUTOREFERER => true,
CURLOPT_FOLLOWLOCATION => true,
@@ -68,8 +80,8 @@ private function initOptions()
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
CURLOPT_USERAGENT => 'Phalcon HTTP/' . self::VERSION . ' (Curl)',
CURLOPT_CONNECTTIMEOUT => 30,
- CURLOPT_TIMEOUT => 30
- ));
+ CURLOPT_TIMEOUT => 30,
+ ]);
}
public function setOption($option, $value)
@@ -92,18 +104,19 @@ public function setConnectTimeout($timeout)
$this->setOption(CURLOPT_CONNECTTIMEOUT, $timeout);
}
- private function send($customHeader = array(), $fullResponse = false)
+ protected function send($customHeader = [], $fullResponse = false)
{
if (!empty($customHeader)) {
$header = $customHeader;
} else {
- $header = array();
+ $header = [];
if (count($this->header) > 0) {
$header = $this->header->build();
}
$header[] = 'Expect:';
}
+ $this->setOption(CURLOPT_HEADERFUNCTION, [$this, 'headerFunction']);
$this->setOption(CURLOPT_HTTPHEADER, $header);
$content = curl_exec($this->handle);
@@ -112,15 +125,13 @@ private function send($customHeader = array(), $fullResponse = false)
throw new HttpException(curl_error($this->handle), $errno);
}
- $headerSize = curl_getinfo($this->handle, CURLINFO_HEADER_SIZE);
-
$response = new Response();
- $response->header->parse(substr($content, 0, $headerSize));
+ $response->header->parse($this->responseHeader);
if ($fullResponse) {
- $response->body = $content;
+ $response->body = $this->responseHeader . $content;
} else {
- $response->body = substr($content, $headerSize);
+ $response->body = $content;
}
return $response;
@@ -134,7 +145,7 @@ private function send($customHeader = array(), $fullResponse = false)
*
* @return void
*/
- private function initPostFields($params, $useEncoding = true)
+ protected function initPostFields($params, $useEncoding = true)
{
if (is_array($params)) {
foreach ($params as $param) {
@@ -154,12 +165,53 @@ private function initPostFields($params, $useEncoding = true)
}
}
+ /**
+ * Setup authentication
+ *
+ * @param string $user
+ * @param string $pass
+ * @param string $auth
+ */
+ public function setAuth($user, $pass, $auth = 'basic')
+ {
+ $this->setOption(CURLOPT_HTTPAUTH, constant('CURLAUTH_'.strtoupper($auth)));
+ $this->setOption(CURLOPT_USERPWD, $user.":".$pass);
+ }
+
+ /**
+ * Set cookies for this session
+ *
+ * @param array $cookies
+ * @link http://curl.haxx.se/docs/manpage.html
+ * @link http://www.nczonline.net/blog/2009/05/05/http-cookies-explained/
+ */
+ public function setCookies(array $cookies)
+ {
+ if (empty($cookies)) {
+ return;
+ }
+
+ $cookieList = [];
+ foreach ($cookies as $cookieName => $cookieValue) {
+ $cookie = urlencode($cookieName);
+
+ if (isset($cookieValue)) {
+ $cookie .= '=';
+ $cookie .= urlencode($cookieValue);
+ }
+
+ $cookieList[] = $cookie;
+ }
+
+ $this->setOption(CURLOPT_COOKIE, implode(';', $cookieList));
+ }
+
public function setProxy($host, $port = 8080, $user = null, $pass = null)
{
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_PROXY => $host,
CURLOPT_PROXYPORT => $port
- ));
+ ]);
if (!empty($user) && is_string($user)) {
$pair = $user;
@@ -170,7 +222,7 @@ public function setProxy($host, $port = 8080, $user = null, $pass = null)
}
}
- public function get($uri, $params = array(), $customHeader = array(), $fullResponse = false)
+ public function get($uri, $params = [], $customHeader = [], $fullResponse = false)
{
$uri = $this->resolveUri($uri);
@@ -178,16 +230,16 @@ public function get($uri, $params = array(), $customHeader = array(), $fullRespo
$uri->extendQuery($params);
}
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_URL => $uri->build(),
CURLOPT_HTTPGET => true,
CURLOPT_CUSTOMREQUEST => Method::GET,
- ));
+ ]);
return $this->send($customHeader, $fullResponse);
}
- public function head($uri, $params = array(), $customHeader = array(), $fullResponse = false)
+ public function head($uri, $params = [], $customHeader = [], $fullResponse = false)
{
$uri = $this->resolveUri($uri);
@@ -195,16 +247,16 @@ public function head($uri, $params = array(), $customHeader = array(), $fullResp
$uri->extendQuery($params);
}
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_URL => $uri->build(),
CURLOPT_HTTPGET => true,
CURLOPT_CUSTOMREQUEST => Method::HEAD,
- ));
+ ]);
return $this->send($customHeader, $fullResponse);
}
- public function delete($uri, $params = array(), $customHeader = array(), $fullResponse = false)
+ public function delete($uri, $params = [], $customHeader = [], $fullResponse = false)
{
$uri = $this->resolveUri($uri);
@@ -212,35 +264,48 @@ public function delete($uri, $params = array(), $customHeader = array(), $fullRe
$uri->extendQuery($params);
}
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_URL => $uri->build(),
CURLOPT_HTTPGET => true,
CURLOPT_CUSTOMREQUEST => Method::DELETE,
- ));
+ ]);
return $this->send($customHeader, $fullResponse);
}
- public function post($uri, $params = array(), $useEncoding = true, $customHeader = array(), $fullResponse = false)
+ public function post($uri, $params = [], $useEncoding = true, $customHeader = [], $fullResponse = false)
{
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_URL => $this->resolveUri($uri),
CURLOPT_POST => true,
CURLOPT_CUSTOMREQUEST => Method::POST,
- ));
+ ]);
$this->initPostFields($params, $useEncoding);
return $this->send($customHeader, $fullResponse);
}
- public function put($uri, $params = array(), $useEncoding = true, $customHeader = array(), $fullResponse = false)
+ public function put($uri, $params = [], $useEncoding = true, $customHeader = [], $fullResponse = false)
{
- $this->setOptions(array(
+ $this->setOptions([
CURLOPT_URL => $this->resolveUri($uri),
CURLOPT_POST => true,
CURLOPT_CUSTOMREQUEST => Method::PUT,
- ));
+ ]);
+
+ $this->initPostFields($params, $useEncoding);
+
+ return $this->send($customHeader, $fullResponse);
+ }
+
+ public function patch($uri, $params = [], $useEncoding = true, $customHeader = [], $fullResponse = false)
+ {
+ $this->setOptions([
+ CURLOPT_URL => $this->resolveUri($uri),
+ CURLOPT_POST => true,
+ CURLOPT_CUSTOMREQUEST => Method::PATCH,
+ ]);
$this->initPostFields($params, $useEncoding);
diff --git a/Library/Phalcon/Http/Client/Provider/Stream.php b/Library/Phalcon/Http/Client/Provider/Stream.php
index 6fd11d20a..d8e0f7f20 100755
--- a/Library/Phalcon/Http/Client/Provider/Stream.php
+++ b/Library/Phalcon/Http/Client/Provider/Stream.php
@@ -54,12 +54,12 @@ public function __destruct()
private function initOptions()
{
- $this->setOptions(array(
+ $this->setOptions([
'user_agent' => 'Phalcon HTTP/' . self::VERSION . ' (Stream)',
'follow_location' => 1,
'max_redirects' => 20,
'timeout' => 30
- ));
+ ]);
}
public function setOption($option, $value)
@@ -69,7 +69,7 @@ public function setOption($option, $value)
public function setOptions($options)
{
- return stream_context_set_option($this->context, array('http' => $options));
+ return stream_context_set_option($this->context, ['http' => $options]);
}
public function setTimeout($timeout)
@@ -82,13 +82,13 @@ private function errorHandler($errno, $errstr)
throw new HttpException($errstr, $errno);
}
- private function send($uri)
+ protected function send($uri)
{
if (count($this->header) > 0) {
$this->setOption('header', $this->header->build(Header::BUILD_FIELDS));
}
- set_error_handler(array($this, 'errorHandler'));
+ set_error_handler([$this, 'errorHandler']);
$content = file_get_contents($uri->build(), false, $this->context);
restore_error_handler();
@@ -99,7 +99,7 @@ private function send($uri)
return $response;
}
- private function initPostFields($params)
+ protected function initPostFields($params)
{
if (!empty($params) && is_array($params)) {
$this->header->set('Content-Type', 'application/x-www-form-urlencoded');
@@ -109,11 +109,11 @@ private function initPostFields($params)
public function setProxy($host, $port = 8080, $user = null, $pass = null)
{
- $uri = new Uri(array(
+ $uri = new Uri([
'scheme' => 'tcp',
'host' => $host,
'port' => $port
- ));
+ ]);
if (!empty($user)) {
$uri->user = $user;
@@ -125,7 +125,7 @@ public function setProxy($host, $port = 8080, $user = null, $pass = null)
$this->setOption('proxy', $uri->build());
}
- public function get($uri, $params = array())
+ public function get($uri, $params = [])
{
$uri = $this->resolveUri($uri);
@@ -133,17 +133,17 @@ public function get($uri, $params = array())
$uri->extendQuery($params);
}
- $this->setOptions(array(
+ $this->setOptions([
'method' => Method::GET,
'content' => ''
- ));
+ ]);
$this->header->remove('Content-Type');
return $this->send($uri);
}
- public function head($uri, $params = array())
+ public function head($uri, $params = [])
{
$uri = $this->resolveUri($uri);
@@ -151,17 +151,17 @@ public function head($uri, $params = array())
$uri->extendQuery($params);
}
- $this->setOptions(array(
+ $this->setOptions([
'method' => Method::HEAD,
'content' => ''
- ));
+ ]);
$this->header->remove('Content-Type');
return $this->send($uri);
}
- public function delete($uri, $params = array())
+ public function delete($uri, $params = [])
{
$uri = $this->resolveUri($uri);
@@ -169,17 +169,17 @@ public function delete($uri, $params = array())
$uri->extendQuery($params);
}
- $this->setOptions(array(
+ $this->setOptions([
'method' => Method::DELETE,
'content' => ''
- ));
+ ]);
$this->header->remove('Content-Type');
return $this->send($uri);
}
- public function post($uri, $params = array())
+ public function post($uri, $params = [])
{
$this->setOption('method', Method::POST);
@@ -188,7 +188,7 @@ public function post($uri, $params = array())
return $this->send($this->resolveUri($uri));
}
- public function put($uri, $params = array())
+ public function put($uri, $params = [])
{
$this->setOption('method', Method::PUT);
@@ -196,4 +196,13 @@ public function put($uri, $params = array())
return $this->send($this->resolveUri($uri));
}
+
+ public function patch($uri, $params = [])
+ {
+ $this->setOption('method', Method::PATCH);
+
+ $this->initPostFields($params);
+
+ return $this->send($this->resolveUri($uri));
+ }
}
diff --git a/Library/Phalcon/Http/Client/README.md b/Library/Phalcon/Http/Client/README.md
index 46ec55d2f..1dadc1377 100755
--- a/Library/Phalcon/Http/Client/README.md
+++ b/Library/Phalcon/Http/Client/README.md
@@ -11,32 +11,35 @@ Request class to make request to URI
```php
use Phalcon\Http\Client\Request;
-$provider = Request::getProvider(); // get available provider Curl or Stream
+// get available provider Curl or Stream
+$provider = Request::getProvider();
$provider->setBaseUri('http://example.com/api/');
$provider->header->set('Accept', 'application/json');
-$response = $provider->get('me/images', array(
+// GET request to http://example.com/api/me/images?access_token=1234 and return response
+$response = $provider->get('me/images', [
'access_token' => 1234
-)); // GET request to http://example.com/api/me/images?access_token=1234 and return response
+]);
echo $response->body;
-$response = $provider->post('me/images', array(
+// POST multipart/form-data request to http://example.com/api/me/images
+$response = $provider->post('me/images', [
'access_token' => 1234,
'image' => '@/home/mine/myimage.jpg'
-)); // POST multipart/form-data request to http://example.com/api/me/images
+]);
echo $response->body;
echo $response->header->get('Content-Type');
echo $response->header->statusCode;
-$response = $provider->delete('me/images', array(
+// DELETE request to http://example.com/api/me/images
+$response = $provider->delete('me/images', [
'access_token' => 1234,
'image_id' => '321'
-)); // DELETE request to http://example.com/api/me/images
+]);
echo $response->body;
-
```
diff --git a/Library/Phalcon/Http/Client/Request.php b/Library/Phalcon/Http/Client/Request.php
index 121d1f58c..f8a052340 100755
--- a/Library/Phalcon/Http/Client/Request.php
+++ b/Library/Phalcon/Http/Client/Request.php
@@ -28,7 +28,7 @@ abstract class Request
protected $baseUri;
public $header = null;
- const VERSION = '0.0.1';
+ const VERSION = '0.0.2';
public function __construct()
{
@@ -46,7 +46,7 @@ public static function getProvider()
return new Stream();
}
- throw new ProviderException('There isn\'t any available provider');
+ throw new ProviderException("There isn't any available provider");
}
public function setBaseUri($baseUri)
diff --git a/Library/Phalcon/Http/README.md b/Library/Phalcon/Http/README.md
index 4b03ca0fe..6c76a0990 100755
--- a/Library/Phalcon/Http/README.md
+++ b/Library/Phalcon/Http/README.md
@@ -20,12 +20,12 @@ echo $uri2->build(); // http://phalconphp.com/last?var1=a&var2=1
$uri3 = $uri1->resolve('last');
echo $uri3->build(); // http://phalconphp.com/foo/bar/baz/last?var1=a&var2=1
-$uri4 = new Uri(array(
+$uri4 = new Uri([
'scheme' => 'https',
'host' => 'admin.example.com',
'user' => 'john',
'pass' => 'doe'
-));
+]);
$uri5 = $uri1->resolve($uri4);
echo $uri5->build(); // https://john:doe@admin.example.com/foo/bar/baz?var1=a&var2=1
diff --git a/Library/Phalcon/Http/Uri.php b/Library/Phalcon/Http/Uri.php
index 5193b4cfb..726763936 100755
--- a/Library/Phalcon/Http/Uri.php
+++ b/Library/Phalcon/Http/Uri.php
@@ -20,7 +20,7 @@
class Uri
{
- private $parts = array();
+ private $parts = [];
public function __construct($uri = null)
{
@@ -31,7 +31,7 @@ public function __construct($uri = null)
if (is_string($uri)) {
$this->parts = parse_url($uri);
if (!empty($this->parts['query'])) {
- $query = array();
+ $query = [];
parse_str($this->parts['query'], $query);
$this->parts['query'] = $query;
}
@@ -139,7 +139,7 @@ public function extend($uri)
$this->parts = array_merge(
$this->parts,
- array_diff_key($uri->parts, array_flip(array('query', 'path')))
+ array_diff_key($uri->parts, array_flip(['query', 'path']))
);
if (!empty($uri->parts['query'])) {
@@ -155,8 +155,8 @@ public function extend($uri)
public function extendQuery($params)
{
- $query = empty($this->parts['query']) ? array() : $this->parts['query'];
- $params = empty($params) ? array() : $params;
+ $query = empty($this->parts['query']) ? [] : $this->parts['query'];
+ $params = empty($params) ? [] : $params;
$this->parts['query'] = array_merge($query, $params);
return $this;
diff --git a/Library/Phalcon/Legacy/Crypt.php b/Library/Phalcon/Legacy/Crypt.php
new file mode 100644
index 000000000..0be9e9f42
--- /dev/null
+++ b/Library/Phalcon/Legacy/Crypt.php
@@ -0,0 +1,446 @@
+ |
+ | Eduar Carvajal |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Legacy;
+
+use Phalcon\Legacy\Crypt\Exception;
+
+/**
+ * Phalcon\Legacy\Crypt
+ *
+ * Port of Phalcon 2.0.x Phalcon\Crypt.
+ * Provides encryption facilities to phalcon applications.
+ *
+ *
+ * $crypt = new \Phalcon\Legacy\Crypt();
+ *
+ * $key = 'le password';
+ * $text = 'This is a secret text';
+ *
+ * $encrypted = $crypt->encrypt($text, $key);
+ *
+ * echo $crypt->decrypt($encrypted, $key);
+ *
+ *
+ * @link https://github.com/phalcon/incubator/issues/563
+ * @package Phalcon\Legacy
+ */
+class Crypt implements CryptInterface
+{
+ protected $key;
+
+ protected $padding = 0;
+
+ protected $mode = MCRYPT_MODE_CBC;
+
+ protected $cipher = "rijndael-256";
+
+ const PADDING_DEFAULT = 0;
+
+ const PADDING_ANSI_X_923 = 1;
+
+ const PADDING_PKCS7 = 2;
+
+ const PADDING_ISO_10126 = 3;
+
+ const PADDING_ISO_IEC_7816_4 = 4;
+
+ const PADDING_ZERO = 5;
+
+ const PADDING_SPACE = 6;
+
+ /**
+ * Changes the padding scheme used
+ *
+ * @param int $scheme
+ * @return CryptInterface
+ */
+ public function setPadding($scheme)
+ {
+ $this->padding = $scheme;
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @param string $cipher
+ * @return CryptInterface
+ */
+ public function setCipher($cipher)
+ {
+ $this->cipher = $cipher;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current cipher
+ *
+ * @return string
+ */
+ public function getCipher()
+ {
+ return $this->cipher;
+ }
+
+ /**
+ * Sets the encrypt/decrypt mode
+ *
+ * @param string $mode
+ * @return CryptInterface
+ */
+ public function setMode($mode)
+ {
+ $this->mode = $mode;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current encryption mode
+ *
+ * @return string
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Sets the encryption key
+ *
+ * @param string $key
+ * @return CryptInterface
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+
+ return $this;
+ }
+
+ /**
+ * Returns the encryption key
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * Pads texts before encryption
+ *
+ * @link http://www.di-mgt.com.au/cryptopad.html
+ *
+ * @param string $text
+ * @param string $mode
+ * @param int $blockSize
+ * @param int $paddingType
+ * @return string
+ * @throws Exception
+ */
+ protected function cryptPadText($text, $mode, $blockSize, $paddingType)
+ {
+ $paddingSize = 0;
+ $padding = null;
+
+ if ($mode == MCRYPT_MODE_CBC || $mode == MCRYPT_MODE_ECB) {
+ $paddingSize = $blockSize - (strlen($text) % $blockSize);
+ if ($paddingSize >= 256) {
+ throw new Exception("Block size is bigger than 256");
+ }
+
+ switch ($paddingType) {
+ case self::PADDING_ANSI_X_923:
+ $padding = str_repeat(chr(0), $paddingSize - 1) . chr($paddingSize);
+ break;
+ case self::PADDING_PKCS7:
+ $padding = str_repeat(chr($paddingSize), $paddingSize);
+ break;
+ case self::PADDING_ISO_10126:
+ $padding = "";
+ foreach (range(0, $paddingSize - 2) as $i) {
+ $padding .= chr(rand());
+ }
+ $padding .= chr($paddingSize);
+ break;
+ case self::PADDING_ISO_IEC_7816_4:
+ $padding = chr(0x80) . str_repeat(chr(0), $paddingSize - 1);
+ break;
+ case self::PADDING_ZERO:
+ $padding = str_repeat(chr(0), $paddingSize);
+ break;
+ case self::PADDING_SPACE:
+ $padding = str_repeat(" ", $paddingSize);
+ break;
+ default:
+ $paddingSize = 0;
+ break;
+ }
+ }
+
+ if (!$paddingSize) {
+ return $text;
+ }
+
+ if ($paddingSize > $blockSize) {
+ throw new Exception("Invalid padding size");
+ }
+
+ return $text . substr($padding, 0, $paddingSize);
+ }
+
+ /**
+ * Removes $paddingType padding from text
+ * If the function detects that the text was not padded, it will return it unmodified
+ *
+ * @param string $text Message to be unpadded
+ * @param string $mode Encryption mode; unpadding is applied only in CBC or ECB mode
+ * @param int $blockSize Cipher block size
+ * @param int $paddingType Padding scheme
+ * @return string
+ */
+ protected function cryptUnpadText($text, $mode, $blockSize, $paddingType)
+ {
+ $paddingSize = 0;
+ $length = strlen($text);
+
+ if ($length > 0 && ($length % $blockSize == 0) && ($mode == MCRYPT_MODE_CBC || $mode == MCRYPT_MODE_ECB)) {
+ switch ($paddingType) {
+ case self::PADDING_ANSI_X_923:
+ $last = substr($text, $length - 1, 1);
+ $ord = (int) ord($last);
+ if ($ord <= $blockSize) {
+ $paddingSize = $ord;
+ $padding = str_repeat(chr(0), $paddingSize - 1) . $last;
+ if (substr($text, $length - $paddingSize) != $padding) {
+ $paddingSize = 0;
+ }
+ }
+ break;
+ case self::PADDING_PKCS7:
+ $last = substr($text, $length - 1, 1);
+ $ord = (int) ord($last);
+ if ($ord <= $blockSize) {
+ $paddingSize = $ord;
+ $padding = str_repeat(chr($paddingSize), $paddingSize);
+ if (substr($text, $length - $paddingSize) != $padding) {
+ $paddingSize = 0;
+ }
+ }
+ break;
+ case self::PADDING_ISO_10126:
+ $last = substr($text, $length - 1, 1);
+ $paddingSize = (int) ord($last);
+ break;
+ case self::PADDING_ISO_IEC_7816_4:
+ $i = $length - 1;
+ while ($i > 0 && $text[$i] == 0x00 && $paddingSize < $blockSize) {
+ $paddingSize++;
+ $i--;
+ }
+ if ($text[$i] == 0x80) {
+ $paddingSize++;
+ } else {
+ $paddingSize = 0;
+ }
+ break;
+ case self::PADDING_ZERO:
+ $i = $length - 1;
+ while ($i >= 0 && $text[$i] == 0x00 && $paddingSize <= $blockSize) {
+ $paddingSize++;
+ $i--;
+ }
+ break;
+ case self::PADDING_SPACE:
+ $i = $length - 1;
+ while ($i >= 0 && $text[$i] == 0x20 && $paddingSize <= $blockSize) {
+ $paddingSize++;
+ $i--;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ($paddingSize && $paddingSize <= $blockSize) {
+ if ($paddingSize < $length) {
+ return substr($text, 0, $length - $paddingSize);
+ }
+ return "";
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Encrypts a text
+ *
+ *
+ * $encrypted = $crypt->encrypt("Ultra-secret text", "encrypt password");
+ *
+ *
+ * @param string $text
+ * @param mixed $key
+ * @return string
+ * @throws Exception
+ */
+ public function encrypt($text, $key = null)
+ {
+ if (!function_exists("mcrypt_get_iv_size")) {
+ throw new Exception("mcrypt extension is required");
+ }
+
+ if ($key === null) {
+ $encryptKey = $this->key;
+ } else {
+ $encryptKey = $key;
+ }
+
+ if (empty($encryptKey)) {
+ throw new Exception("Encryption key cannot be empty");
+ }
+
+ $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
+ if (strlen($encryptKey) > $ivSize) {
+ throw new Exception("Size of key is too large for this algorithm");
+ }
+
+ $iv = strval(mcrypt_create_iv($ivSize, MCRYPT_RAND));
+ $blockSize = intval(mcrypt_get_block_size($this->cipher, $this->mode));
+
+ if ($this->padding != 0 && ($this->mode == MCRYPT_MODE_CBC || $this->mode == MCRYPT_MODE_ECB)) {
+ $padded = $this->cryptPadText($text, $this->mode, $blockSize, $this->padding);
+ } else {
+ $padded = $text;
+ }
+
+ return $iv . mcrypt_encrypt($this->cipher, $encryptKey, $padded, $this->mode, $iv);
+ }
+
+ /**
+ * Decrypts a text
+ *
+ *
+ * echo $crypt->decrypt($encrypted, "decrypt password");
+ *
+ *
+ * @param string $text
+ * @param string $key
+ * @return string
+ * @throws Exception
+ */
+ public function decrypt($text, $key = null)
+ {
+ if (!function_exists("mcrypt_get_iv_size")) {
+ throw new Exception("mcrypt extension is required");
+ }
+
+ if ($key === null) {
+ $decryptKey = $this->key;
+ } else {
+ $decryptKey = $key;
+ }
+
+ if (empty($decryptKey)) {
+ throw new Exception("Decryption key cannot be empty");
+ }
+
+ $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
+ $keySize = strlen($decryptKey);
+ if ($keySize > $ivSize) {
+ throw new Exception("Size of key is too large for this algorithm");
+ }
+
+ if ($keySize > strlen($text)) {
+ throw new Exception(
+ "Size of IV is larger than text to decrypt. Are you trying to decrypt an uncrypted text?"
+ );
+ }
+
+ $data = substr($text, $ivSize);
+ $decrypted = mcrypt_decrypt($this->cipher, $decryptKey, $data, $this->mode, substr($text, 0, $ivSize));
+ $blockSize = mcrypt_get_block_size($this->cipher, $this->mode);
+
+ if ($this->mode == MCRYPT_MODE_CBC || $this->mode == MCRYPT_MODE_ECB) {
+ return $this->cryptUnpadText($decrypted, $this->mode, $blockSize, $this->padding);
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Encrypts a text returning the result as a base64 string
+ *
+ * @param string $text
+ * @param mixed $key
+ * @param bool $safe
+ * @return string
+ */
+ public function encryptBase64($text, $key = null, $safe = false)
+ {
+ if ($safe) {
+ return strtr(base64_encode($this->encrypt($text, $key)), "+/", "-_");
+ }
+
+ return base64_encode($this->encrypt($text, $key));
+ }
+
+ /**
+ * Decrypt a text that is coded as a base64 string
+ *
+ * @param string $text
+ * @param mixed $key
+ * @param bool $safe
+ * @return string
+ */
+ public function decryptBase64($text, $key = null, $safe = false)
+ {
+ if ($safe) {
+ return $this->decrypt(base64_decode(strtr($text, "-_", "+/")), $key);
+ }
+
+ return $this->decrypt(base64_decode($text), $key);
+ }
+
+ /**
+ * Returns a list of available cyphers
+ *
+ * @return array
+ */
+ public function getAvailableCiphers()
+ {
+ return mcrypt_list_algorithms();
+ }
+
+ /**
+ * Returns a list of available modes
+ *
+ * @return array
+ */
+ public function getAvailableModes()
+ {
+ return mcrypt_list_modes();
+ }
+}
diff --git a/Library/Phalcon/Legacy/Crypt/Exception.php b/Library/Phalcon/Legacy/Crypt/Exception.php
new file mode 100644
index 000000000..3b89a70c2
--- /dev/null
+++ b/Library/Phalcon/Legacy/Crypt/Exception.php
@@ -0,0 +1,31 @@
+ |
+ | Eduar Carvajal |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Legacy\Crypt;
+
+/**
+ * Phalcon\Legacy\Crypt\Exception
+ *
+ * Exceptions thrown in Phalcon\Crypt use this class
+ *
+ * @package Phalcon\Legacy
+ */
+class Exception extends \Phalcon\Exception
+{
+}
diff --git a/Library/Phalcon/Legacy/CryptInterface.php b/Library/Phalcon/Legacy/CryptInterface.php
new file mode 100644
index 000000000..5a6da172b
--- /dev/null
+++ b/Library/Phalcon/Legacy/CryptInterface.php
@@ -0,0 +1,126 @@
+ |
+ | Eduar Carvajal |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Legacy;
+
+/**
+ * Phalcon\Legacy\CryptInterface
+ *
+ * Interface for Phalcon\Legacy\Crypt
+ *
+ * @link https://github.com/phalcon/incubator/issues/563
+ * @package Phalcon\Legacy
+ */
+interface CryptInterface
+{
+ /**
+ * Sets the cipher algorithm
+ *
+ * @param string $cipher
+ * @return CryptInterface
+ */
+ public function setCipher($cipher);
+
+ /**
+ * Returns the current cipher
+ *
+ * @return string
+ */
+ public function getCipher();
+
+ /**
+ * Sets the encrypt/decrypt mode
+ *
+ * @param string $mode
+ * @return CryptInterface
+ */
+ public function setMode($mode);
+
+ /**
+ * Returns the current encryption mode
+ *
+ * @return string
+ */
+ public function getMode();
+
+ /**
+ * Sets the encryption key
+ *
+ * @param string $key
+ * @return CryptInterface
+ */
+ public function setKey($key);
+
+ /**
+ * Returns the encryption key
+ *
+ * @return string
+ */
+ public function getKey();
+
+ /**
+ * Encrypts a text
+ *
+ * @param string $text
+ * @param mixed $key
+ * @return string
+ */
+ public function encrypt($text, $key = null);
+
+ /**
+ * Decrypts a text
+ *
+ * @param string $text
+ * @param string $key
+ * @return string
+ */
+ public function decrypt($text, $key = null);
+
+ /**
+ * Encrypts a text returning the result as a base64 string
+ *
+ * @param string $text
+ * @param mixed $key
+ * @return string
+ */
+ public function encryptBase64($text, $key = null);
+
+ /**
+ * Decrypt a text that is coded as a base64 string
+ *
+ * @param string $text
+ * @param mixed $key
+ * @return string
+ */
+ public function decryptBase64($text, $key = null);
+
+ /**
+ * Returns a list of available cyphers
+ *
+ * @return array
+ */
+ public function getAvailableCiphers();
+
+ /**
+ * Returns a list of available modes
+ *
+ * @return array
+ */
+ public function getAvailableModes();
+}
diff --git a/Library/Phalcon/Loader/Extended.php b/Library/Phalcon/Loader/Extended.php
deleted file mode 100644
index 44a3f4127..000000000
--- a/Library/Phalcon/Loader/Extended.php
+++ /dev/null
@@ -1,316 +0,0 @@
- |
- +------------------------------------------------------------------------+
-*/
-
-namespace Phalcon\Loader;
-
-use Phalcon\Loader;
-
-/**
- * Phalcon\Loader\Extended
- *
- * This component extends Phalcon\Loader and adds ability to
- * set multiple directories a namespace.
- *
- *
- * use Phalcon\Loader\Extended as Loader;
- *
- * // Creates the autoloader
- * $loader = new Loader();
- *
- * // Register some namespaces
- * $loader->registerNamespaces(
- * [
- * 'Example\Base' => 'vendor/example/base/',
- * 'Some\Adapters' => [
- * 'vendor/example/adapters/src/',
- * 'vendor/example/adapters/test/',
- * ]
- * ]
- * );
- *
- * // Register autoloader
- * $loader->register();
- *
- * // Requiring this class will automatically include file vendor/example/adapters/src/Some.php
- * $adapter = Example\Adapters\Some();
- *
- * // Requiring this class will automatically include file vendor/example/adapters/test/Another.php
- * $adapter = Example\Adapters\Another();
- *
- *
- * @package Phalcon\Loader
- */
-class Extended extends Loader
-{
- /**
- * Register namespaces and their related directories
- *
- * @param array $namespaces
- * @param bool $merge
- * @return $this
- */
- public function registerNamespaces(array $namespaces, $merge = false)
- {
- $preparedNamespaces = $this->prepareNamespace($namespaces);
-
- if ($merge) {
- $currentNamespaces = $this->_namespaces;
- if (is_array($currentNamespaces)) {
- foreach ($preparedNamespaces as $name => $paths) {
- if (!isset($currentNamespaces[$name])) {
- $currentNamespaces[$name] = [];
- }
-
- $currentNamespaces[$name] = array_merge($currentNamespaces[$name], $paths);
- }
-
- $this->_namespaces = $currentNamespaces;
- } else {
- $this->_namespaces = $preparedNamespaces;
- }
- } else {
- $this->_namespaces = $preparedNamespaces;
- }
-
- return $this;
- }
-
- protected function prepareNamespace(array $namespace)
- {
- $prepared = [];
- foreach ($namespace as $name => $path) {
- if (!is_array($path)) {
- $path = [$path];
- }
-
- $prepared[$name] = $path;
- }
-
- return $prepared;
- }
-
- /**
- * Autoloads the registered classes
- *
- * @param string $className
- * @return bool
- */
- public function autoLoad($className)
- {
- if (!is_string($className) || empty($className)) {
- return false;
- }
-
- if (is_object($this->_eventsManager)) {
- $this->_eventsManager->fire('loader:beforeCheckClass', $this, $className);
- }
-
- /**
- * First we check for static paths for classes
- */
- if (is_array($this->_classes) && isset($this->_classes[$className])) {
- $filePath = $this->_classes[$className];
-
- if (is_file($filePath)) {
- if (is_object($this->_eventsManager)) {
- $this->_foundPath = $filePath;
- $this->_eventsManager->fire('loader:pathFound', $this, $filePath);
- }
-
- require $filePath;
- return true;
- }
- }
-
- $ds = DIRECTORY_SEPARATOR;
- $ns = "\\";
-
- /**
- * Checking in namespaces
- */
- if (is_array($this->_namespaces)) {
- foreach ($this->_namespaces as $nsPrefix => $directories) {
- /**
- * The class name must start with the current namespace
- */
- if (0 === strpos($className, $nsPrefix)) {
- $fileName = substr($className, strlen($nsPrefix . $ns));
- $fileName = str_replace($ns, $ds, $fileName);
-
- if ($fileName) {
- foreach ($directories as $directory) {
- /**
- * Add a trailing directory separator if the user forgot to do that
- */
- $fixedDirectory = rtrim($directory, $ds) . $ds;
-
- foreach ($this->_extensions as $extension) {
- $filePath = $fixedDirectory . $fileName . "." . $extension;
-
- /**
- * Check if a events manager is available
- */
- if (is_object($this->_eventsManager)) {
- $this->_checkedPath = $filePath;
- $this->_eventsManager->fire('loader:beforeCheckPath', $this);
- }
-
- /**
- * This is probably a good path, let's check if the file exists
- */
- if (is_file($filePath)) {
- if (is_object($this->_eventsManager)) {
- $this->_foundPath = $filePath;
- $this->_eventsManager->fire('loader:pathFound', $this, $filePath);
- }
-
- /**
- * Simulate a require
- */
- require $filePath;
-
- /**
- * Return true mean success
- */
- return true;
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Checking in prefixes
- */
- if (is_array($this->_prefixes)) {
- foreach ($this->_prefixes as $prefix => $directory) {
- /**
- * The class name starts with the prefix?
- */
- if (0 === strpos($className, $prefix)) {
- /**
- * Get the possible file path
- */
- $fileName = str_replace($prefix . $ns, "", $className);
- $fileName = str_replace($prefix . "_", "", $fileName);
- $fileName = str_replace("_", $ds, $fileName);
-
- if ($fileName) {
- /**
- * Add a trailing directory separator if the user forgot to do that
- */
- $fixedDirectory = rtrim($directory, $ds) . $ds;
-
- foreach ($this->_extensions as $extension) {
- $filePath = $fixedDirectory . $fileName . "." . $extension;
-
- if (is_object($this->_eventsManager)) {
- $this->_checkedPath = $filePath;
- $this->_eventsManager->fire('loader:beforeCheckPath', $this, $filePath);
- }
-
- if (is_file($filePath)) {
- /**
- * Call 'pathFound' event
- */
- if (is_object($this->_eventsManager)) {
- $this->_foundPath = $filePath;
- $this->_eventsManager->fire('loader:pathFound', $this, $filePath);
- }
-
- require $filePath;
- return true;
- }
- }
- }
- }
- }
- }
-
- /**
- * Change the pseudo-separator by the directory separator in the class name
- */
- $dsClassName = str_replace("_", $ds, $className);
-
- /**
- * And change the namespace separator by directory separator too
- */
- $nsClassName = str_replace("\\", $ds, $dsClassName);
-
- /**
- * Checking in directories
- */
- if (is_array($this->_directories)) {
- foreach ($this->_directories as $directory) {
- /**
- * Add a trailing directory separator if the user forgot to do that
- */
- $fixedDirectory = rtrim($directory, $ds) . $ds;
-
- foreach ($this->_extensions as $extension) {
- /**
- * Create a possible path for the file
- */
- $filePath = $fixedDirectory . $nsClassName . "." . $extension;
-
- if (is_object($this->_eventsManager)) {
- $this->_checkedPath = $filePath;
- $this->_eventsManager->fire('loader:beforeCheckPath', $this, $filePath);
- }
-
- /**
- * Check in every directory if the class exists here
- */
- if (is_file($filePath)) {
- /**
- * Call 'pathFound' event
- */
- if (is_object($this->_eventsManager)) {
- $this->_foundPath = $filePath;
- $this->_eventsManager->fire('loader:pathFound', $this, $filePath);
- }
-
- /**
- * Simulate a require
- */
- require $filePath;
-
- /**
- * Return true meaning success
- */
- return true;
- }
- }
- }
- }
-
- /**
- * Call 'afterCheckClass' event
- */
- if (is_object($this->_eventsManager)) {
- $this->_eventsManager->fire('loader:afterCheckClass', $this, $className);
- }
-
- /**
- * Cannot find the class, return false
- */
- return false;
- }
-}
diff --git a/Library/Phalcon/Loader/PSR.php b/Library/Phalcon/Loader/PSR.php
deleted file mode 100644
index aebe5f239..000000000
--- a/Library/Phalcon/Loader/PSR.php
+++ /dev/null
@@ -1,70 +0,0 @@
- |
- | Serghei Iakovlev |
- +------------------------------------------------------------------------+
-*/
-
-namespace Phalcon\Loader;
-
-use Phalcon\Loader;
-
-/**
- * Phalcon\Loader\PSR.
- * Implements PSR-0 autoloader for your apps.
- *
- * @package Phalcon\Loader
- */
-class PSR extends Loader
-{
- /**
- * Namespace separator
- * @var string
- */
- protected $namespaceSeparator = '\\';
-
- /**
- * Loads the given class or interface.
- *
- * @param string $className The name of the class to load.
- *
- * @return bool
- */
- public function autoLoad($className)
- {
- // Reduce slashes
- $className = ltrim($className, $this->namespaceSeparator);
-
- $array = explode($this->namespaceSeparator, $className);
-
- if (array_key_exists($array[0], $this->_namespaces)) {
- $array[0] = $this->_namespaces[$array[0]];
- $class = array_pop($array);
- array_push($array, str_replace("_", DIRECTORY_SEPARATOR, $class));
-
- $file = implode($array, DIRECTORY_SEPARATOR);
-
- foreach ($this->_extensions as $ext) {
- if (file_exists($file . ".$ext")) {
- require $file . ".$ext";
- return true;
- }
- }
- }
-
- // If it did not fit standard PSR-0, pass it on to the original Phalcon autoloader
- return parent::autoLoad($className);
- }
-}
diff --git a/Library/Phalcon/Loader/README.md b/Library/Phalcon/Loader/README.md
deleted file mode 100644
index 9891a73bc..000000000
--- a/Library/Phalcon/Loader/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Phalcon\Loader
-
-## Phalcon\Loader\Extended
-
-This component extends [Phalcon\Loader][1] and adds ability to
-set multiple directories a namespace.
-
-```php
-use Phalcon\Loader\Extended as Loader;
-
-// Creates the autoloader
-$loader = new Loader();
-
-// Register some namespaces
-$loader->registerNamespaces(
- [
- 'Example\Base' => 'vendor/example/base/',
- 'Some\Adapters' => [
- 'vendor/example/adapters/src/',
- 'vendor/example/adapters/test/',
- ]
- ]
-);
-
-// Register autoloader
-$loader->register();
-
-// Requiring this class will automatically include
-// file vendor/example/adapters/src/Some.php
-$adapter = Example\Adapters\Some();
-
-// Requiring this class will automatically include
-// file vendor/example/adapters/test/Another.php
-$adapter = Example\Adapters\Another();
-```
-
-## Phalcon\Loader\PSR
-
-Implements [PSR-0][2] autoloader for your apps.
-
-[1]: https://docs.phalconphp.com/en/latest/api/Phalcon_Loader.html
-[2]: http://www.php-fig.org/psr/psr-0/
diff --git a/Library/Phalcon/Logger/Formatter/Firelogger.php b/Library/Phalcon/Logger/Formatter/Firelogger.php
index 1b3128c29..ba352dc5a 100644
--- a/Library/Phalcon/Logger/Formatter/Firelogger.php
+++ b/Library/Phalcon/Logger/Formatter/Firelogger.php
@@ -124,7 +124,7 @@ public function getTypeString($type)
* @param integer $order How many logs are stored in the stack already.
* @return mixed
*/
- public function format($message, $type, $timestamp, $context = array(), $trace = null, $order = 0)
+ public function format($message, $type, $timestamp, $context = [], $trace = null, $order = 0)
{
$level = $this->getTypeString($type);
@@ -136,16 +136,16 @@ public function format($message, $type, $timestamp, $context = array(), $trace =
$message = '';
}
- $item = array(
+ $item = [
'name' => $this->name,
- 'args' => array(),
+ 'args' => [],
'level' => $level,
'timestamp' => $timestamp,
'order' => $order, // PHP is really fast, timestamp has insufficient resolution for log records ordering
'time' => gmdate('H:i:s', (int) $timestamp) . '.000',
'template' => $message,
'message' => $message
- );
+ ];
if ($this->style) {
$item['style'] = $this->style;
@@ -154,11 +154,11 @@ public function format($message, $type, $timestamp, $context = array(), $trace =
if (isset($exception)) {
// exception with backtrace
$traceInfo = $this->extractTrace($exception->getTrace());
- $item['exc_info'] = array(
+ $item['exc_info'] = [
$exception->getMessage(),
$exception->getFile(),
$traceInfo[0]
- );
+ ];
$item['exc_frames'] = $traceInfo[1];
$item['exc_text'] = get_class($exception);
$item['template'] = $exception->getMessage();
@@ -175,16 +175,16 @@ public function format($message, $type, $timestamp, $context = array(), $trace =
if (isset($trace)) {
$traceInfo = $this->extractTrace($trace);
- $item['exc_info'] = array(
+ $item['exc_info'] = [
'',
'',
$traceInfo[0]
- );
+ ];
$item['exc_frames'] = $traceInfo[1];
}
if (isset($richMessage)) {
- $item['args'] = array($richMessage);
+ $item['args'] = [$richMessage];
}
}
@@ -224,7 +224,7 @@ protected function pickle($var, $level = 0)
}
$var[$marker] = true;
- $res = array();
+ $res = [];
foreach ($var as $k => &$v) {
if ($k !== $marker) {
@@ -241,14 +241,14 @@ protected function pickle($var, $level = 0)
$arr = (array) $var;
$arr['__class##'] = get_class($var);
- static $list = array(); // detects recursions
+ static $list = []; // detects recursions
if (in_array($var, $list, true)) {
return '*RECURSION*';
}
if ($level < $this->maxPickleDepth || !$this->maxPickleDepth) {
$list[] = $var;
- $res = array();
+ $res = [];
foreach ($arr as $k => &$v) {
if ($k[0] === "\x00") {
@@ -281,11 +281,11 @@ protected function pickle($var, $level = 0)
*/
protected function extractTrace($trace)
{
- $t = array();
- $f = array();
+ $t = [];
+ $f = [];
foreach ($trace as $frame) {
// prevent notices about invalid indices, wasn't able to google smart solution, PHP is dumb ass
- $frame += array(
+ $frame += [
'file' => null,
'line' => null,
'class' => null,
@@ -293,19 +293,19 @@ protected function extractTrace($trace)
'function' => null,
'object' => null,
'args' => null
- );
+ ];
- $t[] = array(
+ $t[] = [
$frame['file'],
$frame['line'],
$frame['class'] . $frame['type'] . $frame['function'],
$frame['object']
- );
+ ];
$f[] = $frame['args'];
};
- return array($t, $f);
+ return [$t, $f];
}
/**
@@ -330,12 +330,12 @@ protected function extractFileLine($trace)
}
if (count($trace) == 0) {
- return array("?", "0");
+ return ["?", "0"];
}
$file = $trace[0]['file'];
$line = $trace[0]['line'];
- return array($file, $line);
+ return [$file, $line];
}
}
diff --git a/Library/Phalcon/Mailer/Message.php b/Library/Phalcon/Mailer/Message.php
index f671bbe35..c3f27ae08 100644
--- a/Library/Phalcon/Mailer/Message.php
+++ b/Library/Phalcon/Mailer/Message.php
@@ -66,7 +66,7 @@ public function __construct(Manager $manager)
* Set the from address of this message.
*
* You may pass an array of addresses if this message is from multiple people.
- * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ * Example: ['receiver@domain.org', 'other@domain.org' => 'A name']
*
* If $name is passed and the first parameter is a string, this name will be
* associated with the address.
@@ -102,7 +102,7 @@ public function getFrom()
* Set the reply-to address of this message.
*
* You may pass an array of addresses if replies will go to multiple people.
- * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ * Example: ['receiver@domain.org', 'other@domain.org' => 'A name']
*
* If $name is passed and the first parameter is a string, this name will be
* associated with the address.
@@ -138,7 +138,7 @@ public function getReplyTo()
* Set the to addresses of this message.
*
* If multiple recipients will receive the message an array should be used.
- * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ * Example: ['receiver@domain.org', 'other@domain.org' => 'A name']
*
* If $name is passed and the first parameter is a string, this name will be
* associated with the address.
@@ -174,7 +174,7 @@ public function getTo()
* Set the Cc addresses of this message.
*
* If multiple recipients will receive the message an array should be used.
- * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ * Example: ['receiver@domain.org', 'other@domain.org' => 'A name']
*
* If $name is passed and the first parameter is a string, this name will be
* associated with the address.
@@ -210,7 +210,7 @@ public function getCc()
* Set the Bcc addresses of this message.
*
* If multiple recipients will receive the message an array should be used.
- * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ * Example: ['receiver@domain.org', 'other@domain.org' => 'A name']
*
* If $name is passed and the first parameter is a string, this name will be
* associated with the address.
diff --git a/Library/Phalcon/Mvc/Model/Behavior/Blameable.php b/Library/Phalcon/Mvc/Model/Behavior/Blameable.php
index 39ce29a28..d182eb5de 100644
--- a/Library/Phalcon/Mvc/Model/Behavior/Blameable.php
+++ b/Library/Phalcon/Mvc/Model/Behavior/Blameable.php
@@ -76,7 +76,7 @@ public function auditAfterCreate(ModelInterface $model)
$audit = $this->createAudit('C', $model);
$metaData = $model->getModelsMetaData();
$fields = $metaData->getAttributes($model);
- $details = array();
+ $details = [];
foreach ($fields as $field) {
$auditDetail = new AuditDetail();
@@ -112,7 +112,7 @@ public function auditAfterUpdate(ModelInterface $model)
//Date the model had before modifications
$originalData = $model->getSnapshotData();
- $details = array();
+ $details = [];
foreach ($changedFields as $field) {
$auditDetail = new AuditDetail();
$auditDetail->field_name = $field;
diff --git a/Library/Phalcon/Mvc/Model/Behavior/NestedSet.php b/Library/Phalcon/Mvc/Model/Behavior/NestedSet.php
index 6c7d83413..a60568cbe 100644
--- a/Library/Phalcon/Mvc/Model/Behavior/NestedSet.php
+++ b/Library/Phalcon/Mvc/Model/Behavior/NestedSet.php
@@ -486,12 +486,12 @@ public function moveAsRoot()
$this->ignoreEvent = true;
foreach ($owner::find($condition) as $i) {
- $arr = array(
+ $arr = [
$this->leftAttribute => $i->{$this->leftAttribute} + $delta,
$this->rightAttribute => $i->{$this->rightAttribute} + $delta,
$this->levelAttribute => $i->{$this->levelAttribute} + $levelDelta,
$this->rootAttribute => $owner->{$this->primaryKey}
- );
+ ];
if ($i->update($arr) == false) {
$this->db->rollback();
$this->ignoreEvent = false;
@@ -722,7 +722,7 @@ private function moveNode(ModelInterface $target, $key, $levelUp)
}
foreach ($owner::find($condition) as $i) {
- if ($i->update(array($this->levelAttribute => $i->{$this->levelAttribute} + $levelDelta)) == false) {
+ if ($i->update([$this->levelAttribute => $i->{$this->levelAttribute} + $levelDelta]) == false) {
$this->db->rollback();
$this->ignoreEvent = false;
@@ -730,7 +730,7 @@ private function moveNode(ModelInterface $target, $key, $levelUp)
}
}
- foreach (array($this->leftAttribute, $this->rightAttribute) as $attribute) {
+ foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
$condition = $attribute . '>=' . $left . ' AND ' . $attribute . '<=' . $right;
if ($this->hasManyRoots) {
@@ -738,7 +738,7 @@ private function moveNode(ModelInterface $target, $key, $levelUp)
}
foreach ($owner::find($condition) as $i) {
- if ($i->update(array($attribute => $i->{$attribute} + $key - $left)) == false) {
+ if ($i->update([$attribute => $i->{$attribute} + $key - $left]) == false) {
$this->db->rollback();
$this->ignoreEvent = false;
@@ -869,7 +869,7 @@ private function addNode(ModelInterface $target, $key, $levelUp, array $attribut
private function makeRoot($attributes, $whiteList)
{
$owner = $this->getOwner();
-
+
$owner->{$this->rootAttribute} = 0;
$owner->{$this->leftAttribute} = 1;
$owner->{$this->rightAttribute} = 2;
@@ -886,7 +886,7 @@ private function makeRoot($attributes, $whiteList)
}
$pk = $owner->{$this->rootAttribute} = $owner->{$this->primaryKey};
- $owner::findFirst($pk)->update(array($this->rootAttribute => $pk));
+ $owner::findFirst($pk)->update([$this->rootAttribute => $pk]);
$this->ignoreEvent = false;
$this->db->commit();
diff --git a/Library/Phalcon/Mvc/Model/Behavior/README.md b/Library/Phalcon/Mvc/Model/Behavior/README.md
index df6baea5c..3412de6fd 100644
--- a/Library/Phalcon/Mvc/Model/Behavior/README.md
+++ b/Library/Phalcon/Mvc/Model/Behavior/README.md
@@ -37,28 +37,6 @@ There are two ways this behavior can work: one tree per table and multiple trees
The mode is selected based on the value of `hasManyRoots` option that is `false` by default meaning single tree mode.
In multiple trees mode you can set `rootAttribute` option to match existing field in the table storing the tree.
-### Example schema
-
-```sql
-CREATE TABLE `categories` (
- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
- `name` VARCHAR(128) NOT NULL,
- `description` TEXT DEFAULT NULL,
- `root` INT UNSIGNED DEFAULT NULL,
- `lft` INT UNSIGNED NOT NULL,
- `rgt` INT UNSIGNED NOT NULL,
- `level` INT UNSIGNED NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `category_coordinates` (`lft`,`rgt`,`root`),
- KEY `category_root` (`root`),
- KEY `category_lft` (`lft`),
- KEY `category_lft_root` (`lft`, `root`),
- KEY `category_rgt` (`rgt`),
- KEY `category_rgt_root` (`rgt`, `root`),
- KEY `category_level` (`level`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-```
-
### Selecting from a tree
In the following we'll use an example model Category with the following in its DB:
@@ -380,20 +358,6 @@ for ($i = $level; $i; $i--) {
}
```
-Or just:
-
-```php
-$order = 'lft'; // or 'root, lft' for multiple trees
-$categories = Categories::find(['order' => $order]);
-
-$result = [];
-foreach ($categories as $category) {
- $result[] = str_repeat(' ', ($category->level - 1) * 5) . $category->name;
-}
-
-echo print_r($result, true), PHP_EOL;
-```
-
## Blameable
```php
diff --git a/Library/Phalcon/Mvc/Model/EagerLoading/Loader.php b/Library/Phalcon/Mvc/Model/EagerLoading/Loader.php
index fa3df94ab..01dc47b51 100644
--- a/Library/Phalcon/Mvc/Model/EagerLoading/Loader.php
+++ b/Library/Phalcon/Mvc/Model/EagerLoading/Loader.php
@@ -91,7 +91,7 @@ public function __construct($from)
$this->subject = $from;
$this->subjectClassName = $className;
- $this->eagerLoads = ($from === null || empty($arguments)) ? array() : static::parseArguments($arguments);
+ $this->eagerLoads = ($from === null || empty($arguments)) ? [] : static::parseArguments($arguments);
}
/**
diff --git a/Library/Phalcon/Mvc/Model/MetaData/Base.php b/Library/Phalcon/Mvc/Model/MetaData/Base.php
index 2a70c6a3c..5b98006ce 100644
--- a/Library/Phalcon/Mvc/Model/MetaData/Base.php
+++ b/Library/Phalcon/Mvc/Model/MetaData/Base.php
@@ -36,10 +36,10 @@ abstract class Base extends MetaData implements \Phalcon\Mvc\Model\MetaDataInter
*
* @var array
*/
- protected static $defaults = array(
+ protected static $defaults = [
'lifetime' => 8600,
'prefix' => '',
- );
+ ];
/**
* Backend's options.
diff --git a/Library/Phalcon/Mvc/Model/MetaData/Memcache.php b/Library/Phalcon/Mvc/Model/MetaData/Memcache.php
deleted file mode 100644
index f748aee02..000000000
--- a/Library/Phalcon/Mvc/Model/MetaData/Memcache.php
+++ /dev/null
@@ -1,91 +0,0 @@
-
- */
-namespace Phalcon\Mvc\Model\MetaData;
-
-use Phalcon\Cache\Backend\Memcache as CacheBackend;
-use Phalcon\Cache\Frontend\Data as CacheFrontend;
-use Phalcon\Mvc\Model\Exception;
-
-/**
- * \Phalcon\Mvc\Model\MetaData\Memcache
- * Memcache adapter for \Phalcon\Mvc\Model\MetaData
- */
-class Memcache extends Base
-{
- /**
- * Default option for memcache port.
- *
- * @var array
- */
- protected static $defaultPort = 11211;
-
- /**
- * Default option for persistent session.
- *
- * @var boolean
- */
- protected static $defaultPersistent = false;
-
- /**
- * Memcache backend instance.
- *
- * @var \Phalcon\Cache\Backend\Memcache
- */
- protected $memcache = null;
-
- /**
- * {@inheritdoc}
- *
- * @param null|array $options
- * @throws \Phalcon\Mvc\Model\Exception
- */
- public function __construct($options = null)
- {
- if (!is_array($options)) {
- throw new Exception('No configuration given');
- }
-
- if (!isset($options['host'])) {
- throw new Exception('No host given in options');
- }
-
- if (!isset($options['port'])) {
- $options['port'] = self::$defaultPort;
- }
-
- if (!isset($options['persistent'])) {
- $options['persistent'] = self::$defaultPersistent;
- }
-
- parent::__construct($options);
- }
-
- /**
- * {@inheritdoc}
- * @return \Phalcon\Cache\Backend\Memcache
- */
- protected function getCacheBackend()
- {
- if (null === $this->memcache) {
- $this->memcache = new CacheBackend(
- new CacheFrontend(array('lifetime' => $this->options['lifetime'])),
- array(
- 'host' => $this->options['host'],
- 'port' => $this->options['port'],
- 'persistent' => $this->options['persistent'],
- )
- );
- }
-
- return $this->memcache;
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/MetaData/Memcached.php b/Library/Phalcon/Mvc/Model/MetaData/Memcached.php
deleted file mode 100644
index cb57d85bf..000000000
--- a/Library/Phalcon/Mvc/Model/MetaData/Memcached.php
+++ /dev/null
@@ -1,129 +0,0 @@
-
- * @author Dmitriy Zhavoronkov
- * @author Ilya Gusev
- * @license New BSD License
- * @link http://phalconphp.com/
- */
-namespace Phalcon\Mvc\Model\MetaData;
-
-use Phalcon\Cache\Backend\Libmemcached as CacheBackend;
-use Phalcon\Cache\Frontend\Data as CacheFrontend;
-use Phalcon\Mvc\Model\Exception;
-use Memcached as MemcachedGeneric;
-
-/**
- * \Phalcon\Mvc\Model\MetaData\Memcached
- * Memcached adapter for \Phalcon\Mvc\Model\MetaData
- *
- * @category Phalcon
- * @package Phalcon\Mvc\Model\MetaData
- * @author Nikita Vershinin
- * @author Dmitriy Zhavoronkov
- * @license New BSD License
- * @link http://phalconphp.com/
- */
-class Memcached extends Base
-{
- /**
- * Default option for memcached port.
- *
- * @var array
- */
- protected static $defaultPort = 11211;
-
- /**
- * Default option for weight.
- *
- * @var int
- */
- protected static $defaultWeight = 1;
-
- /**
- * Memcached backend instance.
- *
- * @var \Phalcon\Cache\Backend\Libmemcached
- */
- protected $memcached = null;
-
- /**
- * {@inheritdoc}
- *
- * @param null|array $options options array
- *
- * @throws \Phalcon\Mvc\Model\Exception
- */
- public function __construct($options = null)
- {
- if (!is_array($options)) {
- throw new Exception('No configuration given');
- }
-
- if (!isset($options['host'])) {
- throw new Exception('No host given in options');
- }
-
- if (!isset($options['port'])) {
- $options['port'] = self::$defaultPort;
- }
-
- if (!isset($options['weight'])) {
- $options['weight'] = self::$defaultWeight;
- }
-
- parent::__construct($options);
- }
-
- /**
- * {@inheritdoc}
- * @return \Phalcon\Cache\Backend\Libmemcached
- */
- protected function getCacheBackend()
- {
- if (null === $this->memcached) {
- $this->memcached = new CacheBackend(
- new CacheFrontend(array('lifetime' => $this->options['lifetime'])),
- array(
- 'servers' => array(
- array(
- 'host' => $this->options['host'],
- 'port' => $this->options['port'],
- 'weight' => $this->options['weight']
- ),
- ),
- 'client' => array(
- MemcachedGeneric::OPT_HASH => MemcachedGeneric::HASH_MD5,
- MemcachedGeneric::OPT_PREFIX_KEY => $this->options['prefix'],
- )
- )
- );
- }
-
- return $this->memcached;
- }
-
- /**
- * {@inheritdoc}
- *
- * @value string $key
- *
- * @return string
- */
- protected function prepareKey($key)
- {
- return $key;
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/MetaData/README.md b/Library/Phalcon/Mvc/Model/MetaData/README.md
index 4e7e6a03b..44a0f9fca 100644
--- a/Library/Phalcon/Mvc/Model/MetaData/README.md
+++ b/Library/Phalcon/Mvc/Model/MetaData/README.md
@@ -2,64 +2,14 @@
Usage examples of the adapters available here:
-## Memcache
-
-This adapter uses a Memcache backend to store the cached content:
-
-```php
-$di->set('modelsMetadata', function ()
-{
- return new \Phalcon\Mvc\Model\MetaData\Memcache(array(
- 'lifetime' => 8600,
- 'host' => 'localhost',
- 'port' => 11211,
- 'persistent' => false,
- ));
-});
-```
-
-## Memcached
-
-This adapter uses a Libmemcached backend to store the cached content:
-
-```php
-$di->set('modelsMetadata', function ()
-{
- return new \Phalcon\Mvc\Model\MetaData\Memcached(array(
- 'lifetime' => 8600,
- 'host' => 'localhost',
- 'port' => 11211,
- 'weight' => 1,
- 'prefix' => 'prefix.',
- ));
-});
-```
-
-## Redis
-
-This adapter uses a [Redis](http://redis.io/) backend to store the cached content and [phpredis](https://github.com/phpredis/phpredis) extension:
-
-```php
-$di->set('modelsMetadata', function ()
-{
- $redis = new Redis();
- $redis->connect('localhost', 6379);
-
- return new \Phalcon\Mvc\Model\MetaData\Redis(array(
- 'redis' => $redis,
- ));
-});
-```
-
## Wincache
This adapter uses a Wincache backend to store the cached content:
```php
-$di->set('modelsMetadata', function ()
-{
- return new \Phalcon\Mvc\Model\MetaData\Wincache(array(
+$di->set('modelsMetadata', function () {
+ return new \Phalcon\Mvc\Model\MetaData\Wincache([
'lifetime' => 8600,
- ));
+ ]);
});
-```
\ No newline at end of file
+```
diff --git a/Library/Phalcon/Mvc/Model/MetaData/Redis.php b/Library/Phalcon/Mvc/Model/MetaData/Redis.php
deleted file mode 100644
index 739cfcd2e..000000000
--- a/Library/Phalcon/Mvc/Model/MetaData/Redis.php
+++ /dev/null
@@ -1,90 +0,0 @@
-
- * @author Ilya Gusev
- */
-namespace Phalcon\Mvc\Model\MetaData;
-
-use Phalcon\Cache\Backend\Redis as CacheBackend;
-use Phalcon\Cache\Frontend\Data as CacheFrontend;
-use Phalcon\Mvc\Model\Exception;
-
-/**
- * \Phalcon\Mvc\Model\MetaData\Redis
- * Redis adapter for \Phalcon\Mvc\Model\MetaData
- */
-class Redis extends Base
-{
- /**
- * Redis backend instance.
- *
- * @var \Phalcon\Cache\Backend\Redis
- */
- protected $redis = null;
-
- /**
- * {@inheritdoc}
- *
- * @param null|array $options
- * @throws \Phalcon\Mvc\Model\Exception
- */
- public function __construct($options = null)
- {
- if (!is_array($options)) {
- throw new Exception('No configuration given');
- }
-
- if (!isset($options['redis'])) {
- throw new Exception('Parameter "redis" is required');
- }
-
- parent::__construct($options);
- }
-
- /**
- * {@inheritdoc}
- *
- * @return \Phalcon\Cache\Backend\Redis
- */
- protected function getCacheBackend()
- {
- if (null === $this->redis) {
- $this->redis = new CacheBackend(
- new CacheFrontend(array('lifetime' => $this->options['lifetime'])),
- array(
- 'redis' => $this->options['redis'],
- )
- );
- }
-
- return $this->redis;
- }
-
- /**
- * {@inheritdoc}
- * @param string $key
- * @return array
- */
- public function read($key)
- {
- return parent::read($key) ?: null;
- }
-
- /**
- * {@inheritdoc}
- *
- * @param string $key
- * @return string
- */
- protected function prepareKey($key)
- {
- return str_replace('\\', ':', parent::prepareKey($key));
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/MetaData/Wincache.php b/Library/Phalcon/Mvc/Model/MetaData/Wincache.php
index f8641aa76..b66abc798 100644
--- a/Library/Phalcon/Mvc/Model/MetaData/Wincache.php
+++ b/Library/Phalcon/Mvc/Model/MetaData/Wincache.php
@@ -37,8 +37,8 @@ protected function getCacheBackend()
{
if (null === $this->wincache) {
$this->wincache = new CacheBackend(
- new CacheFrontend(array('lifetime' => $this->options['lifetime'])),
- array()
+ new CacheFrontend(['lifetime' => $this->options['lifetime']]),
+ []
);
}
diff --git a/Library/Phalcon/Mvc/Model/Validator/Between.php b/Library/Phalcon/Mvc/Model/Validator/Between.php
deleted file mode 100644
index 2caf10dde..000000000
--- a/Library/Phalcon/Mvc/Model/Validator/Between.php
+++ /dev/null
@@ -1,84 +0,0 @@
-
- *use Phalcon\Mvc\Model\Validator\Between;
- *
- *class Sliders extends Phalcon\Mvc\Model
- *{
- *
- * public function validation()
- * {
- * $this->validate(new Between([
- * 'field' => 'position',
- * 'max' => 50,
- * 'min' => 2,
- * 'message' => 'Position is not between a valid range',
- * ]));
- *
- * if ($this->validationHasFailed() == true) {
- * return false;
- * }
- * }
- *
- *}
- *
- */
-class Between extends Validator implements ValidatorInterface
-{
- /**
- * {@inheritdoc}
- *
- * NOTE:
- * for Phalcon < 2.0.4 replace
- * \Phalcon\Mvc\EntityInterface
- * by
- * \Phalcon\Mvc\ModelInterface
- *
- * @param EntityInterface $record
- *
- * @return boolean
- * @throws Exception
- */
- public function validate(EntityInterface $record)
- {
- $field = $this->getOption('field');
-
- if (false === is_string($field)) {
- throw new Exception('Field name must be a string');
- }
-
- $value = $record->readAttribute($field);
-
- if (true === $this->isSetOption('allowEmpty') && empty($value)) {
- return true;
- }
-
- if (false === $this->isSetOption('min') || false === $this->isSetOption('max')) {
- throw new Exception('A minimum and maximum must be set');
- }
-
- $maximum = $this->getOption('max');
- $minimum = $this->getOption('min');
-
- if ($value < $minimum || $value > $maximum) {
- // Check if the developer has defined a custom message
- $message = $this->getOption('message') ?: sprintf('%s is not between a valid range', $field);
-
- $this->appendMessage($message, $field, 'Between');
- return false;
- }
-
- return true;
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/Validator/CardNumber.php b/Library/Phalcon/Mvc/Model/Validator/CardNumber.php
deleted file mode 100644
index a5639b09b..000000000
--- a/Library/Phalcon/Mvc/Model/Validator/CardNumber.php
+++ /dev/null
@@ -1,118 +0,0 @@
-
- *use Phalcon\Mvc\Model\Validator\CardNumber;
- *
- *class User extends Phalcon\Mvc\Model
- *{
- *
- * public function validation()
- * {
- * $this->validate(new CardNumber([
- * 'field' => 'cardnumber',
- * 'type' => CardNumber::VISA, // Any if not specified
- * 'message' => 'Card number must be valid'
- * ]));
- *
- * if ($this->validationHasFailed() == true) {
- * return false;
- * }
- * }
- *
- *}
- *
- */
-class CardNumber extends Validator implements ValidatorInterface
-{
- const AMERICAN_EXPRESS = 0; // 34, 37
- const MASTERCARD = 1; // 51-55
- const VISA = 2; // 4
-
- /**
- * {@inheritdoc}
- *
- * NOTE:
- * for Phalcon < 2.0.4 replace
- * \Phalcon\Mvc\EntityInterface
- * by
- * \Phalcon\Mvc\ModelInterface
- *
- * @param EntityInterface $record
- *
- * @return bool
- * @throws Exception
- */
- public function validate(EntityInterface $record)
- {
- $field = $this->getOption('field');
-
- if (false === is_string($field)) {
- throw new Exception('Field name must be a string');
- }
-
- $fieldValue = $record->readAttribute($field);
- $value = preg_replace('/[^\d]/', '', $fieldValue);
-
- if ($this->isSetOption('type')) {
- $type = $this->getOption('type');
-
- switch ($type) {
- case CardNumber::AMERICAN_EXPRESS:
- $issuer = substr($value, 0, 2);
- $result = (true === in_array($issuer, [34, 37]));
- break;
- case CardNumber::MASTERCARD:
- $issuer = substr($value, 0, 2);
- $result = (true === in_array($issuer, [51, 52, 53, 54, 55]));
- break;
- case CardNumber::VISA:
- $issuer = $value[0];
- $result = ($issuer == 4);
- break;
- default:
- throw new Exception('Incorrect type specifier');
- }
-
- if (false === $result) {
- $message = $this->getOption('message') ?: 'Credit card number is invalid';
-
- $this->appendMessage($message, $field, "CardNumber");
- return false;
- }
- }
-
- $value = strrev($value);
- $checkSum = 0;
-
- for ($i = 0; $i < strlen($value); $i++) {
- if (($i % 2) == 0) {
- $temp = $value[$i];
- } else {
- $temp = $value[$i] * 2;
- if ($temp > 9) {
- $temp -= 9;
- }
- }
- $checkSum += $temp;
- }
-
- if (($checkSum % 10) != 0) {
- $message = $this->getOption('message') ?: 'Credit card number is invalid';
-
- $this->appendMessage($message, $field, "CardNumber");
- return false;
- }
- return true;
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/Validator/ConfirmationOf.php b/Library/Phalcon/Mvc/Model/Validator/ConfirmationOf.php
deleted file mode 100644
index f39da2d68..000000000
--- a/Library/Phalcon/Mvc/Model/Validator/ConfirmationOf.php
+++ /dev/null
@@ -1,43 +0,0 @@
-getOption('field');
- $fieldConfirmation = $this->getOption('field_confirmation');
-
- $fieldValue = $record->readAttribute($field);
- $fieldConfirmationValue = $record->readAttribute($fieldConfirmation);
-
- $message = $this->getOption('message')
- ? $this->getOption('message')
- : 'Both fields should contain equal values';
-
- if ($fieldConfirmationValue) {
- if ($fieldValue !== $fieldConfirmationValue) {
- $this->appendMessage($message, $fieldConfirmation, 'ConfirmationOf');
-
- return false;
- }
- }
-
- return true;
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/Validator/Decimal.php b/Library/Phalcon/Mvc/Model/Validator/Decimal.php
deleted file mode 100644
index f6d8b5eb7..000000000
--- a/Library/Phalcon/Mvc/Model/Validator/Decimal.php
+++ /dev/null
@@ -1,104 +0,0 @@
-
- *use Phalcon\Mvc\Model\Validator\Decimal;
- *
- *class Product extends Phalcon\Mvc\Model
- *{
- *
- * public function validation()
- * {
- * $this->validate(new Decimal(array(
- * 'field' => 'price',
- * 'places' => 2,
- * 'digit' => 3, // optional
- * 'point' => ',' // optional. uses to override system decimal point
- * 'message' => 'Price must contain valid decimal value',
- * )));
- *
- * if ($this->validationHasFailed() == true) {
- * return false;
- * }
- * }
- *
- *}
- *
- */
-class Decimal extends Validator implements ValidatorInterface
-{
- /**
- * {@inheritdoc}
- *
- * @param \Phalcon\Mvc\EntityInterface $record
- * @return boolean
- * @throws \Phalcon\Mvc\Model\Exception
- */
- public function validate(EntityInterface $record)
- {
- $field = $this->getOption('field');
-
- if (false === is_string($field)) {
- throw new Exception('Field name must be a string');
- }
-
- $value = $record->readAttribute($field);
-
- if (true === $this->isSetOption('allowEmpty') && empty($value)) {
- return true;
- }
-
- if (false === $this->isSetOption('places')) {
- throw new Exception('A number of decimal places must be set');
- }
-
- if ($this->isSetOption('digits')) {
- // Specific number of digits
- $digits = '{' . ((int) $this->getOption('digits')) . '}';
- } else {
- // Any number of digits
- $digits = '+';
- }
-
- if ($this->isSetOption('point')) {
- $decimal = $this->getOption('point');
- } else {
- // Get the decimal point for the current locale
- list($decimal) = array_values(localeconv());
- }
-
- $result = (boolean) preg_match(
- sprintf(
- '#^[+-]?[0-9]%s%s[0-9]{%d}$#',
- $digits,
- preg_quote($decimal),
- $this->getOption('places')
- ),
- $value
- );
-
- if (!$result) {
- // Check if the developer has defined a custom message
- $message = $this->getOption('message') ?: sprintf('%s must contain valid decimal value', $field);
-
- $this->appendMessage($message, $field, 'Decimal');
- return false;
- }
-
- return true;
- }
-}
diff --git a/Library/Phalcon/Mvc/Model/Validator/README.md b/Library/Phalcon/Mvc/Model/Validator/README.md
deleted file mode 100644
index 77d428d4f..000000000
--- a/Library/Phalcon/Mvc/Model/Validator/README.md
+++ /dev/null
@@ -1,159 +0,0 @@
-
-Phalcon\Mvc\Model\Validator
-===========================
-
-Validators for Phalcon\Mvc\Model
-
-ConfirmationOf
---------------
-Allows to validate if a field has a confirmation field with the same value
-
-```php
-
-use Phalcon\Mvc\Model\Validator\ConfirmationOf;
-
-use Phalcon\Mvc\Model;
-
-class User extends Model
-{
-
- public function initialize()
- {
- $this->skipAttributesOnCreate(array('password_confirmation'));
- $this->skipAttributesOnUpdate(array('password_confirmation'));
- }
-
- public function validation()
- {
- $this->validate(new ConfirmationOf(array(
- 'field' => 'password',
- 'field_confirmation' => 'password_confirmation',
- 'message' => 'Both fields should contain equal values'
- )));
-
- if ($this->validationHasFailed() == true) {
- return false;
- }
- }
-
-}
-
-```
-
-Decimal
--------
-Allows to validate if a field has a valid number in proper decimal format (negative and decimal numbers allowed).
-Optionally, a specific number of digits can be checked too. Uses [locale conversion](http://www.php.net/manual/en/function.localeconv.php) to allow decimal point to be locale specific.
-
-```php
-
-use Phalcon\Mvc\Model\Validator\Decimal;
-
-use Phalcon\Mvc\Model;
-
-class Product extends Model
-{
-
- public function validation()
- {
- $this->validate(new Decimal(array(
- 'field' => 'price',
- 'places' => 2,
- 'digit' => 3, // optional
- 'point' => ',' // optional. uses to override system decimal point
- 'message' => 'Price must contain valid decimal value',
- )));
-
- if ($this->validationHasFailed() == true) {
- return false;
- }
- }
-
-}
-
-```
-
-Between
--------
-Validates that a value is between a range of two values
-
-```php
-
-use Phalcon\Mvc\Model\Validator\Between;
-
-use Phalcon\Mvc\Model;
-
-class Slider extends Model
-{
- public function validation()
- {
- $this->validate(new Between(array(
- 'field' => 'position',
- 'max' => 50,
- 'min' => 2,
- 'message' => 'Position is not between a valid range'
- )));
-
- if ($this->validationHasFailed() == true) {
- return false;
- }
- }
-}
-
-```
-
-CardNumber
--------
-Validates credit card number using Luhn algorithm.
-
-```php
-
-use Phalcon\Mvc\Model;
-use Phalcon\Mvc\Model\Validator\CardNumber;
-
-class User extends Phalcon\Mvc\Model
-{
- public function validation()
- {
- $this->validate(new CardNumber(array(
- 'field' => 'cardnumber',
- 'type' => CardNumber::VISA, // Only one type. Any if not specified
- 'message' => 'Card number must be valid',
- )));
-
- if ($this->validationHasFailed() == true) {
- return false;
- }
- }
-}
-
-```
-
-IPv4
--------
-Validates that a value is ipv4 address in valid range
-
-```php
-
-use Phalcon\Mvc\Model;
-use Phalcon\Mvc\Model\Validator\IPv4;
-
-class Server extends Phalcon\Mvc\Model
-{
- public function validation()
- {
- $this->validate(new IPv4(array(
- 'field' => 'server_ip',
- 'version' => IP::VERSION_4 | IP::VERSION_6, // v6 and v4. The same if not specified
- 'allowReserved' => false, // False if not specified. Ignored for v6
- 'allowPrivate' => false, // False if not specified
- 'message' => 'IP address has to be correct'
- )));
-
- if ($this->validationHasFailed() == true) {
- return false;
- }
- }
-}
-
-```
diff --git a/Library/Phalcon/Mvc/MongoCollection.php b/Library/Phalcon/Mvc/MongoCollection.php
new file mode 100644
index 000000000..9cb571759
--- /dev/null
+++ b/Library/Phalcon/Mvc/MongoCollection.php
@@ -0,0 +1,461 @@
+ |
+ +------------------------------------------------------------------------+
+ */
+
+namespace Phalcon\Mvc;
+
+use Phalcon\Di;
+use MongoDB\BSON\ObjectID;
+use MongoDB\BSON\Unserializable;
+use Phalcon\Mvc\Collection\Document;
+use Phalcon\Mvc\Collection\Exception;
+use Phalcon\Mvc\Collection as PhalconCollection;
+use Phalcon\Db\Adapter\MongoDB\Collection as AdapterCollection;
+
+/**
+ * Class MongoCollection
+ *
+ * @property \Phalcon\Mvc\Collection\ManagerInterface _modelsManager
+ * @package Phalcon\Mvc
+ */
+abstract class MongoCollection extends PhalconCollection implements Unserializable
+{
+ // @codingStandardsIgnoreStart
+ static protected $_disableEvents;
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Sets a value for the _id property, creates a MongoId object if needed
+ *
+ * @param mixed $id
+ */
+ public function setId($id)
+ {
+ $mongoId = null;
+
+ if (is_object($id)) {
+ $mongoId = $id;
+ } else {
+ if ($this->_modelsManager->isUsingImplicitObjectIds($this)) {
+ $mongoId = new ObjectID($id);
+ } else {
+ $mongoId = $id;
+ }
+ }
+
+ $this->_id = $mongoId;
+ }
+
+ /**
+ * Creates/Updates a collection based on the values in the attributes
+ */
+ public function save()
+ {
+ $dependencyInjector = $this->_dependencyInjector;
+
+ if (!is_object($dependencyInjector)) {
+ throw new Exception(
+ "A dependency injector container is required to obtain the services related to the ORM"
+ );
+ }
+
+ $source = $this->getSource();
+
+ if (empty($source)) {
+ throw new Exception("Method getSource() returns empty string");
+ }
+
+ $connection = $this->getConnection();
+
+ $collection = $connection->selectCollection($source);
+
+ $exists = $this->_exists($collection);
+
+ if (false === $exists) {
+ $this->_operationMade = self::OP_CREATE;
+ } else {
+ $this->_operationMade = self::OP_UPDATE;
+ }
+
+ /**
+ * The messages added to the validator are reset here
+ */
+ $this->_errorMessages = [];
+
+ $disableEvents = self::$_disableEvents;
+
+ /**
+ * Execute the preSave hook
+ */
+ if (false === $this->_preSave($dependencyInjector, $disableEvents, $exists)) {
+ return false;
+ }
+
+ $data = $this->toArray();
+
+ /**
+ * We always use safe stores to get the success state
+ * Save the document
+ */
+ switch ($this->_operationMade) {
+ case self::OP_CREATE:
+ $status = $collection->insertOne($data);
+ break;
+
+ case self::OP_UPDATE:
+ $status = $collection->updateOne(['_id' => $this->_id], ['$set' => $this->toArray()]);
+ break;
+
+ default:
+ throw new Exception('Invalid operation requested for MongoCollection->save()');
+ }
+
+ $success = false;
+
+ if ($status->isAcknowledged()) {
+ $success = true;
+
+ if (false === $exists) {
+ $this->_id = $status->getInsertedId();
+ }
+ }
+
+ /**
+ * Call the postSave hooks
+ */
+ return $this->_postSave($disableEvents, $success, $exists);
+ }
+
+ public static function findById($id)
+ {
+ if (!is_object($id)) {
+ $classname = get_called_class();
+ $collection = new $classname();
+
+ /** @var MongoCollection $collection */
+ if ($collection->getCollectionManager()->isUsingImplicitObjectIds($collection)) {
+ $mongoId = new ObjectID($id);
+ } else {
+ $mongoId = $id;
+ }
+ } else {
+ $mongoId = $id;
+ }
+
+ return static::findFirst([["_id" => $mongoId]]);
+ }
+
+ public static function findFirst(array $parameters = null)
+ {
+ $className = get_called_class();
+
+ /** @var MongoCollection $collection */
+ $collection = new $className();
+
+ $connection = $collection->getConnection();
+
+ return static::_getResultset($parameters, $collection, $connection, true);
+ }
+
+ /**
+ * @param array $params
+ * @param CollectionInterface $collection
+ * @param \MongoDb $connection
+ * @param bool $unique
+ *
+ * @return array
+ * @throws Exception
+ */
+ // @codingStandardsIgnoreStart
+ protected static function _getResultset($params, CollectionInterface $collection, $connection, $unique)
+ {
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Check if "class" clause was defined
+ */
+ if (isset($params['class'])) {
+ $classname = $params['class'];
+
+ $base = new $classname();
+
+ if (!$base instanceof CollectionInterface || $base instanceof Document) {
+ throw new Exception(
+ "Object of class '".$classname."' must be an implementation of
+ Phalcon\\Mvc\\CollectionInterface or an instance of Phalcon\\Mvc\\Collection\\Document"
+ );
+ }
+ } else {
+ $base = $collection;
+ }
+
+ $source = $collection->getSource();
+
+ if (empty($source)) {
+ throw new Exception("Method getSource() returns empty string");
+ }
+
+ /**
+ * @var \Phalcon\Db\Adapter\MongoDB\Collection $mongoCollection
+ */
+ $mongoCollection = $connection->selectCollection($source);
+
+ if (!is_object($mongoCollection)) {
+ throw new Exception("Couldn't select mongo collection");
+ }
+
+ $conditions = [];
+
+ if (isset($params[0])||isset($params['conditions'])) {
+ $conditions = (isset($params[0]))?$params[0]:$params['conditions'];
+ }
+
+ /**
+ * Convert the string to an array
+ */
+ if (!is_array($conditions)) {
+ throw new Exception("Find parameters must be an array");
+ }
+
+ $options = [];
+
+ /**
+ * Check if a "limit" clause was defined
+ */
+ if (isset($params['limit'])) {
+ $limit = $params['limit'];
+
+ $options['limit'] = (int)$limit;
+
+ if ($unique) {
+ $options['limit'] = 1;
+ }
+ }
+
+ /**
+ * Check if a "sort" clause was defined
+ */
+ if (isset($params['sort'])) {
+ $sort = $params["sort"];
+
+ $options['sort'] = $sort;
+ }
+
+ /**
+ * Check if a "skip" clause was defined
+ */
+ if (isset($params['skip'])) {
+ $skip = $params["skip"];
+
+ $options['skip'] = (int)$skip;
+ }
+
+ if (isset($params['fields']) && is_array($params['fields']) && !empty($params['fields'])) {
+ $options['projection'] = [];
+
+ foreach ($params['fields'] as $key => $show) {
+ $options['projection'][$key] = $show;
+ }
+ }
+
+ /**
+ * Perform the find
+ */
+ $cursor = $mongoCollection->find($conditions, $options);
+
+ $cursor->setTypeMap(['root'=>get_called_class(),'document'=>'object']);
+
+ if (true === $unique) {
+ /**
+ * Looking for only the first result.
+ */
+ return current($cursor->toArray());
+ }
+
+ /**
+ * Requesting a complete resultset
+ */
+ $collections = [];
+
+
+ foreach ($cursor as $document) {
+ /**
+ * Assign the values to the base object
+ */
+ $collections[] = $document;
+ }
+
+ return $collections;
+ }
+
+ /**
+ * Deletes a model instance. Returning true on success or false otherwise.
+ *
+ *
+ * $robot = Robots::findFirst();
+ * $robot->delete();
+ *
+ * foreach (Robots::find() as $robot) {
+ * $robot->delete();
+ * }
+ *
+ */
+ public function delete()
+ {
+ if (!$id = $this->_id) {
+ throw new Exception("The document cannot be deleted because it doesn't exist");
+ }
+
+ $disableEvents = self::$_disableEvents;
+
+ if (!$disableEvents) {
+ if (false === $this->fireEventCancel("beforeDelete")) {
+ return false;
+ }
+ }
+
+ if (true === $this->_skipped) {
+ return true;
+ }
+
+ $connection = $this->getConnection();
+
+ $source = $this->getSource();
+ if (empty($source)) {
+ throw new Exception("Method getSource() returns empty string");
+ }
+
+ /**
+ * Get the Collection
+ *
+ * @var AdapterCollection $collection
+ */
+ $collection = $connection->selectCollection($source);
+
+ if (is_object($id)) {
+ $mongoId = $id;
+ } else {
+ if ($this->_modelsManager->isUsingImplicitObjectIds($this)) {
+ $mongoId = new ObjectID($id);
+ } else {
+ $mongoId = $id;
+ }
+ }
+
+ $success = false;
+
+ /**
+ * Remove the instance
+ */
+ $status = $collection->deleteOne(['_id' => $mongoId], ['w' => true]);
+
+ if ($status->isAcknowledged()) {
+ $success = true;
+
+ $this->fireEvent("afterDelete");
+ }
+
+ return $success;
+ }
+
+ /**
+ * Checks if the document exists in the collection
+ *
+ * @param \MongoCollection collection
+ *
+ * @return boolean
+ */
+ // @codingStandardsIgnoreStart
+ protected function _exists($collection)
+ {
+ // @codingStandardsIgnoreStart
+
+ if (!$id = $this->_id) {
+ return false;
+ }
+
+ if (is_object($id)) {
+ $mongoId = $id;
+ } else {
+ /**
+ * Check if the model use implicit ids
+ */
+ if ($this->_modelsManager->isUsingImplicitObjectIds($this)) {
+ $mongoId = new ObjectID($id);
+ } else {
+ $mongoId = $id;
+ }
+ }
+
+ /**
+ * Perform the count using the function provided by the driver
+ */
+ return $collection->count(["_id"=>$mongoId])>0;
+ }
+
+ /**
+ * Fires an internal event that cancels the operation
+ *
+ * @param string $eventName
+ * @return bool
+ */
+ public function fireEventCancel($eventName)
+ {
+ /**
+ * Check if there is a method with the same name of the event
+ */
+ if (method_exists($this, $eventName)) {
+ if (false === $this->{$eventName}()) {
+ return false;
+ }
+ }
+
+ /**
+ * Send a notification to the events manager
+ */
+ if (false === $this->_modelsManager->notifyEvent($eventName, $this)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static function summatory($field, $conditions = null, $finalize = null)
+ {
+ throw new Exception('The summatory() method is not implemented in the new Mvc MongoCollection');
+ }
+
+ /**
+ * Pass the values from the BSON document back to the object.
+ *
+ * @param array $data
+ */
+ public function bsonUnserialize(array $data)
+ {
+ $this->setDI(Di::getDefault());
+ $this->_modelsManager = Di::getDefault()->getShared('collectionManager');
+
+ foreach ($data as $key => $val) {
+ $this->{$key} = $val;
+ }
+
+ if (method_exists($this, "afterFetch")) {
+ $this->afterFetch();
+ }
+ }
+}
diff --git a/Library/Phalcon/Mvc/View/Engine/README.md b/Library/Phalcon/Mvc/View/Engine/README.md
index e064d188b..e30dde550 100644
--- a/Library/Phalcon/Mvc/View/Engine/README.md
+++ b/Library/Phalcon/Mvc/View/Engine/README.md
@@ -28,7 +28,7 @@ $di->set('view', function() {
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array(".mhtml" => 'Phalcon\Mvc\View\Engine\Mustache')
+ ['.mhtml' => 'Phalcon\Mvc\View\Engine\Mustache']
);
return $view;
@@ -48,7 +48,7 @@ Twig_Autoloader::register();
Register the adapter in the view component:
```php
-//Setting up the view component
+// Setting up the view component
$di->set('view', function() {
$view = new \Phalcon\Mvc\View();
@@ -56,7 +56,7 @@ $di->set('view', function() {
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array(".twig" => 'Phalcon\Mvc\View\Engine\Twig')
+ ['.twig' => 'Phalcon\Mvc\View\Engine\Twig']
);
return $view;
@@ -66,21 +66,27 @@ $di->set('view', function() {
or
```php
-//Setting up the view component
+use Phalcon\Mvc\View;
+use Phalcon\Mvc\View\Engine\Twig;
+
+// Setting up the view component
$di->set('view', function() {
- $view = new \Phalcon\Mvc\View();
+ $view = new View();
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array(
+ [
'.twig' => function($view, $di) {
- //Setting up Twig Environment Options
- $options = array('cache' => '../cache/');
- $twig = new \Phalcon\Mvc\View\Engine\Twig($view, $di, $options);
+ // Setting up Twig Environment Options
+ $options = ['cache' => '../cache/'];
+ $twig = new Twig($view, $di, $options);
+
return $twig;
- }));
+ }
+ ]
+ );
return $view;
});
@@ -89,27 +95,40 @@ $di->set('view', function() {
You can also create your own defined functions to extend Twig parsing capabilities by passing a forth parameter to the Twig constructor that consists of an Array of Twig_SimpleFunction elements. This will allow you to extend Twig, or even override default functions, with your own.
```php
-//Setting up the view component
+use Twig_SimpleFunction;
+use Phalcon\Mvc\View;
+use Phalcon\Mvc\View\Engine\Twig;
+
+// Setting up the view component
$di->set('view', function() {
- $view = new \Phalcon\Mvc\View();
+ $view = new View();
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array(
+ [
'.twig' => function($view, $di) {
- //Setting up Twig Environment Options
- $options = array('cache' => '../cache/');
+ // Setting up Twig Environment Options
+ $options = ['cache' => '../cache/'];
+
// Adding support for the native PHP chunk_split function
- $user_functions = array(
- new \Twig_SimpleFunction('chunk_split', function ($string, $len = 76, $end = "\r\n") use ($di) {
- return chunk_split($string, $len, $end);
- }, $options)
- );
- $twig = new \Phalcon\Mvc\View\Engine\Twig($view, $di, $options, $user_functions);
+ $user_functions = [
+ new Twig_SimpleFunction(
+ 'chunk_split',
+ function ($string, $len = 76, $end = "\r\n") use ($di) {
+ return chunk_split($string, $len, $end);
+ },
+ $options
+ ),
+ ];
+
+ $twig = new Twig($view, $di, $options, $user_functions);
+
return $twig;
- }));
+ }
+ ]
+ );
return $view;
});
@@ -155,15 +174,17 @@ require_once 'Smarty3/Smarty.class.php';
Register the adapter in the view component:
```php
-//Setting up the view component
+use Phalcon\Mvc\View;
+
+// Setting up the view component
$di->set('view', function() {
- $view = new \Phalcon\Mvc\View();
+ $view = new View();
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array(".tpl" => 'Phalcon\Mvc\View\Engine\Smarty')
+ ['.tpl' => 'Phalcon\Mvc\View\Engine\Smarty']
);
return $view;
@@ -173,8 +194,9 @@ $di->set('view', function() {
Smarty's equivalent to Phalcon's "setVar($key, $value)" function is "assign($key, $value, $nocache = false)" which has a third optional argument. This third argument, when set to true, marks the variable as exempt from caching. This is an essential Smarty feature that other template engines lack, being useful for pages that have portions that are often changing such as the current user who is logged in. If you want to utilize this additional argument, use the incubator SmartyView instead of View which extends View to include this functionality.
```php
-//Setting up the view component
use Phalcon\Mvc\View\SmartyView;
+
+// Setting up the view component
$di->set('view', function() {
$view = new SmartyView();
@@ -182,7 +204,7 @@ $di->set('view', function() {
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array(".tpl" => 'Phalcon\Mvc\View\Engine\Smarty')
+ ['.tpl' => 'Phalcon\Mvc\View\Engine\Smarty']
);
return $view;
@@ -202,17 +224,21 @@ $this->view->setVar($key, $value);
Smarty can be configured to alter its default behavior, the following example explain how to do that:
```php
+use Phalcon\Mvc\View;
+use Phalcon\Mvc\View\Engine\Smarty;
+
$di->set('view', function() use ($config) {
- $view = new \Phalcon\Mvc\View();
+ $view = new View();
$view->setViewsDir('../app/views/');
$view->registerEngines(
- array('.html' => function($view, $di) {
+ [
+ '.html' => function($view, $di) {
- $smarty = new \Phalcon\Mvc\View\Engine\Smarty($view, $di);
+ $smarty = new Smarty($view, $di);
- $smarty->setOptions(array(
+ $smarty->setOptions([
'template_dir' => $view->getViewsDir(),
'compile_dir' => '../app/viewscompiled',
'error_reporting' => error_reporting() ^ E_NOTICE,
@@ -223,11 +249,11 @@ $di->set('view', function() use ($config) {
'compile_check' => true,
'caching' => false,
'debugging' => true,
- ));
+ ]);
return $smarty;
}
- )
+ ]
);
return $view;
diff --git a/Library/Phalcon/Mvc/View/Engine/Twig.php b/Library/Phalcon/Mvc/View/Engine/Twig.php
index 7c85ceac1..2ed4f07d8 100644
--- a/Library/Phalcon/Mvc/View/Engine/Twig.php
+++ b/Library/Phalcon/Mvc/View/Engine/Twig.php
@@ -28,8 +28,8 @@ class Twig extends Engine implements EngineInterface
public function __construct(
ViewBaseInterface $view,
DiInterface $di = null,
- $options = array(),
- $userFunctions = array()
+ $options = [],
+ $userFunctions = []
) {
$loader = new \Twig_Loader_Filesystem($view->getViewsDir());
$this->twig = new Twig\Environment($di, $loader, $options);
@@ -47,13 +47,11 @@ public function __construct(
* @param \Phalcon\DiInterface $di
* @param array $userFunctions
*/
- protected function registryFunctions($view, DiInterface $di, $userFunctions = array())
+ protected function registryFunctions($view, DiInterface $di, $userFunctions = [])
{
- $options = array(
- 'is_safe' => array('html')
- );
+ $options = ['is_safe' => ['html']];
- $functions = array(
+ $functions = [
new \Twig_SimpleFunction('content', function () use ($view) {
return $view->getContent();
}, $options),
@@ -84,16 +82,16 @@ protected function registryFunctions($view, DiInterface $di, $userFunctions = ar
new \Twig_SimpleFunction('submitButton', function ($parameters) {
return \Phalcon\Tag::submitButton($parameters);
}, $options),
- new \Twig_SimpleFunction('selectStatic', function ($parameters, $data = array()) {
+ new \Twig_SimpleFunction('selectStatic', function ($parameters, $data = []) {
return \Phalcon\Tag::selectStatic($parameters, $data);
}, $options),
- new \Twig_SimpleFunction('select', function ($parameters, $data = array()) {
+ new \Twig_SimpleFunction('select', function ($parameters, $data = []) {
return \Phalcon\Tag::select($parameters, $data);
}, $options),
new \Twig_SimpleFunction('textArea', function ($parameters) {
return \Phalcon\Tag::textArea($parameters);
}, $options),
- new \Twig_SimpleFunction('form', function ($parameters = array()) {
+ new \Twig_SimpleFunction('form', function ($parameters = []) {
return \Phalcon\Tag::form($parameters);
}, $options),
new \Twig_SimpleFunction('endForm', function () {
@@ -126,7 +124,7 @@ protected function registryFunctions($view, DiInterface $di, $userFunctions = ar
new \Twig_SimpleFunction('url', function ($route) use ($di) {
return $di->get("url")->get($route);
}, $options)
- );
+ ];
if (!empty($userFunctions)) {
$functions = array_merge($functions, $userFunctions);
diff --git a/Library/Phalcon/Mvc/View/Engine/Twig/CoreExtension.php b/Library/Phalcon/Mvc/View/Engine/Twig/CoreExtension.php
index d190ce5f0..5463d03fc 100644
--- a/Library/Phalcon/Mvc/View/Engine/Twig/CoreExtension.php
+++ b/Library/Phalcon/Mvc/View/Engine/Twig/CoreExtension.php
@@ -35,16 +35,16 @@ public function getName()
*/
public function getFunctions()
{
- $options = array(
+ $options = [
'needs_environment' => true,
'pre_escape' => 'html',
- 'is_safe' => array('html'),
- );
+ 'is_safe' => ['html'],
+ ];
- return array(
+ return [
'assetsOutputCss' => new \Twig_Function_Method($this, 'functionAssetsOutputCss', $options),
'assetsOutputJs' => new \Twig_Function_Method($this, 'functionAssetsOutputJs', $options),
- );
+ ];
}
/**
@@ -78,9 +78,7 @@ public function functionAssetsOutputJs(Environment $env, $options = null)
*/
public function getTokenParsers()
{
- return array(
- new TokenParsers\Assets(),
- );
+ return [new TokenParsers\Assets()];
}
/**
diff --git a/Library/Phalcon/Mvc/View/Engine/Twig/Environment.php b/Library/Phalcon/Mvc/View/Engine/Twig/Environment.php
index 0fd86a0dd..0f60828c3 100644
--- a/Library/Phalcon/Mvc/View/Engine/Twig/Environment.php
+++ b/Library/Phalcon/Mvc/View/Engine/Twig/Environment.php
@@ -33,7 +33,7 @@ class Environment extends \Twig_Environment
* @param \Twig_LoaderInterface $loader
* @param array $options
*/
- public function __construct(DiInterface $di, \Twig_LoaderInterface $loader = null, $options = array())
+ public function __construct(DiInterface $di, \Twig_LoaderInterface $loader = null, $options = [])
{
$this->di = $di;
diff --git a/Library/Phalcon/Mvc/View/Engine/Twig/TokenParsers/Assets.php b/Library/Phalcon/Mvc/View/Engine/Twig/TokenParsers/Assets.php
index 7f1cf6d82..55cd19c4a 100644
--- a/Library/Phalcon/Mvc/View/Engine/Twig/TokenParsers/Assets.php
+++ b/Library/Phalcon/Mvc/View/Engine/Twig/TokenParsers/Assets.php
@@ -38,8 +38,8 @@ public function parse(\Twig_Token $token)
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new Node(
- array('arguments' => $arguments),
- array('methodName' => $methodName),
+ ['arguments' => $arguments],
+ ['methodName' => $methodName],
$token->getLine(),
$this->getTag()
);
diff --git a/Library/Phalcon/Paginator/Pager.php b/Library/Phalcon/Paginator/Pager.php
index 6be4ff8f5..6e10e84a5 100644
--- a/Library/Phalcon/Paginator/Pager.php
+++ b/Library/Phalcon/Paginator/Pager.php
@@ -31,7 +31,7 @@ class Pager implements \IteratorAggregate, \Countable
*
* @var array
*/
- protected $options = array();
+ protected $options = [];
/**
* Current rows limit (if provided)
@@ -55,7 +55,7 @@ class Pager implements \IteratorAggregate, \Countable
* @param array $options options array
*
*/
- public function __construct(AdapterInterface $adapter, array $options = array())
+ public function __construct(AdapterInterface $adapter, array $options = [])
{
$this->paginateResult = $adapter->getPaginate();
diff --git a/Library/Phalcon/Paginator/Pager/Layout.php b/Library/Phalcon/Paginator/Pager/Layout.php
index 3a881bd91..46a841856 100644
--- a/Library/Phalcon/Paginator/Pager/Layout.php
+++ b/Library/Phalcon/Paginator/Pager/Layout.php
@@ -61,7 +61,7 @@ class Layout
* @link https://github.com/doctrine/doctrine1/blob/master/lib/Doctrine/Pager/Layout.php#L67
* @var array
*/
- protected $maskReplacements = array();
+ protected $maskReplacements = [];
/**
* Class constructor.
@@ -117,10 +117,10 @@ public function setSeparatorTemplate($separatorTemplate)
public function addMaskReplacement($oldMask, $newMask, $asValue = false)
{
if (($oldMask = trim($oldMask)) != 'page_number') {
- $this->maskReplacements[$oldMask] = array(
+ $this->maskReplacements[$oldMask] = [
'newMask' => $newMask,
'asValue' => ($asValue === false) ? false : true
- );
+ ];
}
}
@@ -144,7 +144,7 @@ public function removeMaskReplacement($oldMask)
* @param array $options
* @return string
*/
- public function getRendered(array $options = array())
+ public function getRendered(array $options = [])
{
$range = $this->range->getRange();
$result = '';
@@ -175,7 +175,7 @@ public function __toString()
* @param array $options
* @return string
*/
- protected function processPage(array $options = array())
+ protected function processPage(array $options = [])
{
if (!isset($this->maskReplacements['page']) && !isset($options['page'])) {
$options['page'] = $options['page_number'];
@@ -189,7 +189,7 @@ protected function processPage(array $options = array())
* @param array $options
* @return string
*/
- protected function parseTemplate(array $options = array())
+ protected function parseTemplate(array $options = [])
{
$str = $this->parseUrlTemplate($options);
$replacements = $this->parseReplacementsTemplate($options);
@@ -202,7 +202,7 @@ protected function parseTemplate(array $options = array())
* @param array $options
* @return string
*/
- protected function parseUrlTemplate(array $options = array())
+ protected function parseUrlTemplate(array $options = [])
{
$str = '';
@@ -224,10 +224,10 @@ protected function parseUrlTemplate(array $options = array())
* @param array $options
* @return string
*/
- protected function parseReplacementsTemplate(array $options = array())
+ protected function parseReplacementsTemplate(array $options = [])
{
$options['url'] = $this->parseUrl($options);
- $replacements = array();
+ $replacements = [];
foreach ($options as $k => $v) {
$replacements['{%' . $k . '}'] = $v;
@@ -241,11 +241,11 @@ protected function parseReplacementsTemplate(array $options = array())
* @param array $options
* @return string
*/
- protected function parseUrl(array $options = array())
+ protected function parseUrl(array $options = [])
{
$str = $this->parseMaskReplacements($this->urlMask);
- $replacements = array();
+ $replacements = [];
foreach ($options as $k => $v) {
$replacements['{%' . $k . '}'] = $v;
@@ -261,7 +261,7 @@ protected function parseUrl(array $options = array())
*/
protected function parseMaskReplacements($str)
{
- $replacements = array();
+ $replacements = [];
foreach ($this->maskReplacements as $k => $v) {
$replacements['{%' . $k . '}'] = ($v['asValue'] === true) ? $v['newMask'] : '{%' . $v['newMask'] . '}';
diff --git a/Library/Phalcon/Paginator/Pager/Layout/Bootstrap.php b/Library/Phalcon/Paginator/Pager/Layout/Bootstrap.php
index 99f49f5a0..418e20567 100644
--- a/Library/Phalcon/Paginator/Pager/Layout/Bootstrap.php
+++ b/Library/Phalcon/Paginator/Pager/Layout/Bootstrap.php
@@ -39,7 +39,7 @@ class Bootstrap extends Layout
* @param array $options
* @return string
*/
- public function getRendered(array $options = array())
+ public function getRendered(array $options = [])
{
$result = 'Sorry nothing found
{% else %} diff --git a/Library/Phalcon/Queue/Beanstalk/Extended.php b/Library/Phalcon/Queue/Beanstalk/Extended.php index 21117cef1..9d8637276 100644 --- a/Library/Phalcon/Queue/Beanstalk/Extended.php +++ b/Library/Phalcon/Queue/Beanstalk/Extended.php @@ -241,7 +241,7 @@ public function reserveFromTube($tube, $timeout = null) */ public function getTubes() { - $result = array(); + $result = []; $lines = $this->getResponseLines('list-tubes'); if (null !== $lines) { diff --git a/Library/Phalcon/Session/Adapter/Aerospike.php b/Library/Phalcon/Session/Adapter/Aerospike.php index acb4c56f9..90aea41d5 100644 --- a/Library/Phalcon/Session/Adapter/Aerospike.php +++ b/Library/Phalcon/Session/Adapter/Aerospike.php @@ -19,10 +19,11 @@ namespace Phalcon\Session\Adapter; -use Aerospike as AerospikeDb; use Phalcon\Session\Adapter; -use Phalcon\Session\AdapterInterface; use Phalcon\Session\Exception; +use Phalcon\Session\AdapterInterface; +use Phalcon\Cache\Frontend\Data as FrontendData; +use Phalcon\Cache\Backend\Aerospike as AerospikeDb; /** * Phalcon\Session\Adapter\Aerospike @@ -90,7 +91,9 @@ class Aerospike extends Adapter implements AdapterInterface * Phalcon\Session\Adapter\Aerospike constructor * * @param array $options Constructor options - * @throws Exception + * + * @throws \Phalcon\Session\Exception + * @throws \Phalcon\Cache\Exception */ public function __construct(array $options) { @@ -100,12 +103,18 @@ public function __construct(array $options) if (isset($options['namespace'])) { $this->namespace = $options['namespace']; + unset($options['namespace']); } if (isset($options['prefix'])) { $this->prefix = $options['prefix']; } + if (isset($options['set']) && !empty($options['set'])) { + $this->set = $options['set']; + unset($options['set']); + } + if (isset($options['lifetime'])) { $this->lifetime = $options['lifetime']; } @@ -120,13 +129,18 @@ public function __construct(array $options) $opts = $options['options']; } - $this->db = new AerospikeDb(['hosts' => $options['hosts']], $persistent, $opts); - - if (!$this->db->isConnected()) { - throw new Exception( - sprintf("Aerospike failed to connect [%s]: %s", $this->db->errorno(), $this->db->error()) - ); - } + $this->db = new AerospikeDb( + new FrontendData(['lifetime' => $this->lifetime]), + [ + 'hosts' => $options['hosts'], + 'namespace' => $this->namespace, + 'set' => $this->set, + 'prefix' => $this->prefix, + 'persistent' => $persistent, + 'options' => $opts, + + ] + ); parent::__construct($options); @@ -143,11 +157,11 @@ public function __construct(array $options) /** * Gets the Aerospike instance. * - * @return AerospikeDb + * @return \Aerospike */ public function getDb() { - return $this->db; + return $this->db->getDb(); } /** @@ -178,14 +192,7 @@ public function close() */ public function read($sessionId) { - $key = $this->buildKey($sessionId); - $status = $this->db->get($key, $record); - - if ($status != AerospikeDb::OK) { - return false; - } - - return base64_decode($record['bins']['value']); + return $this->db->get($sessionId, $this->lifetime); } /** @@ -196,10 +203,7 @@ public function read($sessionId) */ public function write($sessionId, $data) { - $key = $this->buildKey($sessionId); - $bins = ['value' => base64_encode($data)]; - - $this->db->put($key, $bins, $this->lifetime); + return $this->db->save($sessionId, $data, $this->lifetime); } /** @@ -210,12 +214,19 @@ public function write($sessionId, $data) */ public function destroy($sessionId = null) { - $sessionId = $sessionId ?: $this->getId(); - $key = $this->buildKey($sessionId); + if (null === $sessionId) { + $sessionId = $this->getId(); + } + + if (!isset($_SESSION) || !is_array($_SESSION)) { + $_SESSION = []; + } - $status = $this->db->remove($key); + foreach ($_SESSION as $id => $key) { + unset($_SESSION[$id]); + } - return $status == AerospikeDb::OK; + return $this->db->delete($sessionId); } /** @@ -227,19 +238,4 @@ public function gc() { return true; } - - /** - * Generates a unique key used for storing session data in Aerospike DB. - * - * @param string $sessionId Session variable name - * @return array - */ - protected function buildKey($sessionId) - { - return $this->db->initKey( - $this->namespace, - $this->set, - $this->prefix . $sessionId - ); - } } diff --git a/Library/Phalcon/Session/Adapter/Database.php b/Library/Phalcon/Session/Adapter/Database.php index 7ccf9350e..ee771432b 100644 --- a/Library/Phalcon/Session/Adapter/Database.php +++ b/Library/Phalcon/Session/Adapter/Database.php @@ -46,7 +46,7 @@ public function __construct($options = null) { if (!isset($options['db']) || !$options['db'] instanceof DbAdapter) { throw new Exception( - 'Parameter "db" is required and it must be an instance of Phalcon\Acl\AdapterInterface' + 'Parameter "db" is required and it must be an instance of Phalcon\Db\AdapterInterface' ); } diff --git a/Library/Phalcon/Session/Adapter/HandlerSocket.php b/Library/Phalcon/Session/Adapter/HandlerSocket.php index d07f5e963..c98b375ba 100644 --- a/Library/Phalcon/Session/Adapter/HandlerSocket.php +++ b/Library/Phalcon/Session/Adapter/HandlerSocket.php @@ -47,17 +47,17 @@ class HandlerSocket extends Adapter implements AdapterInterface * 'dbname' => (string) : the database name of the mysql handlersocket server * 'dbtable' => (string) : the table name of the mysql handlersocket server */ - protected $options = array( + protected $options = [ 'cookie_path' => '/', 'cookie_domain' => '', 'lifetime' => 3600, - 'server' => array( + 'server' => [ 'host' => self::DEFAULT_HOST, 'port' => self::DEFAULT_PORT, 'dbname' => self::DEFAULT_DBNAME, 'dbtable' => self::DEFAULT_DBTABLE - ) - ); + ], + ]; /** * HandlerSocket object @@ -78,7 +78,7 @@ class HandlerSocket extends Adapter implements AdapterInterface * * @var array */ - protected $fields = array(); + protected $fields = []; /** * Class constructor. @@ -86,7 +86,7 @@ class HandlerSocket extends Adapter implements AdapterInterface * @param array $options associative array of options * @throws \Phalcon\Session\Exception */ - public function __construct($options = array()) + public function __construct($options = []) { // initialize the handlersocket database if (empty($options)) { @@ -97,12 +97,12 @@ public function __construct($options = array()) //set object as the save handler session_set_save_handler( - array($this, 'open'), - array($this, 'close'), - array($this, 'read'), - array($this, 'write'), - array($this, 'destroy'), - array($this, 'gc') + [$this, 'open'], + [$this, 'close'], + [$this, 'read'], + [$this, 'write'], + [$this, 'destroy'], + [$this, 'gc'] ); } @@ -122,7 +122,7 @@ public function __destruct() * @param array $options associative array of options * @return void */ - public function start($options = array()) + public function start($options = []) { $object = new self($options); @@ -160,7 +160,7 @@ public function close() */ public function read($id) { - $retval = $this->hs->executeSingle($this->hsIndex, '=', array($id), 1, 0); + $retval = $this->hs->executeSingle($this->hsIndex, '=', [$id], 1, 0); if (!isset($retval[0], $retval[0][2])) { return ''; @@ -183,13 +183,13 @@ public function read($id) public function write($id, $data) { if (isset($this->fields['id']) && $this->fields['id'] != $id) { - $this->fields = array(); + $this->fields = []; } if (empty($this->fields)) { - $this->hs->executeInsert($this->hsIndex, array($id, date('Y-m-d H:i:s'), $data)); + $this->hs->executeInsert($this->hsIndex, [$id, date('Y-m-d H:i:s'), $data]); } else { - $this->hs->executeUpdate($this->hsIndex, '=', array($id), array($id, date('Y-m-d H:i:s'), $data), 1, 0); + $this->hs->executeUpdate($this->hsIndex, '=', [$id], [$id, date('Y-m-d H:i:s'), $data], 1, 0); } return true; @@ -203,7 +203,7 @@ public function write($id, $data) */ public function destroy($id) { - $this->hs->executeDelete($this->hsIndex, '=', array($id), 1, 0); + $this->hs->executeDelete($this->hsIndex, '=', [$id], 1, 0); return true; } @@ -227,7 +227,7 @@ public function gc($maxlifetime) '' ); - $this->hs->executeDelete($index, '<', array($time), 1000, 0); + $this->hs->executeDelete($index, '<', [$time], 1000, 0); return true; } @@ -241,7 +241,7 @@ public function gc($maxlifetime) protected function init($options) { if (empty($options['server'])) { - $options['server'] = array(); + $options['server'] = []; } if (empty($options['server']['host'])) { diff --git a/Library/Phalcon/Session/Adapter/Mongo.php b/Library/Phalcon/Session/Adapter/Mongo.php index 07b00aaee..b75a0d9fa 100644 --- a/Library/Phalcon/Session/Adapter/Mongo.php +++ b/Library/Phalcon/Session/Adapter/Mongo.php @@ -74,6 +74,7 @@ public function open() */ public function close() { + return true; } /** diff --git a/Library/Phalcon/Test/FunctionalTestCase.php b/Library/Phalcon/Test/FunctionalTestCase.php index 3b3149086..9be7edf17 100755 --- a/Library/Phalcon/Test/FunctionalTestCase.php +++ b/Library/Phalcon/Test/FunctionalTestCase.php @@ -134,7 +134,7 @@ public function assertAction($expected) /** * Assert that the response headers contains the given array *
- * $expected = array('Content-Type' => 'application/json')
+ * $expected = ['Content-Type' => 'application/json']
*
*
* @param array $expected The expected headers
diff --git a/Library/Phalcon/Translate/Adapter/Redis.php b/Library/Phalcon/Translate/Adapter/Redis.php
new file mode 100644
index 000000000..0cab6dc7a
--- /dev/null
+++ b/Library/Phalcon/Translate/Adapter/Redis.php
@@ -0,0 +1,249 @@
+redis = $options['redis'];
+ $this->language = $options['language'];
+
+ if (isset($options['levels'])) {
+ $this->levels = $options['levels'];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $translateKey
+ * @return boolean
+ */
+ public function exists($translateKey)
+ {
+ $index = $this->getLongKey($translateKey);
+ $key = $this->getShortKey($index);
+
+ $this->loadValueByKey($key);
+
+ return (isset($this->cache[$key]) && isset($this->cache[$key][$index]));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $translateKey
+ * @param array $placeholders
+ * @return string
+ */
+ public function query($translateKey, $placeholders = null)
+ {
+ $index = $this->getLongKey($translateKey);
+ $key = $this->getShortKey($index);
+
+ $this->loadValueByKey($key);
+
+ return isset($this->cache[$key]) && isset($this->cache[$key][$index])
+ ? $this->cache[$key][$index]
+ : $translateKey;
+ }
+
+ /**
+ * Adds a translation for given key (No existence check!)
+ *
+ * @param string $translateKey
+ * @param string $message
+ * @return boolean
+ */
+ public function add($translateKey, $message)
+ {
+ $index = $this->getLongKey($translateKey);
+ $key = $this->getShortKey($index);
+
+ $this->loadValueByKey($key);
+
+ if (!isset($this->cache[$key])) {
+ $this->cache[$key] = [];
+ }
+
+ $this->cache[$key][$index] = $message;
+
+ return $this->redis->set($key, serialize($this->cache[$key]));
+ }
+
+ /**
+ * Update a translation for given key (No existence check!)
+ *
+ * @param string $translateKey
+ * @param string $message
+ * @return boolean
+ */
+ public function update($translateKey, $message)
+ {
+ return $this->add($translateKey, $message);
+ }
+
+ /**
+ * Deletes a translation for given key (No existence check!)
+ *
+ * @param string $translateKey
+ * @return boolean
+ */
+ public function delete($translateKey)
+ {
+ $index = $this->getLongKey($translateKey);
+ $key = $this->getShortKey($index);
+ $nbResult = $this->redis->del($key);
+
+ unset($this->cache[$key]);
+
+ return $nbResult > 0;
+ }
+
+ /**
+ * Sets (insert or updates) a translation for given key
+ *
+ * @param string $translateKey
+ * @param string $message
+ * @return boolean
+ */
+ public function set($translateKey, $message)
+ {
+ return $this->exists($translateKey) ?
+ $this->update($translateKey, $message) : $this->add($translateKey, $message);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $translateKey
+ * @return string
+ */
+ public function offsetExists($translateKey)
+ {
+ return $this->exists($translateKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $translateKey
+ * @param string $message
+ * @return string
+ */
+ public function offsetSet($translateKey, $message)
+ {
+ return $this->update($translateKey, $message);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $translateKey
+ * @return string
+ */
+ public function offsetGet($translateKey)
+ {
+ return $this->query($translateKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $translateKey
+ * @return string
+ */
+ public function offsetUnset($translateKey)
+ {
+ return $this->delete($translateKey);
+ }
+
+ /**
+ * Loads key from Redis to local cache.
+ *
+ * @param string $key
+ */
+ protected function loadValueByKey($key)
+ {
+ if (!isset($this->cache[$key])) {
+ $result = $this->redis->get($key);
+ $result = unserialize($result);
+
+ if (is_array($result)) {
+ $this->cache[$key] = $result;
+ }
+ }
+ }
+
+ /**
+ * Returns long key for index.
+ *
+ * @param string $index
+ * @return string
+ */
+ protected function getLongKey($index)
+ {
+ return md5($this->language . ':' . $index);
+ }
+
+ /**
+ * Returns short key for index.
+ *
+ * @param string $index
+ * @return string
+ */
+ protected function getShortKey($index)
+ {
+ return substr($index, 0, $this->levels);
+ }
+}
diff --git a/Library/Phalcon/Validation/Validator/CardNumber.php b/Library/Phalcon/Validation/Validator/CardNumber.php
new file mode 100644
index 000000000..cf76a842d
--- /dev/null
+++ b/Library/Phalcon/Validation/Validator/CardNumber.php
@@ -0,0 +1,108 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Validation\Validator;
+
+use Phalcon\Validation;
+use Phalcon\Validation\Message;
+use Phalcon\Validation\Validator;
+use Phalcon\Validation\Exception;
+
+/**
+ * Phalcon\Mvc\Model\Validator\CardNumber
+ *
+ * Checks if a credit card number using Luhn algorithm
+ *
+ *
+ * use Phalcon\Validation\Validator\CardNumber as CreditCardValidator;
+ *
+ * $validator->add('creditcard', new CreditCardValidator([
+ * 'message' => 'The credit card number is not valid',
+ * 'type' => CardNumber::VISA, // Any if not specified
+ * ]));
+ *
+ */
+class CardNumber extends Validator
+{
+ const AMERICAN_EXPRESS = 0; // 34, 37
+ const MASTERCARD = 1; // 51-55
+ const VISA = 2; // 4
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param Validation $validation
+ * @param string $attribute
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public function validate(Validation $validation, $attribute)
+ {
+ $value = preg_replace('/[^\d]/', '', $validation->getValue($attribute));
+ $message = ($this->hasOption('message')) ? $this->getOption('message') : 'Credit card number is invalid';
+
+ if ($this->hasOption('type')) {
+ $type = $this->getOption('type');
+
+ switch ($type) {
+ case CardNumber::AMERICAN_EXPRESS:
+ $issuer = substr($value, 0, 2);
+ $result = (true === in_array($issuer, [34, 37]));
+ break;
+ case CardNumber::MASTERCARD:
+ $issuer = substr($value, 0, 2);
+ $result = (true === in_array($issuer, [51, 52, 53, 54, 55]));
+ break;
+ case CardNumber::VISA:
+ $issuer = $value[0];
+ $result = ($issuer == 4);
+ break;
+ default:
+ throw new Exception('Incorrect type specifier');
+ }
+
+ if (false === $result) {
+ $validation->appendMessage(new Message($message, $attribute, 'CardNumber'));
+ return false;
+ }
+ }
+
+ $value = strrev($value);
+ $checkSum = 0;
+
+ for ($i = 0; $i < strlen($value); $i++) {
+ if (($i % 2) == 0) {
+ $temp = $value[$i];
+ } else {
+ $temp = $value[$i] * 2;
+ if ($temp > 9) {
+ $temp -= 9;
+ }
+ }
+ $checkSum += $temp;
+ }
+
+ if (($checkSum % 10) != 0) {
+ $validation->appendMessage(new Message($message, $attribute, 'CardNumber'));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/Library/Phalcon/Validation/Validator/ConfirmationOf.php b/Library/Phalcon/Validation/Validator/ConfirmationOf.php
index 461599074..ee50337a2 100644
--- a/Library/Phalcon/Validation/Validator/ConfirmationOf.php
+++ b/Library/Phalcon/Validation/Validator/ConfirmationOf.php
@@ -13,15 +13,15 @@
| obtain it through the world-wide-web, please send an email |
| to license@phalconphp.com so we can send you a copy immediately. |
+------------------------------------------------------------------------+
- | Authors: David Hubner
+ * use Phalcon\Validation\Validator;
+ *
+ * $validator->add('g-recaptcha-response', new Validator([
+ * 'message' => 'The captcha is not valid',
+ * 'secret' => 'your_site_key'
+ * ]));
+ *
+ *
+ * @link https://developers.google.com/recaptcha/intro
+ * @package Phalcon\Validation\Validator
+ */
+class ReCaptcha extends Validator
+{
+ /**
+ * API request URL
+ */
+ const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api/siteverify';
+
+ /**
+ * Response error code reference
+ * @var array $messages
+ */
+ protected $messages = [
+ 'missing-input-secret' => 'The secret parameter is missing.',
+ 'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
+ 'missing-input-response' => 'The response parameter is missing.',
+ 'invalid-input-response' => 'The response parameter is invalid or malformed.',
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param Validation $validation
+ * @param string $attribute
+ *
+ * @return bool
+ */
+ public function validate(Validation $validation, $attribute)
+ {
+ $secret = $this->getOption('secret');
+ $value = $validation->getValue($attribute);
+ $request = $validation->getDI()->get('request');
+ $remoteIp = $request->getClientAddress(false);
+
+ if (!empty($value)) {
+ $curl = curl_init(self::RECAPTCHA_URL);
+ curl_setopt_array($curl, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POSTFIELDS => [
+ 'secret' => $secret,
+ 'response' => $value,
+ 'remoteip' => $remoteIp
+ ]
+ ]);
+ $response = json_decode(curl_exec($curl), true);
+ curl_close($curl);
+ }
+
+ if (empty($response['success'])) {
+ $label = $this->getOption('label');
+ if (empty($label)) {
+ $label = $validation->getLabel($attribute);
+ }
+
+ $message = $this->getOption('message');
+ $replacePairs = [':field', $label];
+ if (empty($message) && !empty($response['error-codes'])) {
+ $message = $this->messages[$response['error-codes']];
+ }
+
+ if (empty($message)) {
+ $message = $validation->getDefaultMessage('ReCaptcha');
+ }
+
+ $validation->appendMessage(new Message(strtr($message, $replacePairs), $attribute, 'ReCaptcha'));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/README.md b/README.md
index 8487be9fa..d2dec3270 100644
--- a/README.md
+++ b/README.md
@@ -24,22 +24,22 @@ Install Composer in a common location or in your project:
curl -s http://getcomposer.org/installer | php
```
-If you are using Phalcon 2.0.x, create the `composer.json` file as follows:
+Then create the `composer.json` file as follows:
```json
{
"require": {
- "phalcon/incubator": "^2.0"
+ "phalcon/incubator": "~3.0"
}
}
```
-If you are still using Phalcon 1.3.x, create a `composer.json` with the following instead:
+If you are still using Phalcon 2.0.x, create the `composer.json` file as follows:
```json
{
"require": {
- "phalcon/incubator": "^1.3"
+ "phalcon/incubator": "^2.0"
}
}
```
@@ -59,15 +59,15 @@ Just clone the repository in a common location or inside your project:
git clone https://github.com/phalcon/incubator.git
```
-For a specific Git branch (eg 1.3.5) please use:
+For a specific Git branch (eg 2.0.13) please use:
```
-git clone -b 1.3.5 git@github.com:phalcon/incubator.git
+git clone -b 2.0.13 git@github.com:phalcon/incubator.git
```
## Autoloading from the Incubator
-Add or register the following namespace strategy to your Phalcon\Loader in order
+Add or register the following namespace strategy to your `Phalcon\Loader` in order
to load classes from the incubator repository:
```php
@@ -99,6 +99,8 @@ See [CONTRIBUTING.md](docs/CONTRIBUTING.md)
### Annotations
* [Phalcon\Annotations\Adapter\Memcached](Library/Phalcon/Annotations/Adapter) - Memcached adapter for storing annotations (@igusev)
+* [Phalcon\Annotations\Adapter\Redis](Library/Phalcon/Annotations/Adapter) - Redis adapter for storing annotations (@sergeyklay)
+* [Phalcon\Annotations\Adapter\Aerospike](Library/Phalcon/Annotations/Adapter) - Aerospike adapter for storing annotations (@sergeyklay)
### Behaviors
* [Phalcon\Mvc\Model\Behavior\Blameable](Library/Phalcon/Mvc/Model/Behavior) - logs with every created or updated row in your database who created and who updated it (@phalcon)
@@ -111,45 +113,52 @@ See [CONTRIBUTING.md](docs/CONTRIBUTING.md)
### Config
* [Phalcon\Config\Loader](Library/Phalcon/Config) - Dynamic config loader by file extension (@Kachit)
+* [Phalcon\Config\Adapter\Xml](Library/Phalcon/Config) - Reads xml files and converts them to Phalcon\Config objects. (@sergeyklay)
### Console
* [Phalcon\Cli\Console\Extended](Library/Phalcon/Cli/Console) - Extended Console application that uses annotations in order to create automatically a help description (@sarrubia)
* [Phalcon\Cli\Environment](Library/Phalcon/Cli/Environment) - This component provides functionality that helps writing CLI oriented code that has runtime-specific execution params (@sergeyklay)
+### Crypt
+* [Phalcon\Legacy\Crypt](Library/Phalcon/Legacy) - Port of Phalcon 2.0.x (legacy) `Phalcon\Crypt` (@sergeyklay)
+
### Database
-* [Phalcon\Db\Adapter\Cacheable\Mysql](Library/Phalcon/Db) - MySQL adapter that aggressively caches all the queries executed (@phalcon)
-* [Phalcon\Db\Adapter\Factory](Library/Phalcon/Db/Adapter/Factory.php) - Phalcon DB adapters Factory (@Kachit)
+
+#### Adapter
+* [Phalcon\Db\Adapter\Cacheable\Mysql](Library/Phalcon/Db/Adapter) - MySQL adapter that aggressively caches all the queries executed (@phalcon)
+* [Phalcon\Db\Adapter\Factory](Library/Phalcon/Db/Adapter) - Phalcon DB adapters Factory (@Kachit)
+* [Phalcon\Db\Adapter\MongoDB](Library/Phalcon/Db/Adapter) - Database adapter for the new MongoDB extension (@tigerstrikemedia)
+* [Phalcon\Db\Adapter\Pdo\Oracle](Library/Phalcon/Db/Adapter) - Database adapter for the Oracle for the Oracle RDBMS. (@sergeyklay)
+
+#### Dialect
+* [Phalcon\Db\Dialect\MysqlExtended](Library/Phalcon/Db/Dialect) - Generates database specific SQL for the MySQL RDBMS. Extended version. (@phalcon)
+* [Phalcon\Db\Dialect\Oracle](Library/Phalcon/Db/Dialect) - Generates database specific SQL for the Oracle RDBMS. (@sergeyklay)
### Http
* [Phalcon\Http](Library/Phalcon/Http) - Uri utility (@tugrul)
-* [Phalcon\Http\Client](Library/Phalcon/Http\Client) - Http Request and Response (@tugrul)
-
-### Loader
-* [Phalcon\Loader\Extended](Library/Phalcon/Loader/Extended.php) - This component extends `Phalcon\Loader` and added ability to set multiple directories per namespace (@sergeyklay)
-* [Phalcon\Loader\PSR](Library/Phalcon/Loader/PSR.php) - Implements PSR-0 autoloader for your apps (@Piyush)
+* [Phalcon\Http\Client](Library/Phalcon/Http/Client) - Http Request and Response (@tugrul)
### Logger
-* [Phalcon\Logger\Adapter\Database](Library/Phalcon/Logger) - Adapter to store logs in a database table (!phalcon)
+* [Phalcon\Logger\Adapter\Database](Library/Phalcon/Logger) - Adapter to store logs in a database table (@phalcon)
* [Phalcon\Logger\Adapter\Firelogger](Library/Phalcon/Logger) - Adapter to log messages in the Firelogger console in Firebug (@phalcon)
-* [Phalcon\Logger\Adapter\Udplogger](Library/Phalcon/Logger) - Adapter to log messages using UDP protocol to external server (@vitalypanait)
* [Phalcon\Logger\Adapter\File\Multiple](Library/Phalcon/Logger) - Adapter to log to multiple files (@rlaffers)
### Mailer
* [Phalcon\Mailer\Manager](Library/Phalcon/Mailer) - Mailer wrapper over SwiftMailer (@KorsaR-ZN)
+### Model MetaData Adapters
+* [Phalcon\Mvc\Model\MetaData\Wincache](Library/Phalcon/Mvc/Model/MetaData) - Adapter for the Wincache php extension
+
+### MVC
+* [Phalcon\Mvc\MongoCollection](Library/Phalcon/MVC/MongoCollection) - Collection class for the new MongoDB Extension (@tigerstrikemedia)
+
### Template Engines
* [Phalcon\Mvc\View\Engine\Mustache](Library/Phalcon/Mvc/View/Engine) - Adapter for Mustache (@phalcon)
* [Phalcon\Mvc\View\Engine\Twig](Library/Phalcon/Mvc/View/Engine) - Adapter for Twig (@phalcon)
* [Phalcon\Mvc\View\Engine\Smarty](Library/Phalcon/Mvc/View/Engine) - Adapter for Smarty (@phalcon)
-### ORM Validators
-* [Phalcon\Mvc\Model\Validator\ConfirmationOf](Library/Phalcon/Mvc/Model/Validator) - Allows to validate if a field has a confirmation field with the same value (@suxxes)
-* [Phalcon\Mvc\Model\Validator\CardNumber](Library/Phalcon/Mvc/Model/Validator) - Allows to validate credit card number using Luhn algorithm (@parshikov)
-* [Phalcon\Mvc\Model\Validator\Decimal](Library/Phalcon/Mvc/Model/Validator) - Allows to validate if a field has a valid number in proper decimal format (negative and decimal numbers allowed) (@sergeyklay)
-* [Phalcon\Mvc\Model\Validator\Between](Library/Phalcon/Mvc/Model/Validator) - Validates that a value is between a range of two values (@sergeyklay)
-
### Error Handling
-* [Phalcon\Error](Library/Phalcon/Error) - Error handler used to centralize the error handling and displaying clean error pages (theDisco)
+* [Phalcon\Error](Library/Phalcon/Error) - Error handler used to centralize the error handling and displaying clean error pages (@theDisco)
* [Phalcon\Utils\PrettyExceptions](https://github.com/phalcon/pretty-exceptions) - Pretty Exceptions is an utility to show exceptions/errors/warnings/notices using a nicely visualization. (@phalcon / @kenjikobe)
### Queue
@@ -175,7 +184,10 @@ See [CONTRIBUTING.md](docs/CONTRIBUTING.md)
* [Phalcon\Avatar\Gravatar](Library/Phalcon/Avatar) - Provides an easy way to retrieve a user's profile image from Gravatar site based on a given email address (@sergeyklay)
### Validators
+* [Phalcon\Validation\Validator\CardNumber](Library/Phalcon/Validation/Validator) - Allows to validate credit card number using Luhn algorithm (@parshikov)
+* [Phalcon\Validation\Validator\ReCaptcha](Library/Phalcon/Validation/Validator) - The reCAPTCHA Validator (@pflorek)
* [Phalcon\Validation\Validator\ConfirmationOf](Library/Phalcon/Validation/Validator) - Validates confirmation of other field value (@davihu)
+* [Phalcon\Validation\Validator\Decimal](Library/Phalcon/Validation/Validator) - Allows to validate if a field has a valid number in proper decimal format (negative and decimal numbers allowed) (@sergeyklay)
* [Phalcon\Validation\Validator\MongoId](Library/Phalcon/Validation/Validator) - Validate MongoId value (@Kachit)
* [Phalcon\Validation\Validator\PasswordStrength](Library/Phalcon/Validation/Validator) - Validates password strength (@davihu)
diff --git a/composer.json b/composer.json
index 7ff625ece..9926c221f 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,12 @@
"authors": [
{
"name": "Phalcon Team",
- "email": "team@phalconphp.com"
+ "email": "team@phalconphp.com",
+ "homepage": "http://phalconphp.com/en/team"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/phalcon/incubator/graphs/contributors"
}
],
"support": {
@@ -17,13 +22,15 @@
"forum": "https://forum.phalconphp.com/"
},
"require": {
- "php": ">=5.4",
- "ext-phalcon": "^2.0",
+ "php": ">=5.5",
+ "ext-phalcon": "^3.0",
"swiftmailer/swiftmailer": "~5.2"
},
"require-dev": {
- "squizlabs/php_codesniffer": "~2.5",
- "codeception/codeception": "~2.1",
+ "phpdocumentor/reflection-docblock": "^2.0.4",
+ "phpunit/phpunit": "4.8.*",
+ "squizlabs/php_codesniffer": "~2.6",
+ "codeception/codeception": "~2.2",
"codeception/mockery-module": "~0.2",
"codeception/aerospike-module": "~0.1",
"codeception/specify": "~0.4",
@@ -31,14 +38,18 @@
},
"suggest": {
"ext-aerospike": "*",
+ "sergeyklay/aerospike-php-stubs": "The most complete Aerospike PHP stubs which allows autocomplete in modern IDEs",
"duncan3dc/fork-helper": "To use extended class to access the beanstalk queue service"
},
"autoload": {
- "psr-4": { "Phalcon\\": "Library/Phalcon/" }
+ "psr-4": {
+ "Phalcon\\": "Library/Phalcon/"
+ }
},
"autoload-dev": {
"psr-4": {
- "Phalcon\\Test\\": "tests/unit/"
+ "Phalcon\\Test\\": "tests/unit/",
+ "Phalcon\\Test\\Collections\\": "tests/_data/collections"
}
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..47091a0e6
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,56 @@
+version: '2'
+
+services:
+ redis:
+ restart: always
+ image: redis:3.2
+ container_name: incubator_redis
+ expose:
+ - "6379"
+ ports:
+ - "6379:6379"
+
+ mysql:
+ restart: always
+ image: mysql:5.7
+ container_name: incubator_mysql
+ expose:
+ - "3306"
+ ports:
+ - "3306:3306"
+ environment:
+ MYSQL_DATABASE: incubator_tests
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
+
+ memcached:
+ restart: always
+ image: memcached:1.4
+ container_name: incubator_memcached
+ ports:
+ - "11211:11211"
+
+ queue:
+ restart: always
+ image: phalconphp/beanstalkd:1.10
+ container_name: incubator_beanstalkd
+ ports:
+ - "11302:11302"
+
+ aerospike:
+ restart: always
+ image: aerospike/aerospike-server
+ container_name: incubator_aerospike
+ ports:
+ - "3000:3000"
+ - "3001:3001"
+ - "3002:3002"
+ - "3003:3003"
+
+ mongodb:
+ restart: always
+ image: mongo:latest
+ container_name: incubator_mongo
+ expose:
+ - "27017"
+ ports:
+ - "27017:27017"
diff --git a/docs/LICENSE.md b/docs/LICENSE.md
index 61e91a18d..65d26ab15 100644
--- a/docs/LICENSE.md
+++ b/docs/LICENSE.md
@@ -9,6 +9,6 @@ Redistribution and use in source and binary forms, with or without modification,
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-* Neither the name of the