I’d like to find out what coding practices are you using, since I have concerns about where to write business-logic code: in a model methods, in a controller actions or somewhere in between. Also, I’m not sure whether to use multiple form models or a single active record with multiple scenarios.
I have listed pluses an minuses of every approach but still not sure what’d be better. Take a look:
Write business logic in a controller methods:
All your code is in a single place - easy to code and review. No need to keep multiple things in mind.
You can mix BL code with non-BL code easily. For example, create an account and send signup email.
Decreases controller file size having multiple actions. This may cause performance issues having no cache.
Write business logic in a model methods:
You receive well-structured functionality well-documented methods.
You have short controllers and better performance under PHP without APC or others.
This is much closer to a classical approach and layered code structure.
You need to think more, spend more time to implement those methods and interfaces.
Mixing BL code with non-BL code may become painful. Interfaces will grow up dirty.
Write business logic in between (not an option, but still viable):
Fast to write code without thinking too much.
No coding structure.
Hard to review the code.
Unstructured layer interaction.
Write single active record instead of multiple form models:
All your functional code is in a single file.
You need to declare additional attributes in an AR such as ‘RememberMe’, ‘UserAgreemnt’, etc.
Model file grows large and may cause performance issues under non-caching environment.
Write multiple form models in addition to a simple active record:
Always good performance.
You need to track too much files are compatible with each other since
form models will generally mimic active record attributes.
What do you think? What is your personal approach if any?
I agree with you at the point when you say about associated functionality. In my case it’s a situation I create AccountController to host associated functionality to login/logout/signup/verify email/recover password. Also, some of those actions share same properties of a controller which is now ~10 Kb.
That’s right. I was also trying to make things work this way. However, having all those form models decoupled, I still can’t seem to write all BL in there. Non-trivial validation process generally includes varibles required by an action itself. For example,
If I need to add an error message upon validation in a model containing a link to a controller action keeping model unaware of a controller? For example, ‘Your account requires link(email verification)’.
If your form model operates an active record upon validation and then you need this active record to perform operations, you are obliged to implement getAccountRecord() to allow some interaction between model and a code of the controller. For example, upon password recovery you first retrieve account record (in a model), validate it’s state (in a model), assign one-time-password to a record (model) and then you need to send an email which isn’t BL but a system operation (in a controller action).
Or you have to write functional system code in the model besided BL. Is model in the position to perform system operations such as send emails, log users in? What if those operations depend on controller properties? For example, $sendSignupEmail, $rememberMeLifetime, $emailVerificationTimeout. I’ll have to write twice as much by implementing an interface of a model allowing me to control business/system actions with model’s methods.
That’s not the case, actually. All are models.
Also, I should probably make sure not to let refactoring become the whole process itself
Yeah, I remember those days before Yii, it was hell! I need to create my own FormModel class then actually extends it.
Now, with Yii. I usually handles my form by extending the CFormModel, since it supports for validation and also custom validation.
Example, I have 2 tables users and user_profiles (1 to 1 with users). Now with Yii, I actually create the form as a component by extending the CFormModel then put it in my ‘components’ folder. My models, well all my models must extend CActiveRecord, perhaps only my manager class does not extend the CActiveRecord. So, in this case, I need to develop:
2 Models that extends CActiveRecord: User, and UserProfile.
1 Model that does not extend anything, that is: UserManager.
1 Model as Component: UserForm extends CFormModel -> Validation will be performed here. If the data is actually valid, it will next call the User and UserProfile, assign the values to those models, and save / update / delete.
In my controller, I only have to call the model, and pass value to the my models and components, then load the view.
UserManager is a model or a component, depends on the perspective I guess, if you think it is cross-app, then put it in the components folder. But I usually just put my UserManager in the models folder to make it even more specific and tidy. The manager class contains usually the specific application logic that currently being developed. For example, a CMS application, it does have a user manager indeed, but a Project Management System also has a user manager. but both application have different business rules to manage their users, right ?
So, UserManager in this case is not cross application, why ? Simply because there are just nothing the same like any others, otherwise if you insist, you would spend your next 8 hours with your code rather than with your beautiful girlfriend! hahaha…
UserManager is the place where I actually use my models, then in my controllers I call these kinds of Manager (instantiate or use them directly if it is just a simple Singleton manager) to utilize them. It is usually known as the team-player development environment. You write your logic in the manager, while in real world, a module can contain many Managers, you and your team gather to develop these managers and there are junior developers who use those in the controllers. You see, it is team-player.
It is not my approach but it is already considered as a best practice in Software Engineering. Even the framework Spring in Java has it! So, that is why I have chosen to work with Yii, when I code in PHP.
You were generally talking about whether to make active record managers generic or not, while this isn’t actually a problem I had. It’s clear that managers are BL-specific components and will always contain functionality-specific code. However, I’d like to bring some clarity and standards on how we might implement a generic active record manager. For example:
Instantiate a manager based on a $_GET variable. Same as CActiveRecord constructor.
Instantiate a manager based on a $_POST[‘MyManager’]. Same as CActiveRecord constructor.
Enforce custom manager instantiation. For example, instantiate ProfileManager based on a $userId.
Perform custom parametrized validation on a manager instance.
Perform custom parametrized BL actions on a manager instance.
Return multiple out parameters from custom validation and BL actions.
Use manager instance as a model parameter for a view.
Though this is an evident pattern, it’s not used by YII to offer more flexibility.
By saying ‘single active record’ I was meaning AccountRecord having multiple actions such as signup, login, recover password, verify email, etc. Didn’t mean generics at all. Anyway, it’s clear now that we definitely have an outstanding layer of managers having 2 base classes: TFormManager extnds CFormModel and TGridManager.
While TFormManager is clear, I’m now trying to define what functionality will be required by TGridManager:
Handle sorting, filtering & paging requests.
Provide data source at runtime for TGrid.
P.S. I suggest not to start a new discussion since there isn’t two different topics.