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. - Repositories in public projects should not (and cannot) be defined as
final
. # TODO - 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, if you don't have to. Instead of that use Symfony Dependency Injection.
- For customizing forms use Symfony Form Extension.
- We follow command pattern. This means we use
Command
/CommandHandler
/ message bus approach. Consider using Symfony Messenger for that. - Creating a CLI Command using Symfony Console Component should follow the following rules:
execute
method should haveint
as a return type. For the successful run, the command should return0
. For any errors during execution, the return can be1
or any different error code number.
- In Sylius plugins, use traits for customizing models and use them inside your
tests/Application/src
for testing. This way we avoid handling reference conflicts in the final app. - 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.
- Do not define services as public, if it's not necessary.
- If some of the service definition is tagged, don't use FQCN (Fully Qualified Class Name) as the service id.
- Don't use Sylius theme if you have one template in your project.
- Before starting implementing new functional code, make sure all your core logic is covered with PHPSpec.
- We use PHPSpecs only for code related to bussiness logic, so please do not write them for controllers, repositories, fixture generators etc.
- PHPSpecs are always final classes with
function
s withoutpublic
visibility and: void
return type:
final class ProductSpec extends ObjectBehavior
{
function it_follows_bitbag_coding_standards(): void
{
Assert::true($this->followsStandards());
}
}
- 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.
- Please write your fixtures using Nelmio Alice package, which is included in lchrusciel/api-test-case library.
- Before you implement any new functional feature, write Behat scenario first (Gherkin,
*.feature
file). - After writing the scenario, write a proper scenario execution (Contexts, Pages).
- Use Behat Contexts that are divided into
Hooks
- generic app Background,Setup
specific resource background,Ui
- specific interaction.
- Make your code as simple as it's possible (follow single responsibility principle and KISS principle).
- 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 Models and Services (so that you follow a single responsibility principle).
- 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.
- Be more careful when you think Singleton is something you need in the project. If it is you should go and rethink the code.
- Be careful with
static
statement, probably you will never need to use it. - 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! 0. 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:
- Open source is made by forks if only more than one person is in charge of maintenance of specific package.
- We follow http://docs.sylius.org/en/latest/contributing/ contribution standards.
- 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
{
public const SOME_CONST = 'foo';
public const SOME_OTHER_CONST = 'bar';
/** @var string */
private $someProperty;
public function inheritedMethod(): string
{
//some body
}
private function someFunction(SomeServiceInterface $someService): ?NullOrInterfacedObject
{
$items = $someService->getCollection();
/** @var WeUseThisBlockDefinitionIfNecessaryOfCourseWithInterface $someOtherProperty */
$someOtherProperty = $someService->getSomething();
// Use break line between any operation in your code. Imagine the code as block diagram, where every new line is an arrow between operations.
foreach ($items as $item) {
$item->doSomething();
if (false === $item->getProperty()) { // Always use strict comparison with expected result on the left
return;
}
continue;
}
$someService->someOutputAction();
$this->someProperty->someOtherOutputAction();
return $someOtherProperty;
}
}
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.