I was wondering how most developers use this two Laravel tools.
In Laravel, you can handle business logic with Services, or with Jobs (let's talk only about not-queueable jobs, only those ones that run in the same process).
For example, an user wants to create an entity, let's say a Book, you can handle the entity creation with a service or dispatching a job.
Using a job it would be something like this:
class PostBook extends Job
{
...
public function handle(Book $bookEntity)
{
// Business logic here.
}
...
}
class BooksController extends Controller
{
public function store(Request $request)
{
...
dispatch(new PostBook($request->all()));
...
}
}
Using a service, it would be something like this:
class BookService
{
public function store(Request $request)
{
// Business logic here.
}
}
class BooksController extends Controller
{
public function store(Request $request)
{
...
// I could inject the service instead.
$bookService = $this->app()->make(App\Services\BookService::class);
$bookService->store($request);
...
}
}
The question is, how do you mostly choose to use one way or the other one? And why?
Surely there are two "schools" in this matter, but I would want to understand pros & cons of each one.
"Business logic" can be handled with anything, so it seems like what's really being asked is which option is better for repeating the same business logic without repeating code.
A Job class typically does one thing, as defined by its handle() method. It's difficult to exclude queued jobs from the comparison because running them synchronously usually defeats the purpose, which is to handle slow, expensive, or unreliable actions (such as calling a web API) after the current request has been completed and a response has been shown to the user.
If all jobs were expected to be synchronous, it wouldn't be much different than defining a function for your business logic. That's actually very close to what dispatching a synchronous job does: Somewhere down the call stack it ends up running call_user_func([$job, 'handle']) to invoke a single method on your job object. More importantly, a synchronous job lacks the mechanism for retrying jobs that might have failed due to external causes such as network failures.
Services, on the other hand, are an easy way to encapsulate the logic around a component, and they may do more than one thing. In this context, a component may be thought of as a piece of your application that could be swapped out for a different implementation without having to modify code that was using it. A perfect example, included in the framework, is the Filesystem service (most commonly accessed with the Storage facade).
Consider if you didn't store books by inserting them into your database, but instead by posting to an external API. You may have a BookRepository service that not only has a store() method, but also has a get(), update(), list(), delete(), or any number of other methods. All of these requests share logic for authenticating to the external web service (like adding headers to requests), and your BookRepository class can encapsulate that re-usable logic. You can use this service class inside of scheduled artisan commands, web controllers, api controllers, jobs, middleware, etc. — without repeating code.
Using this example, you might create a Job for storing a new book so you don't make users wait when there are slow API responses (and it can retry when there are failures). Internally, your job calls your Service's store() method when it runs. The work done by the service is scheduled by the job.
Related
Note: I refer to the Symfony Console component quite a lot in my question, but I think this question could be considered broader if thought about in the context of any user interface.
I am using the Symfony Console component to create a console application. I am trying to keep class coupling to a minimum, as it makes unit testing easier and is generally better practice.
My app has some processes which may take some time to complete, so I want to keep the user informed as to what is happening using progress bars and general text output as it runs. Symfony requires an instance of the Symfony Console OutputInterface to be passed to any command's execute method. So far so good; I can create progress bars and output text as I please. However, all of the heavy lifting of my app doesn't happen in the commands' execute methods and is instead within the core classes of my application. These classes shouldn't and don't know they are being used in a console application.
I am struggling to keep it this way because I don't know how to provide feedback to the console (or whatever user interface) without injecting the output class into my core. Doing so would result in tight coupling between the console output class and my application core. I have thought about using an event dispatcher (Symfony has one), but that too means my core will be coupled with the dispatcher (maybe that's fine). Ideally, I need to sort of "bubble" my application state back up to the execute method of the invoked command, where I can then perform output.
Could someone point me in the right direction, please? I feel like this must actually be quite a common case but can't find much about it.
Thanks for your time in advance!
I have succesfully used the event dispatcher approach before. You can trigger events at the start, progress, and end of processing for example, and have an event listener update the progress bar based on that.
<?php
$progress = $this->getHelperSet()->get('progress');
$dispatcher = $this->getContainer()->get('event_dispatcher');
$dispatcher->addListener('my_start_event', function (GenericEvent $event) use ($progress, $output) {
$progress->start($output, $event->getArgument('total'));
});
$dispatcher->addListener('my_progress_event', function () use ($progress) {
$progress->advance();
});
$dispatcher->addListener('my_finish_event', function () use ($progress) {
$progress->finish();
});
If you really want to avoid coupling of the event dispatcher in your service, you could extend or decorate your class (probably implementing a shared interface), and only use the event dispatcher there. You would need an extension point (public or protected method) in the base class however to be able to notify of any progress.
I am trying to refactor controllers and have taken a look at Laravel's command bus.
After reading a bunch of articles and watching a few videos, it seems that this may be a great way to refactor my controllers.
However, it also seems that I shouldn't be returning anything from a command.
When using commands
you follow the Command-query separation (CQS) principle: a function is
either a query (i.e. it returns something) or a command (i.e. it
affects state). Both are mutually exclusive. So a command is not
supposed to return anything and a query is not supposed to modify
anything.
source
I have created command CreateCustomerCommand:
namespace App\Commands;
use QuickBooks_IPP_Service_Customer;
use App\Commands\Command;
use Illuminate\Contracts\Bus\SelfHandling;
class CreateCustomer extends Command implements SelfHandling
{
private $qb;
private $customer_service;
private $customer;
private $is_same_address;
private $name;
private $primary_phone;
...
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
$this->qb = Quickbooks::Instance();
$this->qb->ipp->version(QuickBooks_IPP_IDS::VERSION_3);
$this->customer_service = new QuickBooks_IPP_Service_Customer();
$this->is_same_address = $request->input('customer.isSameAddress');
$this->name = ucwords(strtolower($request->input('customer.name')));
$this->primary_phone = $request->input('customer.primaryPhone');
}
...
/**
* Execute the command.
*
* #return void
*/
public function handle()
{
$this->customer->setDisplayName($this->name);
...
$this->customer_service->add(...);
}
}
Three questions regarding best practices:
After calling $this->customer_service->add(), a customer id is returned. How can I send this id back to the controller?
Where would it be best to incorporate an activity log?
Activity:
$activity = new Activity();
$activity->event = 'Created Customer: ' . $this->name;
$activity->user = Auth::user()->name;
$activity->save();
Would it be best to just include this at the end of CreateCustomerCommand?
What about an event?
Event:
event(new CustomerWasCreatedOrUpdated);
I am new to application architecture and am looking for a way to keep my controllers simple and maintainable. I would love if someone could point me in the right direction.
First, kudos to you for striving to keep your controllers "simple and maintainable". You may not always achieve this goal, but reaching towards it will more often than not pay off.
How can I send the ID back to the controller?
A command is a specialized case of a general service. Your command is free to declare additional public methods that interrogate the result of changing state. If the command were used by a CLI application, that application might do something like echo $this->command->getAddedCustomerId(). A web-based controller could use it similiarly.
However, the advice you quoted -- to either change state with no output or to query with output -- is sage. If you're changing state and you need to know the result of changing that state, you're probably abusing a command.
As an analogy consider the Linux command "useradd", which you would invoke like useradd -m 'Clara Barton' cbarton. That command runs and gives you just a success or failure indication. But note that you gave it the primary key, "cbarton". You can independently query for that key, like grep cbarton /etc/passwd, but importantly useradd didn't create an ID for you.
In summary, a command that changes state should at most tell you success or failure. If you wish to inspect the result of that state change, you should have given the command the keys necessary to locate the state change.
So what you probably want is a general service. A command might use that service, a controller might use the service, a model might use the service. But a service is just a general class that performs one job, and gives you the necessary API.
Where to incorporate an activity log?
Assuming you're not using PHP-AOP, a careful and rigorous practice for activity logging should be established up front and followed throughout the development lifecycle.
To a large extent, the location of the activity log depends upon the major architectural model of your system. If you rely heavily on events, then a good place might be in an extension of the Event facade or a log event. If you rely on DI extensively, then you might pass the Logger in code you decide needs logging.
In the specific case of a command, you would go either way, again depending upon your major architectural model. If you eschewed events, then you would inject the logger via Laravel's normal type-hinting DI. If you leveraged events, then you might do something like Event::fire('log', new LogState('Blah Blah', compact ($foo, $bar)));
That said, the most important thing is that you rely on a pluggable and configurable logging service, one that you can swap out and tweak for testing, QA, and production needs.
What about events?
Well, events are great. Until they're not. In my experience, events can really get complicated as they're, IMO, abused for passing data around and affecting state.
Events are like teleporters: you're going down a path, then the event fires, and suddenly you're teleported all the way across the code base and pop up in a totally different place, then you do something and get dropped right back where you were. You have to think a certain way and be efficient at following the code when Events are at play.
If Laravel events are your first introduction to events, I would discourage you from leveraging them heavily. Instead, I would suggest you limit them to one particular package or portion of your application until you get a feel for the power they offer, and the architectural and developmental rigor they require.
Let's say whenever I do a CRUD operation or modify a relationship in a specific way I also want to do something else. E.g., whenever someone publishes a post I also want to save something to a table for analytics. Maybe not the best example but in general there's a lot of this "grouped" functionality.
Normally I see this type of logic put into controllers. That's all fine an dandy until you want to reproduce this functionality in lots of places. When you start getting into partials, creating an API and generating dummy content it becomes an issue with keeping things DRY.
The ways I've seen to manage this are events, repositories, libraries, and adding to models. Here are my understandings of each:
Services: This is where most people would probably put this code. My main issue with services is that sometimes it's hard to find specific functionality in them and I feel like they get forgotten about when people are focused on using Eloquent. How would I know I need to call a method publishPost() in a library when I can just do $post->is_published = 1?
The only condition I see this working well in is if you ONLY use services (and ideally make Eloquent inaccessible somehow from controllers all together).
Ultimately it seems like this would just create a bunch of extra unnecessary files if your requests generally follow your model structure.
Repositories: From what I understand this is basically like a service but there's an interface so you can switch between ORMs, which I don't need.
Events: I see this as the most elegant system in a sense because you know your model events are always going to be called on Eloquent methods, so you can write your controllers like you normally would. I can see these getting messy though and if anyone has examples of large projects using events for critical coupling I'd like to see it.
Models: Traditionally I'd have classes that performed CRUD and also handled critical coupling. This actually made things easy because you knew all functionality around CRUD + whatever had to be done with it was there.
Simple, but in MVC architecture this isn't normally what I see done. In a sense though I prefer this over services since it's a bit easier to find, and there are less files to keep track of. It can get a bit disorganized though. I'd like to hear downfalls to this method and why most people don't seem to do it.
What are the advantages / disadvantages of each method? Am I missing something?
I think all patterns / architectures that you present are very useful as long as you follow the SOLID principles.
For the where to add logic I think that it's important to refer to the Single Responsibility Principle. Also, my answer considers that you are working on a medium / large project. If it's a throw-something-on-a-page project, forget this answer and add it all to controllers or models.
The short answer is: Where it makes sense to you (with services).
The long answer:
Controllers: What is the responsibility of Controllers? Sure, you can put all your logic in a controller, but is that the controller's responsibility? I don't think so.
For me, the controller must receive a request and return data and this is not the place to put validations, call db methods, etc..
Models: Is this a good place to add logic like sending an welcome email when a user registers or update the vote count of a post? What if you need to send the same email from another place in your code? Do you create a static method? What if that emails needs information from another model?
I think the model should represent an entity. With Laravel, I only use the model class to add things like fillable, guarded, table and the relations (this is because I use the Repository Pattern, otherwise the model would also have the save, update, find, etc methods).
Repositories (Repository Pattern): At the beginning I was very confused by this. And, like you, I thought "well, I use MySQL and thats that.".
However, I have balanced the pros vs cons of using the Repository Pattern and now I use it. I think that now, at this very moment, I will only need to use MySQL. But, if three years from now I need to change to something like MongoDB most of the work is done. All at the expense of one extra interface and a $app->bind(«interface», «repository»).
Events (Observer Pattern): Events are useful for things that can be thrown at any class any given time. Think, for instance, of sending notifications to a user.
When you need, you fire the event to send a notification at any class of your application. Then, you can have a class like UserNotificationEvents that handles all of your fired events for user notifications.
Services: Until now, you have the choice to add logic to controllers or models. For me, it makes all sense to add the logic within Services. Let's face it, Services is a fancy name for classes. And you can have as many classes as it makes sense to you within your aplication.
Take this example: A short while ago, I developed something like the Google Forms. I started with a CustomFormService and ended up with CustomFormService, CustomFormRender, CustomFieldService, CustomFieldRender, CustomAnswerService and CustomAnswerRender. Why? Because it made sense to me. If you work with a team, you should put your logic where it makes sense to the team.
The advantage of using Services vs Controllers / Models is that you are not constrained by a single Controller or a single Model. You can create as many services as needed based on the design and needs of your application. Add to that the advantage of calling a Service within any class of your application.
This goes long, but I would like to show you how I have structured my application:
app/
controllers/
MyCompany/
Composers/
Exceptions/
Models/
Observers/
Sanitizers/
ServiceProviders/
Services/
Validators/
views
(...)
I use each folder for a specific function. For example the Validators directory contains a BaseValidator class responsible for processing the validation, based on the $rules and $messages of specific validators (usually one for each model). I could as easily put this code within a Service, but it makes sense to me to have a specific folder for this even if it is only used within the service (for now).
I recommend you to read the following articles, as they might explain things a little better to you:
Breaking the Mold by Dayle Rees (author of CodeBright): This is where I put it all together, even though I changed a few things to fit my needs.
Decoupling your code in Laravel using Repositories and Services by Chris Goosey: This post explains well what is a Service and the Repository Pattern and how they fit together.
Laracasts also have the Repositories Simplified and Single Responsibility which are good resources with practical examples (even though you have to pay).
I wanted to post a response to my own question. I could talk about this for days, but I'm going to try to get this posted fast to make sure I get it up.
I ended up utilizing the existing structure that Laravel provides, meaning that I kept my files primarily as Model, View, and Controller. I also have a Libraries folder for reusable components that aren't really models.
I DID NOT WRAP MY MODELS IN SERVICES/LIBRARIES. All of the reasons provided didn't 100% convince me of the benefit of using services. While I may be wrong, as far as I can see they just result in tons of extra nearly empty files I need to create and switch between when working with models and also really reduce the benefit of using eloquent (especially when it comes to RETRIEVING models, e.g., using pagination, scopes, etc).
I put the business logic IN THE MODELS and access eloquent directly from my controllers. I use a number of approaches to make sure that the business logic doesn't get bypassed:
Accessors and mutators: Laravel has great accessors and mutators. If I want to perform an action whenever a post is moved from draft to published I can call this by creating function setIsPublishedAttribute and including the logic in there
Overriding Create/Update etc: You can always override Eloquent methods in your models to include custom functionality. That way you can call functionality on any CRUD operation. Edit: I think there's a bug with overriding create in newer Laravel versions (so I use events now registered in boot)
Validation: I hook my validation in the same way, e.g., I'll run validation by overriding CRUD functions and also accessors/mutators if needed. See Esensi or dwightwatson/validating for more information.
Magic Methods: I use the __get and __set methods of my models to hook into functionality where appropriate
Extending Eloquent: If there's an action you'd like to take on all update/create you can even extend eloquent and apply it to multiple models.
Events: This is a straight forward and generally agreed upon place to do this as well. Biggest drawback with events I think is that exceptions are hard to trace (might not be the new case with Laravel's new events system). I also like to group my events by what they do instead of when they are called...e.g., have a MailSender subscriber which listens for events that send mail.
Adding Pivot/BelongsToMany Events: One of the things I struggled with the longest was how to attach behavior to the modification of belongsToMany relationships. E.g., performing an action whenever a user joins a group. I'm almost done polishing up a custom library for this. I haven't published it yet but it is functional! Will try to post a link soon. EDIT I ended up making all my pivots into normal models and my life has been so much easier...
Addressing people's concerns with using models:
Organization: Yes if you include more logic in models, they can be longer, but in general I've found 75% of my models are still pretty small. If I chose to organize the larger ones I can do it using traits (e.g., create a folder for the model with some more files like PostScopes, PostAccessors, PostValidation, etc as needed). I know this is not necessarily what traits are for but this system works without issue.
Additional Note: I feel like wrapping your models in services is like having a swiss army knife, with lots of tools, and building another knife around it that basically does the same thing? Yeah, sometimes you might want to tape a blade off or make sure two blades are used together...but there are typically other ways to do it...
WHEN TO USE SERVICES: This article articulates very well GREAT examples for when to use services (hint: it's not very often). He says basically when your object uses multiple models or models at strange parts of their lifecycle it makes sense. http://www.justinweiss.com/articles/where-do-you-put-your-code/
What I use to do to create the logic between controllers and models is to create a service layer. Basically, this is my flow for any action within my app:
Controller get user's requested action and sent parameters and delegates everything to a service class.
Service class do all the logic related to the operation: input validation, event logging, database operations, etc...
Model holds information of fields, data transformation, and definitions of attributes validations.
This is how I do it:
This the method of a controller to create something:
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);
}
}
This is the service class that does the logic related to the operation:
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();
}
And this is my model:
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',
));
}
}
For more information about this way I use to organize my code for a Laravel app: https://github.com/rmariuzzo/Pitimi
In my opinion, Laravel already has many options for you to store your business logic.
Short answer:
Use Laravel's Request objects to automatically validate your input, and then persist the data in the request (create the model). Since all of the users input is directly available in the request, I believe it makes sense to perform this here.
Use Laravel's Job objects to perform tasks that require individual components, then simply dispatch them. I think Job's encompass service classes. They perform a task, such as business logic.
Long(er) answer:
Use Respositories When Required:
Repositories are bound to be over-bloated, and most of the time, are simply used as an accessor to the model. I feel like they definitely have some use, but unless you're developing a massive application that requires that amount of flexibility for you to be able to ditch Laravel entirely, stay away from repositories. You'll thank yourself later and your code will be much more straight forward.
Ask yourself if there's a possibility that you're going to be changing PHP frameworks or to a database type that Laravel doesn't support.
If your answer is "Probably not", then don't implement the repository pattern.
In addition to above, please don't slap a pattern on top of a superb ORM like Eloquent. You're just adding complexity that isn't required and it won't benefit you at all.
Utilize Services sparingly:
Service classes to me, are just a place to store business logic to perform a specific task with its given dependencies. Laravel has these out of the box, called 'Jobs', and they have much more flexibility than a custom Service class.
I feel like Laravel has a well-rounded solution for the MVC logic problem. It's just a matter or organization.
Example:
Request:
namespace App\Http\Requests;
use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;
class PostRequest extends Request
{
public function rules()
{
return [
'title' => 'required',
'description' => 'required'
];
}
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;
$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 $post;
}
}
Controller:
namespace App\Http\Controllers;
use App\Post;
use App\Http\Requests\PostRequest;
class PostController extends Controller
{
public function store(PostRequest $request)
{
$request->persist(new Post());
flash()->success('Successfully created new post!');
return redirect()->back();
}
public function update(PostRequest $request, Post $post)
{
$request->persist($post);
flash()->success('Successfully updated post!');
return redirect()->back();
}
}
In the example above, the request input is automatically validated, and all we need to do is call the persist method and pass in a new Post. I think readability and maintainability should always trump complex and unneeded design patterns.
You can then utilize the exact same persist method for updating posts as well, since we can check whether or not the post already exists and perform alternating logic when needed.
What are the main differences, in term of performance between service, controller as a service or using a sort of auxiliary object to do the job?
more precisely for this scenario/use-case:
i've got an API on mysite/api, my question is:
Is it better to define a controller as service, a service itself or using in "classical way" as a controller doing all the job with at least an auxiliary object (as a mini-lib) to do most of the job?
auxiliary object I mean use an istance of a class, i suppose everytime the api is called, create the new object (not so good for performance I think).
thanks in advice for your answers.
There won't be any significant performance differences, and all three approaches are absolutely valid, it just matters on your use case.
Thing to note: Controllers are basically services, they are just container aware by default in Symfony (making them Service Locators). It doesn't matter whether controller is a Service, a ContainerAwareInterface object or some object you made, it has to be instantiated either way.
That said, controller is just one of probably hundreds (if not thousands) of objects that will be created on each request and time needed to instantiate it is really negligible.
Use whatever method suits you best.
Edit
Hell, symfony controllers aren't even ContainerAware by default, they are just made so in their examples since they always extendsSymfony\Bundle\FrameworkBundle\Controller\Controller. You can try to make a Controller class like so:
<?php
namespace MyVendor\MyBundle\Controller;
class MyCustomController
{
public function myAction()
{
// do your thing here
}
}
And it will work just fine.
Here is the Symfony code that instantiates controller: ControllerResolver::createController
Basically, it checks whether string matches 'Bundle' pattern or 'Service' pattern. If neither, it just treats it as 'ClassName::methodName', and one way or another: instantiates it.
There is no difference which approach to use. But there are a lot of another questions - rest api routing, formats, documentation etc.
Use ready solution for rest-api gimler/symfony-rest-edition
I'm working on a PHP/MySQL app using the Yii framework.
I've come across the following situation:
In my VideoController, I have a actionCreate which creates a new Video and actionPrivacy which sets the privacy on the Video. The problem is that during the actionCreate the setPrivacy method of the Video model is called which currently has a transaction. I would like the creation of the Video to be in a transaction as well which leads to an error since a transaction is already active.
In the comment on this answer, Bill Karwin writes
So there's no need to make Domain Model classes or DAO classes manage
transactions -- just do it at the Controller level
and in this answer:
Since you're using PHP, the scope of your transactions is at most a
single request. So you should just use container-managed transactions,
not service-layer transa. That is, start the transaction at the start
of handling the request, and commit (or rollback) as you finish
handling the request.
If I manage the transactions in the controller, I would have a bunch of code that looks like:
public function actionCreate() {
$trans = Yii::app()->getDb()->beginTransaction();
...action code...
$trans->commit();
}
That leads to duplicated code in a lot of places where I need transactions for the action.
Or I could refactor it into the beforeAction() and afterAction() methods of the parent Controller class which would then automatically create transactions for each action being performed.
Would there be any problems with this method? What is a good practice for transaction management for a PHP app?
The reason that I say transactions don't belong in the model layer is basically this:
Models can call methods in other models.
If a model tries to start a transaction, but it has no knowledge of whether its caller started a transaction already, then the model has to conditionally start a transaction, as shown in the code example in #Bubba's answer. The methods of the model have to accept a flag so that the caller can tell it whether it is permitted to start its own transaction or not. Or else the model has to have the ability to query its caller's "in a transaction" state.
public function setPrivacy($privacy, $caller){
if (! $caller->isInTransaction() ) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if (! $caller->isInTransaction() ) $this->commit();
}
What if the caller isn't an object? In PHP, it could be a static method or simply non-object-oriented code. This gets very messy, and leads to a lot of repeated code in models.
It's also an example of Control Coupling, which is considered bad because the caller has to know something about the internal workings of the called object. For example, some of the methods of your Model may have a $transactional parameter, but other methods may not have that parameter. How is the caller supposed to know when the parameter matters?
// I need to override method's attempt to commit
$video->setPrivacy($privacy, false);
// But I have no idea if this method might attempt to commit
$video->setFormat($format);
The other solution I have seen suggested (or even implemented in some frameworks like Propel) is to make beginTransaction() and commit() no-ops when the DBAL knows it's already in a transaction. But this can lead to anomalies if your model tries to commit and finds that its doesn't really commit. Or tries to rollback and has that request ignored. I've written about these anomalies before.
The compromise I have suggested is that Models don't know about transactions. The model doesn't know if its request to setPrivacy() is something it should commit immediately or is it part of a larger picture, a more complex series of changes that involve multiple Models and should only be committed if all these changes succeed. That's the point of transactions.
So if Models don't know whether they can or should begin and commit their own transaction, then who does? GRASP includes a Controller pattern which is a non-UI class for a use case, and it is assigned the responsibility to create and control all the pieces to accomplish that use case. Controllers know about transactions because that's the place all the information is accessible about whether the complete use case is complex, and requires multiple changes to be done in Models, within one transaction (or perhaps within several transactions).
The example I have written about before, that is to start a transaction in the beforeAction() method of an MVC Controller and commit it in the afterAction() method, is a simplification. The Controller should be free to start and commit as many transactions as it logically requires to complete the current action. Or sometimes the Controller could refrain from explicit transaction control, and allow the Models to autocommit each change.
But the point is that the information about what tranasction(s) are necessary is something that the Models don't know -- they have to be told (in the form of a $transactional parameter) or else query it from their caller, which would have to delegate the question all the way up to the Controller's action anyway.
You may also create a Service Layer of classes that each know how to execute such complex use cases, and whether to enclose all the changes in a single transaction. That way you avoid a lot of repeated code. But it's not common for PHP apps to include a distinct Service Layer; the Controller's action is usually coincident with a Service Layer.
Best Practice: Put the the transactions in the model, do not put the transactions in the controller.
The primary advantage of the MVC design pattern is this: MVC makes model classes reusable without modification. Make maintenance and implementing new features easy.
For example, presumably you are primarily developing for a browser where a user enters one collection of data at a time, and you move data manipulation into the controller. Later you realize you need to support allowing the user to upload a large number of collections of data to be imported on the server from the command line.
If all the data manipulation was in the model, you could simply slurp in the data and pass it to the model to handle. If there is needful (transactional) functionality in the controller, you would have to replicate that in your CLI script.
On the other hand, perhaps you end up with another controller that needs to perform the same functionality, from a different point. You will need to replicate code in that other controller as well now.
To that end, you merely need to solve the transaction challenges in the model.
Assuming you have a Video class (model) with the setPrivacy() method that already has transaction build in; and you want to call it from another method persist() which needs to also wrap its functionality in a larger transaction, you could merely modify setPrivacy() to perform a conditional transaction.
Perhaps something like this.
class Video{
private $privacy;
private $transaction;
public function __construct($privacy){
$this->privacy = $privacy;
}
public function persist(){
$this->beginTransaction();
// ...action code...
$this->setPrivacy($this->privacy, false);
// ...action code...
$this->commit();
}
public function setPrivacy($privacy, $transactional = true){
if ($transactional) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if ($transactional) $this->commit();
}
private function beginTransaction(){
$this->transaction = Yii::app()->getDb()->beginTransaction();
}
private function commit(){
$this->transaction->commit();
}
}
In the end, your instincts are correct (re: That leads to duplicated code in a lot of places where I need transactions for the action.). Architect your models to support the myriad of transactional needs you have, and let the controller merely determine which entry point (method) it will use in it's own context.
No you are right. The transaction is delegated by the "create" method which is what a controller is supposed to do. Your suggestion of using a 'wrapper' like beforeAction() is the way to go. Just make the controller extend or implement this class. It looks like you are looking for an Observer type pattern or a factory-like implementation.
Well, one disadvantage of these broad transactions (over the whole request) is that you limit concurrency capabilities of your database engine and you also increase deadlocks probability. From this point of view, it might pay off to put transactions only where you need them and let them cover only code that needs to be covered.
If possible, I would definitely go for placing transaction in models. The problem with overlapping transactions can be solved by introducing BaseModel (ancestors of all models) and variable transactionLock in that model. Then you simply wrap your begin/commit transaction directives into BaseModel methods that respect this variable.