Pensé en tomar una grieta en responder a mi propia pregunta. Lo que sigue es sólo una manera de resolver los problemas 1-3 en mi pregunta original.
Descargo de responsabilidad: yo no puede utilizar siempre el derecho de los términos para la descripción de patrones o técnicas. Lo siento por eso.
Los Objetivos:
- Crear un ejemplo completo de un controlador básico para la visualización y edición de
Users
.
- Todo el código debe estar totalmente comprobables y mockable.
- El controlador debe tener ni idea de dónde se almacenan los datos (lo que significa que puede ser cambiado).
- Ejemplo para mostrar una implementación de SQL (la más común).
- Para obtener el máximo rendimiento, los controladores deben recibir únicamente los datos que necesita, ni de los campos adicionales.
- La aplicación debe aprovechar algún tipo de datos del asignador para facilitar su desarrollo.
- La aplicación debe tener la capacidad de llevar a cabo complejas de datos de búsquedas.
La Solución
Estoy dividiendo mi persistente de almacenamiento (base de datos) la interacción en dos categorías: R (Lectura) y de la COALICIÓN (Crear, Actualizar, Eliminar). Mi experiencia ha sido que lee son realmente lo que hace que una aplicación para frenar. Y mientras manipulación de datos (CUD) es realmente más lento, esto ocurre con mucha menos frecuencia, y es por lo tanto mucho menos de una preocupación.
CUD (Crear, Actualizar, Eliminar) es muy fácil. Esto implica trabajar con los modelos, que están a continuación, se pasa a mis Repositories
de persistencia. Tenga en cuenta que mi repositorios proporcionará un método de Lectura, sino, simplemente, para la creación de objetos, no de la pantalla. Más sobre esto más adelante.
R (Read) no es tan fácil. No hay modelos de aquí, sólo objetos de valor. El uso de matrices si lo prefiere. Estos objetos pueden representar un modelo o una mezcla de muchos modelos, cualquier cosa realmente. Estos no son muy interesantes por su propia cuenta, sino cómo se generan. Estoy usando lo que yo estoy llamando Query Objects
.
El Código:
Modelo De Usuario
Vamos a comenzar por lo más fácil con nuestra base de modelo de usuario. Nota que no es un ORM, la ampliación o la base de datos de cosas en todos. Sólo pura modelo de gloria. Agregue sus getters, setters, validación, lo que sea.
class User
{
public $id;
public $first_name;
public $last_name;
public $gender;
public $email;
public $password;
}
Repositorio De Interfaz
Antes de crear mi repositorio de usuarios, quiero crear mi repositorio de interfaz. Esto va a definir el "contrato" que los repositorios deben seguir para ser utilizado por mi controlador. Recuerde, mi controlador no se sabe donde los datos se almacenan realmente.
Tenga en cuenta que mi repositorios sólo cada contienen estos tres métodos. La save()
método es responsable de la creación y actualización de los usuarios, simplemente dependiendo de si o no el objeto de usuario tiene un id de conjunto.
interface UserRepositoryInterface
{
public function find($id);
public function save(User $user);
public function remove(User $user);
}
SQL Repositorio de la Aplicación
Ahora a crear mi implementación de la interfaz. Como se ha mencionado, mi ejemplo de que iba a ser con una base de datos SQL. Nota el uso de un data mapper para evitar tener que escribir repetitivo de consultas SQL.
class SQLUserRepository implements UserRepositoryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function find($id)
{
// Find a record with the id = $id
// from the 'users' table
// and return it as a User object
return $this->db->find($id, 'users', 'User');
}
public function save(User $user)
{
// Insert or update the $user
// in the 'users' table
$this->db->save($user, 'users');
}
public function remove(User $user)
{
// Remove the $user
// from the 'users' table
$this->db->remove($user, 'users');
}
}
Consulta De Objetos De Interfaz
Ahora con la CUD (Crear, Actualizar, Eliminar) al cuidado de nuestro repositorio, podemos centrarnos en el R (Read). Consulta los objetos son simplemente una encapsulación de algún tipo de búsqueda de datos lógica. Ellos son los que no se consulta a los constructores. Mediante la abstracción como nuestro repositorio podemos cambiar es la implementación y prueba de ello es más fácil. Un ejemplo de un Objeto de Consulta podría ser un AllUsersQuery
o AllActiveUsersQuery
, o incluso, MostCommonUserFirstNames
.
Usted puede estar pensando "no puedo crear métodos en mis repositorios para esas consultas?" Sí, pero aquí es ¿por qué no voy a hacer esto:
- Mi repositorios están diseñados para trabajar con los objetos del modelo. En un mundo real de la aplicación, ¿por qué yo nunca la necesidad de obtener el
password
campo si estoy buscando a la lista de todos mis usuarios?
- Los repositorios son a menudo de un modelo específico, sin embargo, las consultas a menudo involucran a más de un modelo. Entonces, ¿qué repositorio ¿su método?
- Esto mantiene mi repositorios muy simple-no es una hinchada clase de métodos.
- Todas las consultas están ahora organizados en sus propias clases.
- Realmente, en este punto, los repositorios existen simplemente para abstraer mi capa de base de datos.
Para mi ejemplo voy a crear un objeto de consulta para la búsqueda de "Todos". Aquí está la interfaz:
interface AllUsersQueryInterface
{
public function fetch($fields);
}
Consulta La Implementación De Objeto
Aquí es donde podemos usar los datos del asignador de nuevo para ayudar a acelerar el desarrollo. Aviso que estoy permitiendo un tweak para el conjunto de datos devuelto-los campos. Esto es lo que quiero ir con la manipulación de la consulta. Recuerde, mi consulta objetos no son generadores de consultas. Ellos simplemente realizar una consulta específica. Sin embargo, ya sé que probablemente voy a utilizar mucho, en un número de situaciones diferentes, se la voy a dar yo la capacidad de especificar los campos. Nunca quiero volver campos no necesito!
class AllUsersQuery implements AllUsersQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch($fields)
{
return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
}
}
Antes de pasar a la controladora, quiero mostrar otro ejemplo para ilustrar cuán poderosa es. Tal vez tengo un motor de generación de informes y la necesidad de crear un informe para AllOverdueAccounts
. Esto puede ser difícil con mis datos mapper, y yo podría escribir algunas real SQL
en esta situación. No hay problema, aquí, es lo que este objeto de consulta podría parecerse a:
class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch()
{
return $this->db->query($this->sql())->rows();
}
public function sql()
{
return "SELECT...";
}
}
Esta bien mantiene toda mi lógica para este informe en una clase, y es fácil de probar. Me puede burlarse de que el contenido de mi corazón, o incluso el uso de una implementación diferente por completo.
El Controlador De
Ahora la parte divertida-traer todas las piezas juntas. Tenga en cuenta que yo soy el uso de la inyección de dependencia. Normalmente las dependencias se inyecta en el constructor, pero yo en realidad prefiero que se inyectan directamente en los métodos de controlador (rutas). Esto minimiza el controlador del objeto gráfico, y de hecho, me parece más legible. Nota, si no te gusta este enfoque, sólo tiene que utilizar el tradicional método constructor.
class UsersController
{
public function index(AllUsersQueryInterface $query)
{
// Fetch user data
$users = $query->fetch(['first_name', 'last_name', 'email']);
// Return view
return Response::view('all_users.php', ['users' => $users]);
}
public function add()
{
return Response::view('add_user.php');
}
public function insert(UserRepositoryInterface $repository)
{
// Create new user model
$user = new User;
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the new user
$repository->save($user);
// Return the id
return Response::json(['id' => $user->id]);
}
public function view(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('view_user.php', ['user' => $user]);
}
public function edit(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('edit_user.php', ['user' => $user]);
}
public function update(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Update the user
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the user
$repository->save($user);
// Return success
return true;
}
public function delete(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Delete the user
$repository->delete($user);
// Return success
return true;
}
}
Pensamientos Finales:
Lo importante a destacar aquí que cuando estoy modificando (crear, actualizar o eliminar) las entidades que, estoy trabajando con el modelo real de los objetos, y la realización de la persistencia a través de mi repositorios.
Sin embargo, cuando estoy mostrar (selección de los datos y su envío a los puntos de vista), no estoy trabajando con el modelo de objetos, sino el viejo y simple objetos de valor. Yo sólo seleccionar los campos que necesitamos, y está diseñado de manera que puedo máximo mi búsqueda de datos de rendimiento.
Mi repositorios estancia muy limpio, y en lugar de este "lío" está organizado en mi modelo de las consultas.
Yo uso un data mapper para ayudar con el desarrollo, como es simplemente ridículo para escribir repetitivo SQL para tareas comunes. Sin embargo, usted absolutamente puede escribir SQL donde sea necesario (complicado consultas, informes, etc.). Y cuando se hace, muy bien escondido a un nombre de la clase.
Me encantaría escuchar su opinión sobre mi enfoque!