Laravel Unwrapped: sesión, autenticación y caché
() translation by (you can also view the original English article)
En los últimos años, Laravel se ha convertido en uno de los frameworks de software más destacados que utilizan los ingenieros de software para la creación de sus aplicaciones web. Similar a la popularidad de la que gozó CodeIgniter en su apogeo, Laravel ha sido elogiada por su facilidad de uso, amabilidad para los principiantes y su adherencia a los estándares de la industria.
Introducción
Sin embargo, una cosa que no muchos programadores aprovechan es el sistema basado en componentes de Laravel. Desde su conversión a componentes impulsados por compositores, Laravel 4 se ha convertido en un sistema muy modular, similar a la verbosidad de frameworks más maduros como Symfony. Esto se llama el grupo de componentes Illuminate
, que en mi opinión, no es el framework real en sí, sino una compilación de bibliotecas que un framework puede usar potencialmente. El framework real de Laravel está representado por la aplicación de esqueleto de Laravel (que se encuentra en el repositorio de GitHub laravel/laravel
) que hace uso de estos componentes para crear una aplicación web.
En este tutorial, nos sumergiremos en un grupo de estos componentes, aprenderemos cómo funcionan, cómo los utiliza el framework y cómo podemos ampliar su funcionalidad.
El componente Session
El componente Session de Laravel maneja las sesiones de la aplicación web. Hace uso de un sistema basado en controladores llamado Laravel Manager, que actúa como una fábrica y un contenedor para cualquier controlador que se establezca en el archivo de configuración. A partir de esta escritura, el componente Session tiene controladores para:
-
file
: un controlador de sesión basado en archivos donde los datos de sesión se guardan en un archivo cifrado. -
cookie
: un controlador de sesión basado en cookies donde los datos de la sesión están encriptados en las cookies del usuario. -
database
: los datos de sesión se guardan en la base de datos configurada para la aplicación. -
apc
: los datos de la sesión se guardan en APC. -
memcached
: los datos de la sesión se guardan en Memcached. -
redis
: los datos de la sesión se guardan en Redis. -
array
: los datos de la sesión se guardan en una matriz PHP. Ten en cuenta que el controlador de sesión de matriz no admite la persistencia y normalmente solo se utiliza en los comandos de la consola.
Proveedores de servicios
La mayoría de los usuarios de Laravel no se dan cuenta, pero una gran parte de cómo funciona Laravel, está dentro de sus proveedores de servicios. Básicamente, son archivos de arranque para cada componente y están lo suficientemente abstraídos para que los usuarios puedan arrancar cualquier componente de cualquier forma.
Esta es una explicación aproximada de cómo funciona esto:
- Se inicia el componente de la aplicación Laravel. Este es el controlador principal de todo el framework, responsable de manejar la solicitud HTTP, ejecutar los proveedores de servicios y actuar como un contenedor de dependencia para el framework.
- Una vez que se ejecuta un proveedor de servicios, se llama a su método
register
. Esto nos permite crear una instancia de cualquier componente que queramos.- Ten en cuenta que todos los proveedores de servicios tienen acceso a la aplicación principal de Laravel (a través de
$this->app
), lo que le permitiría a los proveedores de servicios insertar instancias de las clases resueltas en el contenedor de dependencias.
- Ten en cuenta que todos los proveedores de servicios tienen acceso a la aplicación principal de Laravel (a través de
- Una vez cargadas estas dependencias, deberíamos tener la libertad de usarlas llamando al contenedor, por ejemplo, a través del sistema Facade de Laravel,
App::make
.
Volviendo a Sessions, revisemos rápidamente a SessionServiceProivider
:
1 |
/**
|
2 |
* Register the session manager instance.
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
protected function registerSessionManager() |
7 |
{
|
8 |
$this->app->bindShared('session', function($app) |
9 |
{
|
10 |
return new SessionManager($app); |
11 |
});
|
12 |
}
|
13 |
|
14 |
/**
|
15 |
* Register the session driver instance.
|
16 |
*
|
17 |
* @return void
|
18 |
*/
|
19 |
protected function registerSessionDriver() |
20 |
{
|
21 |
$this->app->bindShared('session.store', function($app) |
22 |
{
|
23 |
// First, we will create the session manager which is responsible for the
|
24 |
// creation of the various session drivers when they are needed by the
|
25 |
// application instance, and will resolve them on a lazy load basis.
|
26 |
$manager = $app['session']; |
27 |
|
28 |
return $manager->driver(); |
29 |
});
|
30 |
}
|
Estos dos métodos son llamados por la función register()
. El primero, registerSessionManager()
, se llama para registrar inicialmente el SessionManager
. Esta clase extiende el Manager
que mencioné arriba. El segundo, registerSessionDriver()
registra un controlador de sesión para el administrador, basado en lo que configuramos. Esto finalmente llama a este método en la clase Illuminate\Support\Manager
:
1 |
/**
|
2 |
* Create a new driver instance.
|
3 |
*
|
4 |
* @param string $driver
|
5 |
* @return mixed
|
6 |
*
|
7 |
* @throws \InvalidArgumentException
|
8 |
*/
|
9 |
protected function createDriver($driver) |
10 |
{
|
11 |
$method = 'create'.ucfirst($driver).'Driver'; |
12 |
|
13 |
// We'll check to see if a creator method exists for the given driver. If not we
|
14 |
// will check for a custom driver creator, which allows developers to create
|
15 |
// drivers using their own customized driver creator Closure to create it.
|
16 |
if (isset($this->customCreators[$driver])) |
17 |
{
|
18 |
return $this->callCustomCreator($driver); |
19 |
}
|
20 |
elseif (method_exists($this, $method)) |
21 |
{
|
22 |
return $this->$method(); |
23 |
}
|
24 |
|
25 |
throw new \InvalidArgumentException("Driver [$driver] not supported."); |
26 |
}
|
Desde aquí, podemos ver que en base al nombre del controlador, desde el archivo de configuración, se llama a un método específico. Entonces, si lo tenemos configurado para usar el controlador de sesión file
, llamará a este método en la clase SessionManager
:
1 |
/**
|
2 |
* Create an instance of the file session driver.
|
3 |
*
|
4 |
* @return \Illuminate\Session\Store
|
5 |
*/
|
6 |
protected function createFileDriver() |
7 |
{
|
8 |
return $this->createNativeDriver(); |
9 |
}
|
10 |
|
11 |
/**
|
12 |
* Create an instance of the file session driver.
|
13 |
*
|
14 |
* @return \Illuminate\Session\Store
|
15 |
*/
|
16 |
protected function createNativeDriver() |
17 |
{
|
18 |
$path = $this->app['config']['session.files']; |
19 |
|
20 |
return $this->buildSession(new FileSessionHandler($this->app['files'], $path)); |
21 |
}
|
Luego, la clase de controlador se inyecta en una clase Store
, que es responsable de llamar a los métodos de sesión reales. Esto nos permite realmente separar la implementación de SessionHandlerInterface
del SPL en los controladores, la clase Store
lo facilita.
Creación de nuestro propio controlador de sesiones
Crearemos nuestro propio controlador de sesión, un controlador de sesión de MongoDB. En primer lugar, necesitaremos crear un MongoSessionHandler
dentro de una instancia de proyecto Laravel recién instalada. (Pediremos prestado mucho de Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler
).:
1 |
<?php namespace Illuminate\Session; |
2 |
|
3 |
use Mongo; |
4 |
use MongoDate; |
5 |
use MongoBinData; |
6 |
|
7 |
class MongoSessionHandler implements \SessionHandlerInterface |
8 |
{
|
9 |
/**
|
10 |
* Mongo db config
|
11 |
*
|
12 |
* @var array
|
13 |
*/
|
14 |
protected $config; |
15 |
|
16 |
/**
|
17 |
* Mongo db connection
|
18 |
*
|
19 |
* @var \Mongo
|
20 |
*/
|
21 |
protected $connection; |
22 |
|
23 |
/**
|
24 |
* Mongodb collection
|
25 |
*
|
26 |
* @var \MongoCollection
|
27 |
*/
|
28 |
protected $collection; |
29 |
/**
|
30 |
* Create a new Mongo driven handler instance.
|
31 |
*
|
32 |
* @param array $config
|
33 |
* - $config['host'] Mongodb host
|
34 |
* - $config['username'] Mongodb username
|
35 |
* - $config['password'] Mongodb password
|
36 |
* - $config['database'] Mongodb database
|
37 |
* - $config['collection'] Mongodb collection
|
38 |
* @return void
|
39 |
*/
|
40 |
public function __construct(array $config) |
41 |
{
|
42 |
$this->config = $config; |
43 |
|
44 |
$connection_string = 'mongodb://'; |
45 |
|
46 |
if (!empty($this->config['username']) && !empty($this->config['password'])) { |
47 |
$connection_string .= "{$this->config['user']}:{$this->config['password']}@"; |
48 |
}
|
49 |
|
50 |
$connection_string .= "{$this->config['host']}"; |
51 |
|
52 |
$this->connection = new Mongo($connection_string); |
53 |
|
54 |
$this->collection = $this->connection->selectCollection($this->config['database'], $this->config['collection']); |
55 |
}
|
56 |
|
57 |
/**
|
58 |
* {@inheritDoc}
|
59 |
*/
|
60 |
public function open($savePath, $sessionName) |
61 |
{
|
62 |
return true; |
63 |
}
|
64 |
|
65 |
/**
|
66 |
* {@inheritDoc}
|
67 |
*/
|
68 |
public function close() |
69 |
{
|
70 |
return true; |
71 |
}
|
72 |
|
73 |
/**
|
74 |
* {@inheritDoc}
|
75 |
*/
|
76 |
public function read($sessionId) |
77 |
{
|
78 |
$session_data = $this->collection->findOne(array( |
79 |
'_id' => $sessionId, |
80 |
));
|
81 |
|
82 |
if (is_null($session_data)) { |
83 |
return ''; |
84 |
} else { |
85 |
return $session_data['session_data']->bin; |
86 |
}
|
87 |
}
|
88 |
|
89 |
/**
|
90 |
* {@inheritDoc}
|
91 |
*/
|
92 |
public function write($sessionId, $data) |
93 |
{
|
94 |
$this->collection->update( |
95 |
array( |
96 |
'_id' => $sessionId |
97 |
),
|
98 |
array( |
99 |
'$set' => array( |
100 |
'session_data' => new MongoBinData($data, MongoBinData::BYTE_ARRAY), |
101 |
'timestamp' => new MongoDate(), |
102 |
)
|
103 |
),
|
104 |
array( |
105 |
'upsert' => true, |
106 |
'multiple' => false |
107 |
)
|
108 |
);
|
109 |
}
|
110 |
|
111 |
/**
|
112 |
* {@inheritDoc}
|
113 |
*/
|
114 |
public function destroy($sessionId) |
115 |
{
|
116 |
$this->collection->remove(array( |
117 |
'_id' => $sessionId |
118 |
));
|
119 |
|
120 |
return true; |
121 |
}
|
122 |
|
123 |
/**
|
124 |
* {@inheritDoc}
|
125 |
*/
|
126 |
public function gc($lifetime) |
127 |
{
|
128 |
$time = new MongoDate(time() - $lifetime); |
129 |
|
130 |
$this->collection->remove(array( |
131 |
'timestamp' => array('$lt' => $time), |
132 |
));
|
133 |
|
134 |
return true; |
135 |
}
|
136 |
}
|
Debes guardar esto en la carpeta vendor/laravel/framework/src/Illuminate/Session
. Para los propósitos de este proyecto, lo pondremos aquí, pero lo ideal es que este archivo esté dentro de su propio espacio de nombres de biblioteca.
Luego, debemos asegurarnos de que la clase Manager
pueda llamar a este controlador. Podemos hacer esto utilizando el método Manager::extend
. Abre vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php
y agrega el siguiente código. Idealmente, deberíamos ampliar el proveedor de servicios, pero eso está fuera del alcance de este tutorial.
1 |
/**
|
2 |
* Setup the Mongo Driver callback
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
public function setupMongoDriver() |
7 |
{
|
8 |
$manager = $this->app['session']; |
9 |
|
10 |
$manager->extend('mongo', function($app) { |
11 |
return new MongoSessionHandler(array( |
12 |
'host' => $app['config']->get('session.mongo.host'), |
13 |
'username' => $app['config']->get('session.mongo.username'), |
14 |
'password' => $app['config']->get('session.mongo.password'), |
15 |
'database' => $app['config']->get('session.mongo.database'), |
16 |
'collection' => $app['config']->get('session.mongo.collection') |
17 |
));
|
18 |
});
|
19 |
}
|
Asegúrate de actualizar el método register()
para llamar a este método:
1 |
/**
|
2 |
* Register the service provider.
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
public function register() |
7 |
{
|
8 |
$this->setupDefaultDriver(); |
9 |
|
10 |
$this->registerSessionManager(); |
11 |
|
12 |
$this->setupMongoDriver(); |
13 |
|
14 |
$this->registerSessionDriver(); |
15 |
}
|
Después, necesitamos definir la configuración de Mongo DB. Abre app/config/session.php
y define los siguientes valores de configuración:
1 |
/**
|
2 |
* Mongo DB settings
|
3 |
*/
|
4 |
'mongo' => array( |
5 |
'host' => '127.0.0.1', |
6 |
'username' => '', |
7 |
'password' => '', |
8 |
'database' => 'laravel', |
9 |
'collection' => 'laravel_session_collection' |
10 |
)
|
Mientras estamos en este archivo, también deberíamos actualizar la configuración driver
en la parte de arriba:
1 |
'driver' => 'mongo' |
Ahora, intenta acceder a la página principal (normalmente, localhost/somefolder/public
). Si esta página se carga sin mostrar la página WHOOPS
, entonces felicitaciones, ¡creamos con éxito un nuevo controlador de sesión! Pruébalo configurando algunos datos ficticios en la sesión, a través de Session::set()
y luego repitiéndolos a través de Session::get()
.
El componente Auth
El componente Auth de Laravel maneja la autenticación de usuarios para el framework, así como la administración de las contraseñas. Lo que hizo el componente Laravel aquí es crear una interpretación abstracta del sistema típico de administración de usuarios que se puede usar en la mayoría de las aplicaciones web, lo que a su vez ayuda al programador a implementar fácilmente un sistema de inicio de sesión. Al igual que el componente Session, también utiliza Laravel Manager. Actualmente, el componente Auth tiene controladores para:
-
eloquent
: esto hace uso del ORM incorporado de Laravel llamadoEloquent
. También utiliza la claseUser.php
prefabricada dentro de la carpetamodels
. -
database
: utiliza cualquier conexión de base de datos configurada de forma predeterminada. Hace uso de una claseGenericUser
para acceder a los datos del usuario.
Dado que sigue la misma implementación que el componente Session
, el proveedor de servicios es muy similar a lo que vimos en la parte superior:
1 |
/**
|
2 |
* Register the service provider.
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
public function register() |
7 |
{
|
8 |
$this->app->bindShared('auth', function($app) |
9 |
{
|
10 |
// Once the authentication service has actually been requested by the developer
|
11 |
// we will set a variable in the application indicating such. This helps us
|
12 |
// know that we need to set any queued cookies in the after event later.
|
13 |
$app['auth.loaded'] = true; |
14 |
|
15 |
return new AuthManager($app); |
16 |
});
|
17 |
}
|
Aquí, podemos ver que básicamente crea una clase AuthManager
que envuelve cualquier controlador que estemos usando, además de actuar como una fábrica para él. Dentro de AuthManager
, vuelve a crear el controlador adecuado, envuelto alrededor de una clase Guard
, que actúa de la misma manera que la clase Store
de Session
.
Creación de nuestro propio controlador de autenticación
Como antes, comencemos creando un MongoUserProvider
:
1 |
<?php namespace Illuminate\Auth; |
2 |
|
3 |
use Mongo; |
4 |
use Illuminate\Hashing\HasherInterface; |
5 |
|
6 |
class MongoUserProvider implements UserProviderInterface { |
7 |
|
8 |
/**
|
9 |
* The mongo instance
|
10 |
*
|
11 |
* @param \Mongo
|
12 |
*/
|
13 |
protected $connection; |
14 |
|
15 |
/**
|
16 |
* The mongo connection instance
|
17 |
*
|
18 |
* @param \MongoConnection
|
19 |
*/
|
20 |
protected $collection; |
21 |
|
22 |
/**
|
23 |
* The Mongo config array
|
24 |
*
|
25 |
* @var array
|
26 |
*/
|
27 |
protected $config; |
28 |
|
29 |
/**
|
30 |
* Create a new Mongo user provider.
|
31 |
*
|
32 |
* @param array $config
|
33 |
* - $config['host'] Mongodb host
|
34 |
* - $config['username'] Mongodb username
|
35 |
* - $config['password'] Mongodb password
|
36 |
* - $config['database'] Mongodb database
|
37 |
* - $config['collection'] Mongodb collection
|
38 |
* @return void
|
39 |
*/
|
40 |
public function __construct(array $config) |
41 |
{
|
42 |
$this->config = $config; |
43 |
|
44 |
$connection_string = 'mongodb://'; |
45 |
|
46 |
if (!empty($this->config['username']) && !empty($this->config['password'])) { |
47 |
$connection_string .= "{$this->config['user']}:{$this->config['password']}@"; |
48 |
}
|
49 |
|
50 |
$connection_string .= "{$this->config['host']}"; |
51 |
|
52 |
$this->connection = new Mongo($connection_string); |
53 |
|
54 |
$this->collection = $this->connection->selectCollection($this->config['database'], $this->config['collection']); |
55 |
}
|
56 |
|
57 |
/**
|
58 |
* Retrieve a user by their unique identifier.
|
59 |
*
|
60 |
* @param mixed $identifier
|
61 |
* @return \Illuminate\Auth\UserInterface|null
|
62 |
*/
|
63 |
public function retrieveById($identifier) |
64 |
{
|
65 |
$user_data = $this->collection->findOne(array( |
66 |
'_id' => $identifier, |
67 |
));
|
68 |
|
69 |
if (!is_null($user_data)) { |
70 |
return new GenericUser((array) $user_data); |
71 |
}
|
72 |
}
|
73 |
|
74 |
/**
|
75 |
* Retrieve a user by the given credentials.
|
76 |
*
|
77 |
* @param array $credentials
|
78 |
* @return \Illuminate\Auth\UserInterface|null
|
79 |
*/
|
80 |
public function retrieveByCredentials(array $credentials) |
81 |
{
|
82 |
// Attempt to look for the user first regardless of password
|
83 |
// We'll do that in the validateCredentials method
|
84 |
if (isset($credentials['password'])) { |
85 |
unset($credentials['password']); |
86 |
}
|
87 |
|
88 |
$user_data = $this->collection->findOne($credentials); |
89 |
|
90 |
if (!is_null($user_data)) { |
91 |
return new GenericUser((array) $user_data); |
92 |
}
|
93 |
}
|
94 |
|
95 |
/**
|
96 |
* Validate a user against the given credentials.
|
97 |
*
|
98 |
* @param \Illuminate\Auth\UserInterface $user
|
99 |
* @param array $credentials
|
100 |
* @return bool
|
101 |
*/
|
102 |
public function validateCredentials(UserInterface $user, array $credentials) |
103 |
{
|
104 |
if (!isset($credentials['password'])) { |
105 |
return false; |
106 |
}
|
107 |
|
108 |
return ($credentials['password'] === $user->getAuthPassword()); |
109 |
}
|
110 |
}
|
Es importante tener en cuenta aquí que no estoy comprobando una contraseña codificada, esto se hizo por simplicidad para que sea más fácil de nuestra parte crear datos ficticios y probarlos más adelante. En el código de producción, debes asegurarte de codificar la contraseña. Consulta la clase Illuminate\Auth\DatabaseUserProvider
para ver un gran ejemplo de cómo hacerlo.
Después, necesitamos registrar nuestra devolución de llamada de controlador personalizada en el AuthManager
. Para ello, necesitamos actualizar el método register
del proveedor de servicios:
1 |
/**
|
2 |
* Register the service provider.
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
public function register() |
7 |
{
|
8 |
$this->app->bindShared('auth', function($app) |
9 |
{
|
10 |
// Once the authentication service has actually been requested by the developer
|
11 |
// we will set a variable in the application indicating such. This helps us
|
12 |
// know that we need to set any queued cookies in the after event later.
|
13 |
$app['auth.loaded'] = true; |
14 |
|
15 |
$auth_manager = new AuthManager($app); |
16 |
|
17 |
$auth_manager->extend('mongo', function($app) { |
18 |
return new MongoUserProvider( |
19 |
array( |
20 |
'host' => $app['config']->get('auth.mongo.host'), |
21 |
'username' => $app['config']->get('auth.mongo.username'), |
22 |
'password' => $app['config']->get('auth.mongo.password'), |
23 |
'database' => $app['config']->get('auth.mongo.database'), |
24 |
'collection' => $app['config']->get('auth.mongo.collection') |
25 |
)
|
26 |
);
|
27 |
});
|
28 |
|
29 |
return $auth_manager; |
30 |
});
|
31 |
}
|
Por último, también necesitamos actualizar el archivo de configuración auth.php
para hacer uso del controlador Mongo, así como proporcionarle los valores de configuración adecuados de Mongo:
1 |
'driver' => 'mongo', |
2 |
...
|
3 |
...
|
4 |
...
|
5 |
/**
|
6 |
* Mongo DB settings
|
7 |
*/
|
8 |
'mongo' => array( |
9 |
'host' => '127.0.0.1', |
10 |
'username' => '', |
11 |
'password' => '', |
12 |
'database' => 'laravel', |
13 |
'collection' => 'laravel_auth_collection' |
14 |
)
|
Probar esto es un poco más complicado, para hacerlo, usa la CLI de Mongo DB para insertar un nuevo usuario en la colección:
1 |
mongo |
2 |
|
3 |
> use laravel_auth
|
4 |
switched to db laravel_auth |
5 |
> db.laravel_auth_collection.insert({id: 1, email:"nikko@nikkobautista.com", password:"test_password"}) |
6 |
> db.laravel_auth_collection.find() |
7 |
> { "_id" : ObjectId("530c609f2caac8c3a8e4814f"), "id" 1, "email" : "nikko@emailtest.com", "password" : "test_password" } |
Ahora, pruébalo con una llamada al método Auth::validate
:
1 |
var_dump(Auth::validate(array('email' => 'nikko@emailtest.com', 'password' => 'test_password'))); |
Esto debería arrojar un bool(true)
. Si lo hace, entonces ¡creamos con éxito nuestro propio controlador Auth!
El componente Caché
El componente Caché de Laravel controla los mecanismos de almacenamiento en caché para su uso en el framework. Al igual que los dos componentes que discutimos, también hace uso del Laravel Manager (¿estás notando un patrón?). El componente Caché tiene controladores para:
apc
memcached
redis
-
file
: una caché basada en archivos. Los datos se guardan en la ruta de accesoapp/storage/cache
. -
database
: caché basada en la base de datos. Los datos se guardan en filas en la base de datos. El esquema de la base de datos se describe en la documentación de Laravel. -
array
: los datos se "almacenan en caché" en una matriz. Ten en cuenta que la cachéarray
no es persistente y se borra en cada carga de la página.
Dado que esto sigue la misma implementación que ambos componentes que discutimos, puedes asumir con seguridad que el proveedor de servicios es bastante similar:
1 |
/**
|
2 |
* Register the service provider.
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
public function register() |
7 |
{
|
8 |
$this->app->bindShared('cache', function($app) |
9 |
{
|
10 |
return new CacheManager($app); |
11 |
});
|
12 |
|
13 |
$this->app->bindShared('cache.store', function($app) |
14 |
{
|
15 |
return $app['cache']->driver(); |
16 |
});
|
17 |
|
18 |
$this->app->bindShared('memcached.connector', function() |
19 |
{
|
20 |
return new MemcachedConnector; |
21 |
});
|
22 |
|
23 |
$this->registerCommands(); |
24 |
}
|
El método register()
aquí crea un CacheManager
, que nuevamente actúa como contenedor y fábrica para los controladores. Dentro del administrador, envuelve el controlador alrededor de una clase Repository
, similar a las clases Store
y Guard
.
Creación de nuestro propio controlador de caché
Crea MongoStore
, que debería extender Illuminate\Cache\StoreInterface
:
1 |
<?php namespace Illuminate\Cache; |
2 |
|
3 |
use Mongo; |
4 |
|
5 |
class MongoStore implements StoreInterface |
6 |
{
|
7 |
/**
|
8 |
* The mongo instance
|
9 |
*
|
10 |
* @param \Mongo
|
11 |
*/
|
12 |
protected $connection; |
13 |
|
14 |
/**
|
15 |
* The mongo connection instance
|
16 |
*
|
17 |
* @param \MongoConnection
|
18 |
*/
|
19 |
protected $collection; |
20 |
|
21 |
/**
|
22 |
* The Mongo config array
|
23 |
*
|
24 |
* @var array
|
25 |
*/
|
26 |
protected $config; |
27 |
|
28 |
/**
|
29 |
* Create a new Mongo cache store.
|
30 |
*
|
31 |
* @param array $config
|
32 |
* - $config['host'] Mongodb host
|
33 |
* - $config['username'] Mongodb username
|
34 |
* - $config['password'] Mongodb password
|
35 |
* - $config['database'] Mongodb database
|
36 |
* - $config['collection'] Mongodb collection
|
37 |
* @return void
|
38 |
*/
|
39 |
public function __construct(array $config) |
40 |
{
|
41 |
$this->config = $config; |
42 |
|
43 |
$connection_string = 'mongodb://'; |
44 |
|
45 |
if (!empty($this->config['username']) && !empty($this->config['password'])) { |
46 |
$connection_string .= "{$this->config['user']}:{$this->config['password']}@"; |
47 |
}
|
48 |
|
49 |
$connection_string .= "{$this->config['host']}"; |
50 |
|
51 |
$this->connection = new Mongo($connection_string); |
52 |
|
53 |
$this->collection = $this->connection->selectCollection($this->config['database'], $this->config['collection']); |
54 |
}
|
55 |
|
56 |
/**
|
57 |
* Retrieve an item from the cache by key.
|
58 |
*
|
59 |
* @param string $key
|
60 |
* @return mixed
|
61 |
*/
|
62 |
public function get($key) |
63 |
{
|
64 |
$cache_data = $this->getObject($key); |
65 |
|
66 |
if (!$cache_data) { |
67 |
return null; |
68 |
}
|
69 |
|
70 |
return unserialize($cache_data['cache_data']); |
71 |
}
|
72 |
|
73 |
/**
|
74 |
* Return the whole object instead of just the cache_data
|
75 |
*
|
76 |
* @param string $key
|
77 |
* @return array|null
|
78 |
*/
|
79 |
protected function getObject($key) |
80 |
{
|
81 |
$cache_data = $this->collection->findOne(array( |
82 |
'key' => $key, |
83 |
));
|
84 |
|
85 |
if (is_null($cache_data)) { |
86 |
return null; |
87 |
}
|
88 |
|
89 |
if (isset($cache_data['expire']) && time() >= $cache_data['expire']) { |
90 |
$this->forget($key); |
91 |
return null; |
92 |
}
|
93 |
|
94 |
return $cache_data; |
95 |
}
|
96 |
|
97 |
/**
|
98 |
* Store an item in the cache for a given number of minutes.
|
99 |
*
|
100 |
* @param string $key
|
101 |
* @param mixed $value
|
102 |
* @param int $minutes
|
103 |
* @return void
|
104 |
*/
|
105 |
public function put($key, $value, $minutes) |
106 |
{
|
107 |
$expiry = $this->expiration($minutes); |
108 |
|
109 |
$this->collection->update( |
110 |
array( |
111 |
'key' => $key |
112 |
),
|
113 |
array( |
114 |
'$set' => array( |
115 |
'cache_data' => serialize($value), |
116 |
'expiry' => $expiry, |
117 |
'ttl' => ($minutes * 60) |
118 |
)
|
119 |
),
|
120 |
array( |
121 |
'upsert' => true, |
122 |
'multiple' => false |
123 |
)
|
124 |
);
|
125 |
}
|
126 |
|
127 |
/**
|
128 |
* Increment the value of an item in the cache.
|
129 |
*
|
130 |
* @param string $key
|
131 |
* @param mixed $value
|
132 |
* @return void
|
133 |
*
|
134 |
* @throws \LogicException
|
135 |
*/
|
136 |
public function increment($key, $value = 1) |
137 |
{
|
138 |
$cache_data = $this->getObject($key); |
139 |
|
140 |
if (!$cache_data) { |
141 |
$new_data = array( |
142 |
'cache_data' => serialize($value), |
143 |
'expiry' => $this->expiration(0), |
144 |
'ttl' => $this->expiration(0) |
145 |
);
|
146 |
} else { |
147 |
$new_data = array( |
148 |
'cache_data' => serialize(unserialize($cache_data['cache_data']) + $value), |
149 |
'expiry' => $this->expiration((int) ($cache_data['ttl']/60)), |
150 |
'ttl' => $cache_data['ttl'] |
151 |
);
|
152 |
}
|
153 |
|
154 |
$this->collection->update( |
155 |
array( |
156 |
'key' => $key |
157 |
),
|
158 |
array( |
159 |
'$set' => $new_data |
160 |
),
|
161 |
array( |
162 |
'upsert' => true, |
163 |
'multiple' => false |
164 |
)
|
165 |
);
|
166 |
}
|
167 |
|
168 |
/**
|
169 |
* Decrement the value of an item in the cache.
|
170 |
*
|
171 |
* @param string $key
|
172 |
* @param mixed $value
|
173 |
* @return void
|
174 |
*
|
175 |
* @throws \LogicException
|
176 |
*/
|
177 |
public function decrement($key, $value = 1) |
178 |
{
|
179 |
$cache_data = $this->getObject($key); |
180 |
|
181 |
if (!$cache_data) { |
182 |
$new_data = array( |
183 |
'cache_data' => serialize((0 - $value)), |
184 |
'expiry' => $this->expiration(0), |
185 |
'ttl' => $this->expiration(0) |
186 |
);
|
187 |
} else { |
188 |
$new_data = array( |
189 |
'cache_data' => serialize(unserialize($cache_data['cache_data']) - $value), |
190 |
'expiry' => $this->expiration((int) ($cache_data['ttl']/60)), |
191 |
'ttl' => $cache_data['ttl'] |
192 |
);
|
193 |
}
|
194 |
|
195 |
$this->collection->update( |
196 |
array( |
197 |
'key' => $key |
198 |
),
|
199 |
array( |
200 |
'$set' => $new_data |
201 |
),
|
202 |
array( |
203 |
'upsert' => true, |
204 |
'multiple' => false |
205 |
)
|
206 |
);
|
207 |
}
|
208 |
|
209 |
/**
|
210 |
* Store an item in the cache indefinitely.
|
211 |
*
|
212 |
* @param string $key
|
213 |
* @param mixed $value
|
214 |
* @return void
|
215 |
*/
|
216 |
public function forever($key, $value) |
217 |
{
|
218 |
return $this->put($key, $value, 0); |
219 |
}
|
220 |
|
221 |
/**
|
222 |
* Remove an item from the cache.
|
223 |
*
|
224 |
* @param string $key
|
225 |
* @return void
|
226 |
*/
|
227 |
public function forget($key) |
228 |
{
|
229 |
$this->collection->remove(array( |
230 |
'key' => $key |
231 |
));
|
232 |
}
|
233 |
|
234 |
/**
|
235 |
* Remove all items from the cache.
|
236 |
*
|
237 |
* @return void
|
238 |
*/
|
239 |
public function flush() |
240 |
{
|
241 |
$this->collection->remove(); |
242 |
}
|
243 |
|
244 |
/**
|
245 |
* Get the expiration time based on the given minutes.
|
246 |
*
|
247 |
* @param int $minutes
|
248 |
* @return int
|
249 |
*/
|
250 |
protected function expiration($minutes) |
251 |
{
|
252 |
if ($minutes === 0) return 9999999999; |
253 |
|
254 |
return time() + ($minutes * 60); |
255 |
}
|
256 |
|
257 |
/**
|
258 |
* Get the cache key prefix.
|
259 |
*
|
260 |
* @return string
|
261 |
*/
|
262 |
public function getPrefix() |
263 |
{
|
264 |
return ''; |
265 |
}
|
266 |
}
|
También necesitaremos agregar la devolución de llamada de Mongo nuevamente al administrador:
1 |
/**
|
2 |
* Register the service provider.
|
3 |
*
|
4 |
* @return void
|
5 |
*/
|
6 |
public function register() |
7 |
{
|
8 |
$this->app->bindShared('cache', function($app) |
9 |
{
|
10 |
$cache_manager = new CacheManager($app); |
11 |
|
12 |
$cache_manager->extend('mongo', function($app) { |
13 |
return new MongoStore( |
14 |
array( |
15 |
'host' => $app['config']->get('cache.mongo.host'), |
16 |
'username' => $app['config']->get('cache.mongo.username'), |
17 |
'password' => $app['config']->get('cache.mongo.password'), |
18 |
'database' => $app['config']->get('cache.mongo.database'), |
19 |
'collection' => $app['config']->get('cache.mongo.collection') |
20 |
)
|
21 |
);
|
22 |
});
|
23 |
|
24 |
return $cache_manager; |
25 |
});
|
26 |
|
27 |
$this->app->bindShared('cache.store', function($app) |
28 |
{
|
29 |
return $app['cache']->driver(); |
30 |
});
|
31 |
|
32 |
$this->app->bindShared('memcached.connector', function() |
33 |
{
|
34 |
return new MemcachedConnector; |
35 |
});
|
36 |
|
37 |
$this->registerCommands(); |
38 |
}
|
Por último, necesitaremos actualizar el archivo de configuración cache.php
:
1 |
'driver' => 'mongo', |
2 |
...
|
3 |
...
|
4 |
...
|
5 |
/**
|
6 |
* Mongo DB settings
|
7 |
*/
|
8 |
'mongo' => array( |
9 |
'host' => '127.0.0.1', |
10 |
'username' => '', |
11 |
'password' => '', |
12 |
'database' => 'laravel', |
13 |
'collection' => 'laravel_cache_collection' |
14 |
)
|
Ahora, intenta utilizar los métodos Cache::put()
y Cache::get()
. Si se hace correctamente, ¡deberíamos poder usar MongoDB para almacenar los datos en la caché!
Conclusión
En este tutorial, aprendimos sobre lo siguiente:
- El sistema basado en componentes de Laravel llamado
Illuminate
, que es utilizado por el framework de Laravel. - Los proveedores de servicios de Laravel y un poco sobre cómo funcionan.
- El sistema Manager de Laravel, que actúa como contenedor y fábrica para los controladores.
- Los componentes Session, Auth y Caché y cómo crear nuevos controladores para cada uno.
- Las bibliotecas Store, Guard y Repository que utilizan estos controladores.
Con suerte, esto ayudará a los programadores a crear sus propios controladores y ampliar la funcionalidad actual del framework de Laravel.