The article will highlight the following problems of developing and supporting projects based on the Yii php framework:
- The main advantages and disadvantages
- Testing
- The nuances of using ActiveRecord
- Service Oriented
- Language innovations
- Framework extension
')
Yii I used in four commercial projects of varying degrees of complexity. These were the following decisions:
- A project with a load of about hundreds of thousands of hits per day, with targeting, analytics for millions of events, multimedia processing, etc. Advertising service. Main technologies:
- Yii1
- Mysql
- RabbitMQ
- Memcached
- Web interface for one of the campaign management solutions based on a ready-made analytical service API. Essentially a simple web GUI with some caching and minimal server logic. Main technologies:
- Yii1
- Sqlite
- bootstrap
- jQuery
- Yii2 REST API for AngularJS web application, and Android, iOS mobile clients. Discount themes, all the logic on the server. Main technologies:
- Yii2
- Mysql
- RabbitMQ
- An internal workflow system that is highly specialized for a specific industry, but with many different business processes from different departments. Main technologies:
- Yii1
- Mysql
- The client part was rewritten on AngularJS 1 and untied from php.
In addition, I experimented with this framework, both versions, in some of my pet projects. In this article, I would like to highlight the problems of developing and supporting projects of varying degrees of complexity on Yii and give a number of recommendations based on my own experience.
The last of the above projects (No. 4) was developed for about two years, by the beginning of my participation in it. At the interview, when I learned about a two-year development, without a release, about using the first version of the framework (in early 2015), I thought that it would not do without problems. But I agreed to participate in order to accept the challenge - to take on what is commonly called the “inherited code”. Yes, the one that causes a wide variety of negative feelings - disgust, hatred, despair, fear - and following the advice of Fowler, armed with tests, make long-term quality, supported, working product. A year of refactoring, a change of architecture several months before the release, and the release of a project with a similar story to production itself deserve a separate article - I have gained invaluable experience that I would like to share if the will of the community is for that.
In this article I will focus on practices that promote the creation of supported applications. I used these examples with Yii 1 and 2, but conceptually all design patterns have the right to life in any other framework or technology.
Two ends yii
Yii is good at low entry thresholds and in that it facilitates rapid prototyping. These same qualities are sources of problems.
Low entry threshold helps attract low-skilled developers who tend to quickly increase technical debt. In the long run, for a serious project, this is a significant risk. For management, this is, at the initial stage, a temptation to save the budget and time when selecting a team.
Rapid prototyping can create the illusion of the customer and management that the project is almost ready and you can start its operation, and adding functionality in the future will be as cheap as in the prototype stage. Such a development is extremely unprofitable for developers and is fraught with invaluable risks for management and the project as a whole. I am inclined to believe that the prototype should be thrown out - this is the essence of the prototype.
Testing
Unit testing in the community of Yii-developers is not widespread. There is a review of testing in the official documentation (
1 ,
2 ), many of the extensions created by enthusiasts are covered with tests. But, because in the community a large percentage of developers with little experience, who have not yet grown to automatic testing, it is more likely not to start a project on TDD as a whole.
Additional complexity for unit testing may be the use of the Active Record template. I will write about this in more detail below.
In the mentioned project only manual acceptance testing was conducted. There were unsupported tests for Selenium, for some reason written in Java, but they took too long to run and were too difficult to run, and, as a result, rarely, the result was ambiguous.
One of the first activities I took was to connect PHPUnit and use TDD in developing new features and refining existing code, teaching other members of the team to automatic testing techniques.
Codeception was later used for acceptance testing API. Codeception was later used for acceptance testing API. There were attempts to use it for functional testing of the web interface, but at the frontend, which had already begun to actively use AngularJS, there were problems with supporting such tests, had to be temporarily abandoned.
I can say about myself - test infected. What you want. I can not develop without tests. I do not believe in the performance of someone else's code, if there are no tests for it. I believe that any code for which there are no auto tests will be sent to the garbage. It will simply break down over time and at some point it will turn out to be cheaper to rewrite it from scratch and with tests than to put it into working state by debugging manually.
In Yii, there is a mechanism for working with fixtures, which largely compensates for the complexity of Active Record testing. Supporting your fixtures is something to think about from the very beginning. Use for their organization ways that will facilitate future support:
- you can use require to split large files into business processes, roles, some other domain logic
- semantically meaningful names (aliases), indicating in which test cases this record is used, with which other models it is associated (relation)
- for me there were some unobvious moments in the work of the FixtureManager, I had to read the source. Always worth knowing the tool used.
- Do not forget to explicitly clear the tables (tearDown / setUp) that are filled during the test process in order to avoid side effects from the state shared by the tests.
Active record
Active Record is also one of the advantages and disadvantages of the framework at the same time. The advantage is that when prototyping the database first, the method encouraged by the code generation of model classes according to the scheme can significantly speed up development, as well as the fact that it is sometimes easier to work with a model, in the context of which you can do both queries and validation and in general everything with it related. The disadvantages start when business logic is too big to be placed in the model, and the violation of the principle of sole responsibility makes itself felt - Active Record is the same as the Domain Model, and the Repository, and the Data Mapper, and the Table and Row Gateways at the same time.
If you see that the domain is more complicated than a business card website or a banal blog, use alternative design patterns to unload your models: Yii has a variety of ways to do this:
- You can use form models to validate user data before passing it to the model.
- validators are conveniently arranged in separate classes
- there are behaviors (behaviors for composition instead of inheritance!) that help in organizing various reusable service functionality
- it is easy to decompose a model by delegating some methods to strategies — for example, if you customize the search () method in many models — it will surely become large enough to deserve such refactoring.
This approach is easier to test and allows models not to swim “fat”.
In general, prefer composition to inheritance - try to bring the functionality from the inheritance hierarchy to delegates with a highly specialized interface.
"Fat" model, one of the most common antipatterns that I saw in projects on Yii. I prefer the following approaches when working with ActiveRecord:
- always create your super class level for models
- Do not edit the generated code - inherit from it!
- Customize the pattern and logic of the generator for your needs - Yii allows you to do it!
- place in your models only the code that is at the level of interaction with the database - tables, column names, query conditions. Avoid knowledge of database structure in other layers of the application, it’s good if your Active Record classes are the lowest in the application.
- check the result of gii work: not all types of complex links are generated correctly, sometimes you have to retreat from pure relational design or manually implement code that cannot be generated.
Service Locator, services and singletons
In fact, an application instance statically available as Yii :: app () is a singleton for itself and a service locator for application components. If there is no OOP fanaticism, this is a workable solution if you do not need to have two different instances of the application in the same process.
Do not skimp creating services - small services with minimal responsibility are good at testing, they are predictable and understandable. To solve any business problem, it is convenient to create a specialized service that will solve exactly one this task. I prefer to register the factories of such services as application components — this allows us to endow the services with state if necessary. Also, the factory provides additional flexibility in initializing objects, in comparison with the most common, judging by examples in the network, an approach to directly register services with a predetermined state in the application configuration.
Narrowly specialized services organized in this way are easy to replace with moks in order to test higher-level business logic. Well, while observing the principle of common responsibility at the level of each of the services, it is not difficult to test each of them separately.
For autocomplete your components, use the
recipe from Alexander Makarov .
Namespaces and other language innovations
Since the first version of Yii was released at the end of 2008 and supports php 5.1, it does not use namespaces at the kernel level. But this does not prevent you from using them. In projects on the first version, I successfully used them.
Instead of using the so-called. Aliases You can use static class names - the loader will pick them up. That is, the configuration may look like this (using the example of a module from official documentation):
And a couple more improvements for your code, which you probably didn't know about, or forgot:
- CgridView columns can accept for value expressions not only as a string for eval, but also as an anonymous function.
- By customizing the generator template, you can use your own namespace, short array syntax, actual psr code formatting and everything your heart desires.
- Yii1 can be connected via composer.
Extend the framework
One of the missing classes in Yii1 for me was
web \ Response , even though
Request exists. Writing the simplest implementation of the response class, and adding a superclass for controllers, with the processing of this in afterAction (), allowed the unit to test the controllers, and ultimately save them from excess "fat".
In general, the introduction of heirs and superclasses for each of the framework components used will be a good help: controllers, models, behaviors, etc.
And yet, the community has useful extensions, not only functional, but architectural. For example, we use
ObjectWatcher in the project — an implementation of the
Identity Map — in order to have the same model instances in different contexts, and
NestedTransaction .
Do not forget the wise words of Steve McConnell:
Program using a language, not a language.
This maxim is also valid for frameworks: be Developers, not% framework_name% programmers!