php - ventajas - recargar clases en laravel
MVC(Laravel) donde agregar lógica (4)
Digamos que cada vez que hago una operación CRUD o modifico una relación de una manera específica, también quiero hacer otra cosa. Por ejemplo, cada vez que alguien publica una publicación, también quiero guardar algo en una tabla para análisis. Tal vez no sea el mejor ejemplo, pero en general hay una gran cantidad de esta funcionalidad "agrupada".
Normalmente veo este tipo de lógica puesta en los controladores. Eso está muy bien, dandi hasta que quieras reproducir esta funcionalidad en muchos lugares. Cuando comienzas a participar en parciales, creas una API y generas contenido falso, se convierte en un problema para mantener las cosas SECAS.
Las formas que he visto para gestionar esto son eventos, repositorios, bibliotecas y agregar modelos. Aquí están mis entendimientos de cada uno:
Servicios: aquí es donde la mayoría de la gente probablemente colocaría este código. Mi principal problema con los servicios es que a veces es difícil encontrar una funcionalidad específica en ellos y siento que se olvidan de cuándo las personas se centran en el uso de Eloquent. ¿Cómo puedo saber que necesito llamar a un método publishPost()
en una biblioteca cuando puedo hacer $post->is_published = 1
?
La única condición en la que veo que esto funciona bien es si SOLAMENTE utilizas los servicios (y lo ideal es hacer que Eloquent sea inaccesible de algún modo por los controladores).
En última instancia, parece que esto solo crearía un montón de archivos adicionales innecesarios si sus solicitudes generalmente siguen la estructura de su modelo.
Repositorios: por lo que entiendo, esto es básicamente como un servicio, pero hay una interfaz para que pueda cambiar entre ORM, que no necesito.
Eventos: Veo esto como el sistema más elegante en cierto sentido porque sabes que los eventos de tu modelo siempre serán llamados en métodos Eloquentes, para que puedas escribir tus controladores como lo harías normalmente. Sin embargo, puedo ver que estos se vuelven complicados y si alguien tiene ejemplos de proyectos grandes que usan eventos para acoplamiento crítico, me gustaría verlos.
Modelos: Tradicionalmente tendría clases que realizaban CRUD y también manejaba el acoplamiento crítico. Esto realmente facilitó las cosas porque sabía que toda la funcionalidad en torno a CRUD + lo que tenía que hacerse con ella estaba allí.
Simple, pero en la arquitectura MVC esto normalmente no es lo que veo hecho. En cierto sentido, prefiero esto a los servicios, ya que es un poco más fácil de encontrar, y hay menos archivos para realizar un seguimiento. Aunque puede ser un poco desorganizado. Me gustaría escuchar las caídas de este método y por qué la mayoría de la gente parece no hacerlo.
¿Cuáles son las ventajas / desventajas de cada método? ¿Me estoy perdiendo de algo?
Creo que todos los patrones / arquitecturas que presentas son muy útiles siempre y cuando sigas los principios SOLID .
Para saber dónde agregar lógica , creo que es importante referirse al Principio de Responsabilidad Individual . Además, mi respuesta considera que estás trabajando en un proyecto mediano / grande. Si se trata de un proyecto de tirar algo en una página , olvida esta respuesta y agrégala a los controladores o modelos.
La respuesta corta es: Donde tiene sentido para usted (con servicios) .
La respuesta larga:
Controladores : ¿Cuál es la responsabilidad de los controladores? Claro, puedes poner toda tu lógica en un controlador, pero ¿esa es la responsabilidad del controlador? No lo creo.
Para mí, el controlador debe recibir una solicitud y devolver datos y este no es el lugar para poner validaciones, llamar a métodos de db, etc.
Modelos : ¿Es este un buen lugar para agregar lógica, como enviar un correo electrónico de bienvenida cuando un usuario se registra o actualiza el conteo de votos de una publicación? ¿Qué sucede si necesita enviar el mismo correo electrónico desde otro lugar en su código? ¿Creas un método estático? ¿Qué pasa si ese correo electrónico necesita información de otro modelo?
Creo que el modelo debería representar una entidad. Con Laravel, solo uso la clase de modelo para agregar elementos como fillable
, guarded
, table
y las relaciones (esto es porque utilizo el patrón de repositorio, de lo contrario, el modelo también tendría los métodos de save
, update
, find
, etc.).
Repositorios (Patrón de repositorio) : Al principio estaba muy confundido por esto. Y, como usted, pensé "bueno, yo uso MySQL y eso es todo".
Sin embargo, he equilibrado los pros y los contras del uso del Patrón de repositorio y ahora lo uso. Creo que ahora , en este momento, solo necesitaré usar MySQL. Pero, si dentro de tres años tengo que cambiar a algo así como MongoDB, la mayor parte del trabajo ya está hecho. Todo a expensas de una interfaz adicional y $app->bind(«interface», «repository»)
.
Eventos ( patrón de observador ): los eventos son útiles para cosas que se pueden lanzar en cualquier clase en cualquier momento dado. Piense, por ejemplo, en enviar notificaciones a un usuario. Cuando lo necesite, active el evento para enviar una notificación a cualquier clase de su aplicación. Luego, puede tener una clase como UserNotificationEvents
que maneja todos sus eventos disparados para las notificaciones de los usuarios.
Servicios : Hasta ahora, tiene la opción de agregar lógica a los controladores o modelos. Para mí, tiene todo el sentido agregar la lógica dentro de los Servicios . Reconozcámoslo, Servicios es un nombre elegante para las clases. Y puede tener tantas clases como tenga sentido para usted dentro de su aplicación.
Tomemos este ejemplo: Hace poco, desarrollé algo así como Google Forms. Empecé con CustomFormService
y terminé con CustomFormService
, CustomFormRender
, CustomFieldService
, CustomFieldRender
, CustomAnswerService
y CustomAnswerRender
. ¿Por qué? Porque tiene sentido para mí. Si trabajas con un equipo, debes poner tu lógica donde tenga sentido para el equipo.
La ventaja de usar Servicios vs Controladores / Modelos es que no está restringido por un solo Controlador o un Modelo único. Puede crear tantos servicios como necesite en función del diseño y las necesidades de su aplicación. Agregue a eso la ventaja de llamar a un Servicio dentro de cualquier clase de su aplicación.
Esto es largo, pero me gustaría mostrarte cómo he estructurado mi aplicación:
app/
controllers/
MyCompany/
Composers/
Exceptions/
Models/
Observers/
Sanitizers/
ServiceProviders/
Services/
Validators/
views
(...)
Yo uso cada carpeta para una función específica. Por ejemplo, el directorio Validators
contiene una clase BaseValidator
responsable de procesar la validación, basada en las $rules
$messages
y $messages
de validadores específicos (generalmente uno para cada modelo). Podría fácilmente poner este código dentro de un Servicio, pero tiene sentido para mí tener una carpeta específica para esto incluso si solo se usa dentro del servicio (por ahora).
Te recomiendo que leas los siguientes artículos, ya que pueden explicarte las cosas un poco mejor:
Rompiendo el molde por Dayle Rees (autor de CodeBright): Aquí es donde lo puse todo junto, aunque cambié algunas cosas para satisfacer mis necesidades.
Desacoplando su código en Laravel utilizando repositorios y servicios por Chris Goosey: Esta publicación explica bien qué es un Servicio y el Patrón de Repositorio y cómo encajan.
Laracasts también tiene los Repositorios Simplificados y la Responsabilidad Individual, que son buenos recursos con ejemplos prácticos (aunque tenga que pagar).
En mi opinión, Laravel ya tiene muchas opciones para que guardes tu lógica comercial.
Respuesta corta:
- Utilice los objetos de
Request
de Laravel para validar automáticamente su entrada, y luego persista los datos en la solicitud (cree el modelo). Dado que la entrada de todos los usuarios está directamente disponible en la solicitud, creo que tiene sentido realizar esto aquí. - Use los objetos de
Job
de laravel para realizar tareas que requieren componentes individuales, luego simplemente envíelos. Creo que las clases de servicios abarcan aJob
. Realizan una tarea, como la lógica comercial.
Respuesta larga (er):
Utilice los repositorios cuando sea necesario: los repositorios están sujetos a una sobrerbolidación, y la mayoría de las veces, simplemente se utilizan como un accessor
al modelo. Siento que definitivamente tienen algún uso, pero a menos que estés desarrollando una aplicación masiva que requiera esa cantidad de flexibilidad para que puedas deshacerte del resto, mantente alejado de los repositorios. Te agradecerás más tarde y tu código será mucho más directo.
Pregúntese si existe la posibilidad de que vaya a cambiar los marcos de PHP o a un tipo de base de datos que laravel no admita.
Si su respuesta es "Probablemente no", entonces no implemente el patrón de repositorio.
Además de lo anterior, por favor no golpee un patrón sobre un excelente ORM como Eloquent. Simplemente agrega complejidad que no es necesaria y no le beneficiará en absoluto.
Utilizar los servicios con moderación: las clases de servicio para mí son solo un lugar para almacenar la lógica de negocios para realizar una tarea específica con sus dependencias dadas. Laravel tiene estos listos para usar, llamados ''Jobs'', y tienen mucha más flexibilidad que una clase de servicio personalizada.
Siento que Laravel tiene una solución completa para el problema de la lógica de MVC
. Es solo una cuestión u organización.
Ejemplo:
Solicitud :
namespace App/Http/Requests;
use App/Post;
use App/Jobs/PostNotifier;
use App/Events/PostWasCreated;
use App/Http/Requests/Request;
class PostRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
''title'' => ''required'',
''description'' => ''required''
];
}
/**
* Save the post.
*
* @param Post $post
*
* @return bool
*/
public function persist(Post $post)
{
if (!$post->exists) {
// If the post doesn''t exist, we''ll assign the
// post as created by the current user.
$post->user_id = auth()->id();
}
$post->title = $this->title;
$post->description = $this->description;
// Perform other tasks, maybe fire an event, dispatch a job.
if ($post->save()) {
// Maybe we''ll fire an event here that we can catch somewhere else that
// needs to know when a post was created.
event(new PostWasCreated($post));
// Maybe we''ll notify some users of the new post as well.
dispatch(new PostNotifier($post));
return true;
}
return false;
}
}
Controlador :
namespace App/Http/Controllers;
use App/Post;
use App/Http/Requests/PostRequest;
class PostController extends Controller
{
/**
* Creates a new post.
*
* @return string
*/
public function store(PostRequest $request)
{
if ($request->persist(new Post())) {
flash()->success(''Successfully created new post!'');
} else {
flash()->error(''There was an issue creating a post. Please try again.'');
}
return redirect()->back();
}
/**
* Updates a post.
*
* @return string
*/
public function update(PostRequest $request, $id)
{
$post = Post::findOrFail($id);
if ($request->persist($post)) {
flash()->success(''Successfully updated post!'');
} else {
flash()->error(''There was an issue updating this post. Please try again.'');
}
return redirect()->back();
}
}
En el ejemplo anterior, la entrada de solicitud se valida automáticamente, y todo lo que tenemos que hacer es llamar al método de persistencia y pasar una nueva publicación. Creo que la legibilidad y la mantenibilidad siempre deben superar patrones de diseño complejos e innecesarios.
A continuación, puede utilizar exactamente el mismo método de persistencia para actualizar las publicaciones, ya que podemos verificar si la publicación ya existe y realizar una lógica alterna cuando sea necesario.
Lo que uso para crear la lógica entre controladores y modelos es crear una capa de servicio . Básicamente, este es mi flujo para cualquier acción dentro de mi aplicación:
- El controlador obtiene la acción solicitada por el usuario y los parámetros enviados, y delega todo en una clase de servicio.
- La clase de servicio hace toda la lógica relacionada con la operación: validación de entrada, registro de eventos, operaciones de bases de datos, etc.
- El modelo contiene información de campos, transformación de datos y definiciones de validaciones de atributos.
Así es como lo hago:
Este es el método de un controlador para crear algo:
public function processCreateCongregation()
{
// Get input data.
$congregation = new Congregation;
$congregation->name = Input::get(''name'');
$congregation->address = Input::get(''address'');
$congregation->pm_day_of_week = Input::get(''pm_day_of_week'');
$pmHours = Input::get(''pm_datetime_hours'');
$pmMinutes = Input::get(''pm_datetime_minutes'');
$congregation->pm_datetime = Carbon::createFromTime($pmHours, $pmMinutes, 0);
// Delegates actual operation to service.
try
{
CongregationService::createCongregation($congregation);
$this->success(trans(''messages.congregationCreated''));
return Redirect::route(''congregations.list'');
}
catch (ValidationException $e)
{
// Catch validation errors thrown by service operation.
return Redirect::route(''congregations.create'')
->withInput(Input::all())
->withErrors($e->getValidator());
}
catch (Exception $e)
{
// Catch any unexpected exception.
return $this->unexpected($e);
}
}
Esta es la clase de servicio que hace la lógica relacionada con la operación:
public static function createCongregation(Congregation $congregation)
{
// Log the operation.
Log::info(''Create congregation.'', compact(''congregation''));
// Validate data.
$validator = $congregation->getValidator();
if ($validator->fails())
{
throw new ValidationException($validator);
}
// Save to the database.
$congregation->created_by = Auth::user()->id;
$congregation->updated_by = Auth::user()->id;
$congregation->save();
}
Y este es mi modelo:
class Congregation extends Eloquent
{
protected $table = ''congregations'';
public function getValidator()
{
$data = array(
''name'' => $this->name,
''address'' => $this->address,
''pm_day_of_week'' => $this->pm_day_of_week,
''pm_datetime'' => $this->pm_datetime,
);
$rules = array(
''name'' => [''required'', ''unique:congregations''],
''address'' => [''required''],
''pm_day_of_week'' => [''required'', ''integer'', ''between:0,6''],
''pm_datetime'' => [''required'', ''regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/''],
);
return Validator::make($data, $rules);
}
public function getDates()
{
return array_merge_recursive(parent::getDates(), array(
''pm_datetime'',
''cbs_datetime'',
));
}
}
Para obtener más información sobre este método, utilizo para organizar mi código para una aplicación Laravel: https://github.com/rmariuzzo/Pitimi
Quería publicar una respuesta a mi propia pregunta. Podría hablar sobre esto durante días, pero intentaré publicar esto rápidamente para asegurarme de que lo haga.
Terminé utilizando la estructura existente que proporciona Laravel, lo que significa que mantuve mis archivos principalmente como Modelo, Vista y Controlador. También tengo una carpeta de Bibliotecas para componentes reutilizables que no son realmente modelos.
NO ENVOLVí MIS MODELOS EN SERVICIOS / BIBLIOTECAS . Todas las razones proporcionadas no me convencieron al 100% del beneficio de usar servicios. Si bien puedo estar equivocado, por lo que puedo ver, resultan en toneladas de archivos casi vacíos que necesito crear y alternar entre trabajar con modelos y también reducir el beneficio de usar elocuentes (especialmente cuando se trata de modelos de RECUPERACIÓN , por ejemplo, usando paginación, ámbitos, etc.).
Puse la lógica de negocios EN LOS MODELOS y accedí elocuentemente directamente desde mis controladores. Utilizo una serie de enfoques para asegurarme de que la lógica comercial no se omita:
- Accesorios y mutadores: Laravel tiene excelentes accesores y mutadores. Si quiero realizar una acción cada vez que una publicación se mueve de borrador a publicado, puedo llamarlo creando la función setIsPublishedAttribute e incluyendo la lógica allí
- Anulación Crear / Actualizar, etc.: siempre puede anular los métodos Eloquent en sus modelos para incluir funcionalidad personalizada. De esa forma puede llamar a la funcionalidad en cualquier operación CRUD. Editar: creo que hay un error al anular la creación en las versiones más nuevas de Laravel (así que uso eventos ahora registrados en el arranque)
- Validación: engancho mi validación de la misma manera, por ej., Ejecutaré la validación anulando las funciones CRUD y también los accedores / mutadores si es necesario. Consulte Esensi o dwightwatson / validating para obtener más información.
- Métodos Mágicos: Utilizo los métodos __get y __set de mis modelos para engancharme a la funcionalidad donde sea apropiado
- Extendiendo Eloquent: si hay una acción que desea realizar en todas las actualizaciones / creaciones, puede extender elocuentes y aplicarla a varios modelos.
- Eventos: Este es un lugar directo y generalmente acordado para hacer esto también. La mayor desventaja con los eventos, creo, es que las excepciones son difíciles de rastrear (podría no ser el nuevo caso con el nuevo sistema de eventos de Laravel). También me gusta agrupar mis eventos por lo que hacen en lugar de cuando se llaman ... por ejemplo, tienen un suscriptor de MailSender que escucha los eventos que envían correos.
- Agregando Pivot / BelongsToMany Eventos: Una de las cosas con las que luché durante más tiempo fue cómo vincular el comportamiento a la modificación de las relaciones de belongsToMany. Por ejemplo, realizar una acción cada vez que un usuario se une a un grupo. Casi termino de pulir una biblioteca personalizada para esto. Aún no lo publiqué, ¡pero funciona! Intentaremos publicar un enlace pronto. EDITAR Terminé haciendo todos mis pivotes en modelos normales y mi vida ha sido mucho más fácil ...
Abordar las preocupaciones de las personas con el uso de modelos:
- Organización: Sí, si incluyes más lógica en los modelos, pueden ser más largos, pero, en general, he descubierto que el 75% de mis modelos todavía son bastante pequeños. Si elijo organizar los más grandes, puedo hacerlo usando rasgos (por ejemplo, crear una carpeta para el modelo con algunos archivos más como PostScopes, PostAccessors, PostValidation, etc., según sea necesario). Sé que esto no es necesariamente para qué sirven los rasgos, pero este sistema funciona sin problemas.
Nota adicional: tengo ganas de envolver a sus modelos en los servicios, es como tener una navaja suiza con muchas herramientas y construir otro cuchillo que básicamente haga lo mismo. Sí, a veces es posible que quieras pegar con cinta adhesiva una cuchilla o asegurarte de que se usen dos cuchillas juntas ... pero normalmente hay otras formas de hacerlo ...
CUÁNDO USAR LOS SERVICIOS : Este artículo articula muy buenos GRANDES ejemplos de cuándo usar los servicios ( pista: no es muy frecuente ). Él dice básicamente que cuando su objeto usa múltiples modelos o modelos en partes extrañas de su ciclo de vida , tiene sentido. justinweiss.com/articles/where-do-you-put-your-code