Coding Rules¶
1. Never pass Container into class constructors. Pass all dependencies separately.¶
❗ Bad:
<?php
namespace Espo\Modules\MyModule;
use Espo\Core\Container;
class MyClass
{
public function __constructor(private Container $container)
{}
}
✔️ Good:
<?php
namespace Espo\Modules\MyModule;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Metadata;
class MyClass
{
public function __constructor(
private EntityManager $entityManager,
private Metadata $metadata
) {}
}
Exception: Passing the Container may be acceptable for proxy classes.
2. Never write RAW SQL. Use ORM instead.¶
✔️ Good:
<?php
use Espo\Modules\Crm\Entities\Account;
$account = $this->entityManager
->getRDBRepositoryByClass(Account::class)
->select(['id', 'name'])
->where(['type' => Account::TYPE_CUSTOMER])
->order('createdAt')
->findOne();
3. Specify types for method parameters. Also specify a method return type.¶
❗ Bad:
<?php
class MyClass
{
public function myMethod($request, $text)
{}
}
✔️ Good:
<?php
class MyClass
{
public function myMethod(Request $request, string $text): void
{}
}
✔️ Good:
<?php
class MyClass
{
public function getString(): ?string
{
// ...
}
}
Also avoid the mixed
type for parameters.
4. Never return TRUE or FALSE to indicate a success or failure. Throw an exception if failure, return NULL for empty result.¶
✔️ Good:
<?php
use Espo\Core\Exceptions\Error;
class MyClass
{
public function getAsString(): ?string
{
if ($this->isSomethingWrong()) {
throw new Error("Something is wrong.");
}
if ($this->hasNoData()) {
return null;
}
return $this->value;
}
}
5. Don't write comments, write self explanatory code.¶
In addition: Do not write phpdoc for non-public methods and properties. It's OK if you don't write phpdoc at all (unless needed for static analysis).
Exception: Adding comments may be reasonable in some rare cases.
❗ Bad (excessive documenting and commenting):
<?php
// ...
/**
* Sanitize input.
*
* @param string $input Input string.
* @return string Sanitized string.
*/
private function sanitizeInput(string $input): string
{
// sanitize
return Util::sanitize($input);
}
✔️ Good:
<?php
// ...
private function sanitizeInput(string $input): string
{
return Util::sanitize($input);
}
✔️ Good (documenting public methods is OK):
<?php
// ...
/**
* Run a command.
*/
public function run(Params $params): void
{}
❗ Bad:
<?php
// ...
// start sanitizing
$string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH);
// end sanitizing
✔️ Good:
<?php
$string = $this->sanitizeString($string);
6. Import classes in the beginning of the file.¶
❗ Bad:
<?php
namespace Espo\Some;
class SomeClass
{
public function process(): void
{
$object1 = new \Espo\Modules\MyModule\Something();
$object2 = new \Espo\Modules\MyModule\AnotherThing();
// ...
throw new \RuntimeException();
}
}
✔️ Good:
<?php
namespace Espo\Some;
use Espo\Modules\MyModule\Something;
use Espo\Modules\MyModule\AnotherThing;
use RuntimeException;
class SomeClass
{
public function process(): void
{
$object1 = new Something();
$object2 = new AnotherThing();
// ...
throw new RuntimeException();
}
}
7. Not more that 2 levels of indentation per method.¶
❗ Bad:
<?php
// ...
public function process(): void
{
if (!$this->isCached()) {
foreach ($this->itemList as $item) {
if (!$this->hasItem($item)) {
$this->loadItem($item);
}
}
}
}
✔️ Better:
<?php
// ...
public function process(): void
{
if (!$this->isCached()) {
foreach ($this->itemList as $item) {
$this->processLoadItem($item);
}
}
}
✔️ Good:
<?php
// ...
public function process(): void
{
if (!$this->isCached()) {
$this->processLoadItems();
}
}
8. Use early returns.¶
❗ Bad:
<?php
// ...
private function sizeExceedsLimit(Storage $storage, string $id): bool
{
$fetchOnlyHeader = false;
$maxSize = $this->config->get('emailMessageMaxSize');
if ($maxSize) {
$size = $storage->getSize($id);
if ($size > $maxSize) {
$fetchOnlyHeader = true;
}
}
return $fetchOnlyHeader;
}
✔️ Good:
<?php
// ...
private function sizeExceedsLimit(Storage $storage, string $id): bool
{
$maxSize = $this->config->get('emailMessageMaxSize');
if (!$maxSize) {
return false;
}
$size = $storage->getSize($id);
return $size > $maxSize;
}
8. Avoid too many parameters in methods.¶
Less is better. Four is too much.
❗ Bad:
<?php
// ...
public function process(string $city, string $country, string $postalCode): void
{}
✔️ Good:
<?php
// ...
public function process(Address $address): void
{}
❗ Bad:
<?php
// ...
public function find(array $where, int $offset = 0, ?int $limit = null, bool $applyAcl = false): Collection
{}
✔️ Good:
<?php
// Using a builder.
$collection = $finder
->createBuilder()
->where($where)
->offset($offset)
->limit($limit)
->withAclApplied()
->find();
9. Methods that return values should not change state. Methods that change state should not return values.¶
Not a strict rule but in most cases should be followed.
❗ Bad:
<?php
class SomeProcessor
{
private bool $isProcessed = false;
public function process(): bool
{
$this->isProcessed = true;
// do something
return true;
}
}
✔️ Good:
<?php
class SomeProcessor
{
public function process(): Result
{
// do something
return Result::createSuccess();
}
}
10. Prefer composition over inheritance.¶
Try to use inheritance only when it's really reasonable.
❗ Bad:
<?php
class AccountChecker extends BaseChecker
{
public function check(Entity $entity): bool
{
$result = parent::check($entity);
// some additional logic
return $result;
}
}
✔️ Good:
<?php
class AccountChecker implements Checker
{
// Here we can inject additional dependencies that would be problematic
// if we extended the base class.
public function __construct(
private BaseChecker $baseChecker,
private SomeDependency $someDependency
) {}
public function check(Entity $entity): bool
{
$result = $this->baseChecker->check($entity);
// some additional logic
return $result;
}
}
The protected visibility should be used very sparsely.
11. Use DTOs rather than associative arrays or stdClass objects.¶
Use immutable data transfer objects.
❗ Bad:
<?php
$data = [
'attribute' => $attribute,
'value' => $value,
'entityType' => $entityType,
];
$someObject->process($data);
✔️ Good (cloning):
<?php
$data = Data::create()
->withAttribute($attribute)
->withValue($value)
->withEntityType($entityType);
$someObject->process($data);
✔️ Good (building):
<?php
$data = Data::createBuilder()
->setAttribute($attribute)
->setValue($value)
->setEntityType($entityType)
->build();
$someObject->process($data);
✔️ Good (constructor named parameters):
<?php
$data = new Data(
attribute: $attribute,
value: $value,
entityType: $entityType,
);
$someObject->process($data);