This project follows PSR coding standards and those recommended by Sylius and Symfony projects in this order. It is extended based on the experience of the whole BitBag team for everybody's sake.
- Code Style
- General
- Static analysis tools
- Symfony / Sylius / Frameworks
- Testing
- OOP / Architecture
- Workflow
- Open Source
- Git
- Starting a new project, always use the newest stable PHP, Symfony, Sylius, Shopware version.
- Always follow PSR-12 recommendations.
- Always prefer newer syntax rules. In example:
- Using
$elements = [1, 2, 3];
instead of$elements = array(1, 2, 3);
- Using property / function parameter types, wherever possible
- Using a new constructor syntax known from PHP 8.0
- Using
- Always use a trailing comma in arrays and method parameters (if PHP version allows to do that).
- Use Yoda-Style comparisons
null === $var->getResult($anotherVar)
instead of$var->getResult($anotherVar) === null
. - Use class constants / enums for static (hardcoded) values.
- Don't use annotations for framework configuration. If you consider using attributes (i.e. because of framework requirements), please do this in project-scope.
- Don't use PHPDoc for things, that can be determined from the code level. Use it only when it is REALLY valuable and in interfaces that return array of objects or collections, like:
interface Foo
{
/** @return Collection|ItemInterface[] */
public function getItems(): Collection;
}
- Always use strict types declaration in each class header, like:
<?php
declare(strict_types=1);
namespace Foo\Bar;
final class Foo
{
}
- A method must not have more than one parameter inline. Otherwise, split them with
\n
. In an edge-case where two parameters are too long to fit PSR line limit, split them as well.
Examples:
// Good patterns:
public function bar(FirstParamInterface $firstParam): void;
public function bar(
FirstParamInterface $firstParam,
): void;
public function bar(
FirstParamInterface $firstParam,
SecondParamInterface $secondParam,
ThirdParamInterface $thirdParam,
): void;
public function fooBarIsALongMethodName(
WithEvenALongerParameter $firstParam,
AndASecondParameterThatIsNotShorter $secondParameter,
): void;
-
Once you use PHPStorm (and yes, you do if you work at BitBag), you can open your IDE preferences (
PHPStorm -> Preferences
) and search forFile and Code Templates
. PHP Class Doc Comment, PHP File Header, PHP Interface Doc Comment are those templates that should at least be customized. -
For any point not included in the current section and the PSR rules please consider the https://mnapoli.fr/approaching-coding-style-rationally/ tips (as very valuable propositions).
-
If you consider any changes in your project, please discuss it with your team and if decided to do so, please do this in project-level.
-
No
/.idea
and other local config files in.gitignore
. Put them into a global gitignore file, read more on https://help.github.com/articles/ignoring-files/#create-a-global-gitignore. -
All side-effect files (or directories) from project dependencies should be put into project
.gitignore
file. -
For project development we require *NIX system kernel (for working with Git, servers, maintaining Symfony application etc.). We require from you working on Windows (WSL only) / MacOS / Ubuntu.
-
Code that is not documented doesn't exist. Writing documentation of a bundle/plugin/project is part of the development process. Remember that in the end, someone else is going to use your code who might not know each part of it. This also applies to writing GitHub repository descriptions, basic composer package information, etc.
Specially write/update information of:
- Information of needed tools, including their versions.
- Package installation process. Specially please follow your instructions from the beginning to end, to be sure the installation process is completed.
- How to run the application / tests (set of commands/steps needed to do it).
- If you prepare new major version of a package, please write/update UPGRADE.md file to describe the breaking changes. Please note, not every code-breaking change needs a new major version of application.
- Always use BitBag Coding Standard library for code cleanup. Also use PHPStan on level 8 wherever it's possible. It's included in BitBag Coding Standard library. Both ECS and PHPStan should be included in the CI process.
- We recommend using YAML (
*.yaml
) for defining routings and configs. - We recommend using XML (
*.xml
) for defining services, doctrine mappings, and validation definitions. - If you prefer doing 0. and 1. it another way, please do it consistent in entire project.
- For services definitions in a single bundle use
form.xml
,event_listener.xml
, etc. Don't put everything in theservices.xml
file, do it in public projects with only a few services. If you have more than one type of service Inside your app, create a separate config file under theservices/
directory. - Any information regarding external system (like DSN, service path) have to been placed in
.env
file as placeholders. If you consider putting there any credentials, please put only empty placeholders there. - Please use
.env.local
(which is not commited to the repository) file for all sensitive and environment-specific data. - Use finals everywhere you can (specially in non-bundle projects).
- Repositories and entities in public projects should not (and cannot) be defined as
final
. - Interface-driven development is not welcome, but if you consider extending your class or overriding your service, please use traits and interfaces.
- Entity fields in public projects (vendors) should be
protected
instead ofprivate
. - Decorate resource factories with decoration pattern and do not call resource instance with
new
keyword directly. Instead, inject resource factory into the constructor and callcreateNew()
on it. SeeSylius\Component\Product\Factory\ProductFactory
,sylius.custom_factory.product
service definition and Symfony Service Decoration. Thepriority
flag we are starting with equals 1 and is increased by one for each other decoration. - Don't include the entire service container into your service by default, if you don't have to. Instead of that use Symfony Dependency Injection.
- For customizing forms use Symfony Form Extension.
- We don't use either autowire nor autoconfigure Symfony options as it is a very "magic" way of defining services. We always prefer to manually define services and inject proper arguments into them to have better control of our Container. # To be discussed
- Do not define services as public, if it's not necessary.
- Don't use Sylius theme if you have one template in your project.
- Always write tests for your application.
- When project starts, please choose a testing toolset and continue using it during project for the consistency.
- Before starting implementing new functional code, make sure all your core logic is covered with unit tests (PHPUnit / PHPSpec).
- We use unit tests only for code related to bussiness logic, so please do not write them for controllers, repositories, fixture generators, getters, setters etc.
- For integration tests we use PHPUnit with lchrusciel/api-test-case library. The integration tests means testing code with integration to external services, like database, redis, file system. So please write them for repositories, cache drivers, file uploaders etc.
- For functional API tests we use PHPUnit with lchrusciel/api-test-case library. The functional API tests means running application API endpoints, comparing their responses with additional database checks if needed.
- Please write your fixtures using Nelmio Alice package, which is included in lchrusciel/api-test-case library.
- For functional and end to end GUI tests please use Behat.
- After you implement any new functional feature, write Behat scenario (Gherkin,
*.feature
file). - When you're fixing a bug, it's recommended to write behat scenario first (TDD) to prove the fix works correctly.
- Make your code as simple as it's possible (follow S.O.L.I.D. and KISS principles).
- Please use the Demeter Law to not to code trains as below:
$product->getVariants()->first()->getNextThing()->getNextThing()->ohNoImInTheTrain();
- Use interfaces for any core logic class implementation, especially when you create a plugin or bundle.
- Use
final
any time it is possible (in order to avoid infinite inheritance chain, in order to customize some parts use Decorator and Dependency Injection patterns).- The only exception to this rule is only for a framework/library specific requirements. I.e Doctrine Entities cannot be final classes because of proxy classes existence.
- Use ADR pattern for controllers. For instance, your controller should not extend any class and contain just an
__invoke
method. It should also be suffixed withAction
keyword.
<?php
declare(strict_types=1);
namespace App\Controller\Action;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Repository\FooRepositoryInterface;
final class SayHelloToTheWorldAction
{
/** @var FooRepositoryInterface */
private $fooRepository;
public function __construct(FooRepositoryInterface $fooRepository)
{
$this->fooRepository = $fooRepository;
}
public function __invoke(Request $request): Response
{
return new Response("Hello world!");
}
}
-1. Not confident with Git yet? Visit the simple guide!
-
If you use Git in IDE - make sure it follows all standards. We don't care what GUI/CLI you use as long as you know what happens under the hood.
-
Commit messages should be written (if only it's possible) with the following convention:
TASK-123 - Max 64 characters description in english written in Present Simple.
. If you don't work with any ticket system, please skip theTASK-123 -
part. How to write a commit message and use Git in a proper way? Read here. -
We use Gitflow. So you have to:
- Use correct branch prefixes:
hotfix/
,feature/
,refactoring/
etc, - Follow the Gitflow branching rules described in the image,
- If you use any ticket system, use the ticket name in the branch, after prefix. Jira can match branches and tasks together.
Any kind of change of the flow has to be discussed to the team leader.
- Use correct branch prefixes:
- If your're external contributor, you should use forks and pull-requests for the contribution.
- A good example of open source contribution rules you can find here: https://docs.sylius.com/en/latest/book/contributing/index.html.
- Any
*.php
file created by the BitBag developer (in Open Source project) needs to have at least the following definition where the author is the user who created this file:
<?php
/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/
declare(strict_types=1);
namespace Foo;
use Foo\Bar\App;
final class Bar implements BarInterface
{
// ...
}
- Use traits for customizing models and/or repositories and use them inside your
tests/Application/src
for testing. This way we avoid handling reference conflicts in the final app. - Entity fields in public projects (vendors) have to be
protected
instead ofprivate
to make them possible of being extended in final app. - Please use interfaces for every service you can. Also please prefer using interfaces in service constructors for better plugin extendability. It will make possible service overwriting and decoration.
- Please follow Symfony configuration structure rules. For projects with previous approach it's always welcome to do the refactor.
- When using Sylius Resource based classes (controllers, entities, interfaces, factories, repositories etc.), instead of putting the hardcoded FCQN in configuration (i.e. grids) please use the auto-generated parameters.
- Prefer using template events than overridding Sylius / Symfony templates in plugin.
- When supporting multiple Sylius / Symfony versions you probably would to load different configurations for them. To do so, you can follow our CMS Kernel logic.
- We use GitHub Actions for CI. When developing new plugin, please prepare the build file. As an example you can follow our OpenMarketplace build.yml file.
- README.md file is a must have. It has to contain at least description of plugin features and installation process. And please remember to test the installation process (following all the README commands) before publishing any changes.
Be smart and keep in mind that once you do something stupid, I will find you and I will force you to work with Laravel or Magento. There is nothing called a stupid question, but please ask it in a smart way :). It's better to talk about a feature with the whole team for 30 minutes than lose 8 hours on implementing some dummy code that will destroy the current codebase order and deep the technical debt.