30 Tips for a Stable and Efficient Process of Developing PHP Symfony Apps

Filip Horvat
9 min readJan 5, 2024

--

There are millions of resources with rules, best practices, and tips on how to optimize your code. I want to share with you my mix of 30 selected tips on how to make your project more stable and the development process more efficient.

The tips below are especially useful for small and medium projects, as well as agency projects. However, most of them can also be applied in other cases.

In the last few years, I’ve primarily worked on Symfony+API Platform projects, which serve APIs for React-based SPAs. While the tips I’m sharing are somewhat related to these technologies, they are mostly general and applicable to other PHP technologies as well.

Let’s get started.

  1. Use code style fixer - PHP-CS-Fixer
  • Your code will be consistent.
  • Your code in merge requests will have only necessary changes.
  • You will not commit unnecessary changes related to style in files that are not relevant and do not have to be committed.
  • Your Git changes will be more trackable and clean.

2.Use code analyzer - Phpstan

  • Using a code analyzer will help prevent obvious bugs in your changes and make your codebase more stable.

3.Write tests for everything

  • Write tests for all cases agreed upon with the client or those you aim to cover, ensuring they work as expected.
  • Preferably apply TDD, but it’s not necessary as long as you ensure comprehensive coverage.
  • Of course, do not spend time on cases that are not relevant and that neither you nor the client care about, for example, someone entering custom information in the URL or a similar scenario.
  • You might think it is a waste of time, but believe me, the time spent on writing tests will save you much more throughout the entire app development cycle. This is especially crucial when you perform refactors, and trust me, you will.

4.Ensure that your tests run as quickly as possible.

  • There is no straightforward rule on how to write tests; it depends on the project. However, you don’t need to blindly adhere to common rules. What’s more important is that the tests fulfill their intended purpose and, even more crucially, that they run as quickly as possible.
  • Waiting too long for tests to be executed can significantly impact your efficiency and the development timeline.
  • It’s not just a matter of how much time you spend waiting for tests to be executed; if that time is unreasonably slow, it can impact developer morale and lead to frustration.

5.Maximize the speed of your CI and deployment process.

  • The speed of CI and deployment can significantly impact your efficiency, potentially adding an overhead of 20–30% to the development hours required for the same feature.
  • Spend time and effort to create an optimal and as fast as possible CI; it’s an investment that will pay off many times over
  • It is also important to ensure you have enough workers so that you do not need to wait for them. Additionally, they should be stable and not fail sometimes due to CI problems unrelated to the code itself.

6.Add only the truly necessary classes, lines of code, and comments.

  • Make your classes as small as possible.”
  • Avoid creating a repository entity class or any other boilerplate classes if they are not used anywhere.
  • Use PHP 8 readonly variables in the constructor; refrain from explicitly creating class variables and assigning values in the constructor.
  • Avoid adding comments when the code is self-explanatory.

7.Move as much logic to the backend as possible.

  • The frontend code should ideally include only the necessary logic that cannot be avoided.
  • For instance, if you have an order entity with subtotal and quantity, perform the total calculation in the backend and transmit it through the API to the frontend. Avoid duplicating logic for total calculation in both frontend and backend to minimize unnecessary complexity. Sending the total from the backend is preferable, particularly when used in backend processes like generating emails or PDFs, to prevent discrepancies in rounding and other issues.

8.Standardize your code

  • If an option is used in one place, utilize the same option for the next similar task later.
  • For example, in Symfony, Doctrine events can be listened to in various ways (with attributes, with tags, etc.). If you choose one method, stick to it for all cases. The same principle applies to controllers, which can be defined in multiple ways. If you pick one, consistently use that option for all cases.
  • Use a different style of code only when it is necessary.
  • The same rule applies to cases where you handle tasks on the frontend, such as displaying error messages. If you choose a specific approach, apply that rule consistently across all cases. Avoid storing some error message texts in the backend and others in the frontend.

9.Extend your code as minimally as possible.

  • For example, avoid adding a custom table name if it is not necessary; use the default one generated by Doctrine. Similarly, utilize the default API routes generated by API Platform, also extend default API Platform error responses only when really needed.
  • Extend the default behavior of the framework only when there is a valid reason.
  • That will prevent creating a problems later when you wil upgrade your app, install new packages, etc.

10.Use PHP without Xdebug in your local environment.

  • Sometimes PHP is up to 10 times slower with Xdebug turned on, which can significantly impact the efficiency of developing an app. It can also be frustrating for a developer who needs to wait extensively during debugging.
  • Turn on Xdebug only when necessary.

11.Maintain consistent naming conventions.

  • When changing a class name, ensure that you also rename all associated variables.
  • For example, if you have: MailSender, then when renaming it to EmailSender, be sure to update associated variables accordingly.
public function __construct(
private readonly MailSender $mailSender,
) {
}
public function __construct(
private readonly EmailSender $emailSender,
) {
}

12.Avoid including unnecessary validations.

  • For instance, if your API is exclusively for your frontend app and a specific endpoint is expected to receive an ‘email’ parameter, and your frontend code consistently sends it, there’s no need to implement a validation check for the presence of the email parameter.
  • This would introduce unnecessary complexity to your code, and there’s no need to cover that case with tests. Just leave it as is.
  • Collaborate with the team, project manager, and the client to define all the cases you want to cover. Do not concern yourself with other cases

13.Avoid overengineering.

  • Avoid using design patterns for simple projects and problems. There are millions of debates suggesting that mainstream design patterns are not always perfect or optimal for everyday situations. Don’t feel obligated to follow common professional rules or think that your code and app are less cool if you don’t use them.
  • This also applies to using interfaces, which can be genuinely useful in certain cases but might introduce unnecessary complexity. Avoid adding interfaces just for the sake of it, thinking it makes your app cooler or adheres more strictly to professional rules.
  • Strike a reasonable balance between perfection and practicality.

14.Make your codebase clean

  • Periodically remove dead code, entities, database tables, and features.
  • Consolidate repeated code into one class and reuse it across multiple places.
  • Eliminate duplicated code; for instance, if you have the same validation in both the controller and the service, remove it from one place.”

15.Consider modular monolith

  • If your project is relatively complex, consider the modular monolith as the first choice to manage and split complexity.
  • Aim to decouple modules as much as possible.

16.Use src only for app domain code

  • Place only your application domain code in the /src directory, and move other code to bundles.
  • For instance, if you have a helper class unrelated to your business domain, such as one returning a PDF in response, put that code in a bundle and utilize it from the /src context.
  • You don’t need to create a bundle composer package immediately; you can organize your project bundles in a separate folder and commit it to your repository.
  • Later, if you believe a bundle will be reused in other projects, you can convert it into a composer package.
  • Maintain a clean and minimal /src folder to reduce the complexity of your app domain.

17.Clean, simple and minimal readme files

  • Include clean, simple, and minimal instructions in your README files. Avoid adding optional instructions unless absolutely necessary
  • A new colleague should be able to start a project effortlessly by following the readme.
  • Readme should include really only necessary instruction because if it to large you will need to maintain that big amount of text which will cost you a lot of time time.
  • Creating numerous readme or wiki pages may seem appealing, but they require ongoing updates and often become obsolete. Aim for simplicity and effectiveness; outdated pages are not useful and are likely to be ignored.

18.Standardize you development environment as much as possible

  • For example, all developer should have for local development defined OS ubuntu xx version, docker xx version etc.
  • Do not spend your time to cover all cases, all OS and all docker versions if that is really not necessary

19.Define you docker services with versions which are used on prod server

Do not add:

FROM nginx:latest

Use exact one:

FROM nginx:1.25.2

20.Be consistent in naming

  • If your custom controller is in actions folder, put all controllers there, don’t put some in Actions folder other in Controllers folder
  • If your doctrine subscriber is named as BuildingSubscriber, name subscriber for Order also OrderSubscriber, not OrderListener

21.Create efficient docker configuration

  • When someone clones repository and starts a docker on new laptop it should everything work smooth without any problems
  • The same principle applies when a current developer on the project acquires a new laptop and clones a project.

22.Change dockblock with type hint where it is possible

for example:

/**
* @var CategoryRepository
*/
protected $categories;

to:

protected CategoryRepository $categories;

23.Do not use docblock when it is not necessary

  • The objective is to keep the codebase as minimal as possible. For instance:
/**
* @param User $user
*/
public function getData(User $user): array
{
  • Param here is redundant because it is already type hinted:
public function getData(User $user): array

24.Prepare .env for a local development

  • Put in your .env everthing prepared for local development, do not copy paste .env.local to colleagues when they clone a repository so they will be able to start a project because that reduces efficiency and adds complexity
  • For other environments use separated .env files, for example .env.staging

25.Use different APP_ENV for all you environments

  • Do not use APP_ENV=dev for both local development and on your testing servers, there is always some difference and you will be always able to control them

For local development use:

APP_ENV=dev

For develop or staging environment use:

APP_ENV=develop
APP_ENV=staging

26.Try to avoid strict divide between backend and frontend developers if possible

  • If you have strictly divided teams of backend and frontend developers, even a small change like modifying a label in a listing can take a few hours. The backend developer may need only a minute to make the change, but waiting for deployment and coordination with a frontend developer can extend the process.
  • Consider teaching and encouraging your backend developers to apply smaller frontend changes themselves; this can significantly increase overall efficiency. Instead of a few hours, the feature could be ready in just minutes if the backend developer can handle the entire task.

27.Make your process of creating a new server environments efficient

  • If you have develop and staging environments, you should be able to create a new one, for example staging2, with as less as possible effort.
  • That also applies to new projects, you need to have process efficient to create and delete new environments without much effort

28.Do not be afraid of change

  • If some part of you system works but it is not optimal, and sometimes produces and errors do not tell “it works do not touch” because if you sum all the time you spend you had to fix the issue, because that part of the system is not optimal, is many times more than the time which you could invest in fixing that issue, and there is also a client dissatisfaction.

29.Do not add unnecessary features to your codebase

  • For example, let suppose you have a cool advanced permissions system when everything is possible, adding custom roles and permissions, adding permissions per entities, permissions per entity fields, etc.
  • In that case you can present the client what you system can, but you only allow them to manage what they defined that they want.
  • If you allow them to use the complete system at some point they will start to create some edge case, report a bugs, you will need to spend time to explain how something is working or maybe fix some part of the system and that was really not necessary.

30.Make you composer.json clean

  • Pick packages you are using with caution
  • Try to avoid custom
  • Try to avoid forked repositories
  • Consider putting ‘*’ for all your dev repositories like “csfixer”, “phpstan”, etc. You will always be able to update development packages and stay up-to-date with development packages without breaking the functionalities of the app.
"phpstan/phpstan": "*",

That’s all. I hope you find some of the tips useful and applicable for your project, learn something new, or gain insights you may not have considered before.

--

--

Filip Horvat

Senior Software Engineer, Backend PHP Developer, Located at Croatia, Currently working at myzone.com