You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: readme.md
+39-47
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,8 @@
1
-
##PHP Guidelines
1
+
# PHP Guidelines
2
2
3
-
### Introduction
3
+
An example of the concept can be found at the link: [PHP Guidelines example](https://github.com/pvaviloff/php-guidelines-example)
4
+
5
+
## Introduction
4
6
5
7
This document provides a set of strategies/recommendations for scaling up development teams and structuring projects. To achieve team scalability, there are two goals that drive the process: writing code as if it was developed by a single developer, and writing documentation that can be understood by someone off the street.
6
8
@@ -14,7 +16,7 @@ People work on the project, and there is a risk of encountering misunderstanding
14
16
15
17
The team is not homogeneous. The end of the implementation process can be considered when 80% of the team follows the established process. The introduction of new technology generates/changes/imposes restrictions on the work process. Before implementation, an analysis of the team's readiness is required.
16
18
17
-
###Basic concepts and logic for applying theoretical knowledge
19
+
## Basic concepts and logic for applying theoretical knowledge
The main goal of the team is to solve business process-related problems promptly. Therefore, all code should be written according to [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md) and [Clean Code](https://github.com/piotrplenik/clean-code-php). If you are not sure whether you are writing readable code, consult your neighbor. It does not have to be someone from the team. We follow the rule: "If two people understood what was written, the third is likely to understand it too."
29
31
30
32
The first problems start with implicitly described tasks and poorly designed base. If you have any questions while reading the task, first of all, write them in the task comment and ask the PM. There are often situations where you discuss the clarification of the task directly with the task assigner. If this happens, make it a rule to notify the PM of any changes/clarifications. Ask the task assigner to adjust the task or even just send the correspondence in which you resolved the unclear points to the task. All clarifications and/or changes to the task must be recorded in the task. This can lead to a task re-estimation.
@@ -39,7 +41,7 @@ Do we need to write documentation? Documentation is only necessary for functiona
39
41
40
42
Documentation should consist of two blocks. You describe the technical documentation. Also, through the PM, ask the product owner to describe the user documentation, which should describe: why the functionality was created, how to use it (preferably with screenshots) from the user's point of view (UX). You need to describe how it should work, and the product owner should describe how to work with it.
41
43
42
-
####Used concepts
44
+
### Used concepts
43
45
44
46
The team aims to write as one developer. What does it mean? Empirically identified principles and recommendations have been developed that work within the overall system and allow the entire team to write in one style. The basis is taken from the Symfony documentation, SOLID, and DDD.
45
47
@@ -53,9 +55,11 @@ The programmer should act as a translator from the language of the project manag
53
55
54
56
We also adhere to the principle of a "layered cake." We delimit responsibility zones and link each zone using dependency injection (DI).
55
57
56
-

58
+
<palign="center">
59
+
<imgsrc="./images/onion.png" />
60
+
</p>
57
61
58
-
####Used from SOLID:
62
+
### Used from SOLID:
59
63
60
64
The Single Responsibility Principle states that a service should have only one responsibility. It is easily implemented in our conditions, both during refactoring and when developing new functionality.
61
65
@@ -65,13 +69,13 @@ The Interface Segregation Principle suggests that it is better to have many inte
65
69
66
70
The Liskov Substitution Principle states that if S is a subtype of T, then objects of type T can be replaced with objects of type S without any desired properties being changed. It is partially achievable and depends on implementation.
67
71
68
-
####Not used from SOLID:
72
+
### Not used from SOLID:
69
73
70
74
The Open Closed Principle A class should be open for extension but closed for modification. The biggest problem is that not everyone from other teams may follow the SOLID principles. In projects with 10+ people, it is difficult to maintain versioning of objects.
71
75
72
76
We use Dependency Injection to remove dependencies between Controllers, Services, and Repositories and to reduce code coupling.
73
77
74
-
###General implementation scheme
78
+
## General implementation scheme
75
79
76
80
Based on MVC, we build a basic architecture:
77
81
@@ -81,8 +85,6 @@ Repository - a class that manages data storage (MySQL, MongoDB, ClickHouse). We
81
85
82
86
Entity - a class that describes the structure of databases.
83
87
84
-
Transformer - a class that transforms the final result for further transmission. Place it next to the service for which it is intended.
85
-
86
88
To link the Controller, Service, and Repository together, we use a Value Object. The Value Object also combines the concept of DTO to reduce the number of entities. Often, DTOs when changing business logic often merge into Value Objects.
87
89
88
90
## General Interaction Scheme
@@ -100,7 +102,7 @@ In Controllers that implement business logic from Domains, it is prohibited to u
100
102
The use of traits is prohibited in projects.
101
103
Direct access to the container is prohibited (with the exception of service providers and factories).
102
104
103
-
####Project structure
105
+
## Project structure
104
106
105
107
---- src/
106
108
---- ---- ExampleDomain/
@@ -110,57 +112,46 @@ Direct access to the container is prohibited (with the exception of service prov
Entity - defines a certain entity in the business logic and always has an identifier (a unique key. The key can be expressed as an id/guid/uuid or a combination of properties (a composite unique key) that will identify the entity), by which the Entity can be found or compared to another Entity. If two Entities have identical identifiers, they are the same Entity. It is almost always mutable.
124
126
125
127
```php
126
128
<?php
129
+
127
130
namespace App\BrandDomain\Entities;
128
131
129
-
use Doctrine\ORM\Mapping\Entity;
130
-
use Doctrine\ORM\Mapping\Table;
131
132
use Symfony\Component\Uid\Uuid;
132
133
use Doctrine\ORM\Mapping as ORM;
133
-
use Doctrine\ORM\Mapping\UniqueConstraint;
134
134
135
-
/**
136
-
* @Entity
137
-
* @Table(name="brands")
138
-
*/
135
+
136
+
#[ORM\Entity]
137
+
#[ORM\Table(name:"brands")]
139
138
class BrandEntity
140
139
{
141
-
/**
142
-
* @ORM\Id()
143
-
* @ORM\Column(type="uuid")
144
-
*/
145
-
private Uuid $guid;
140
+
#[ORM\Id]
141
+
#[ORM\Column(type: "uuid")]
142
+
public readonly Uuid $guid;
146
143
147
144
public function __construct(
148
-
/**
149
-
* @ORM\Column(length: 140)
150
-
*/
151
-
private readonly int $name,
145
+
#[ORM\Column(length: 140)]
146
+
public readonly string $name,
152
147
) {
153
148
$this->guid = Uuid::v4();
154
149
}
155
150
156
-
public function getGuid(): Uuid
157
-
{
158
-
return $this->guid;
159
-
}
160
151
}
161
152
```
162
153
163
-
###DTO and ValueObject
154
+
## DTO and ValueObject
164
155
165
156
Data Transfer Object (DTO) - is an object, a data structure that carries information between processes (Controllers, Services, Repositories). It is desirable not to change already set properties to get rid of implicit logic. It has no dependencies. It only contains typed properties and getters/setters. No logical actions can be performed in this object. It is often used to pass filters from the controller to services and repositories.
166
157
@@ -179,7 +170,7 @@ final class BrandCreatorObject
179
170
}
180
171
```
181
172
182
-
###Aggregates
173
+
## Aggregates
183
174
184
175
Aggregate - the use of aggregates allows avoiding excessive connection between objects that make up the model. This avoids confusion and simplifies the structure by not allowing the creation of tightly coupled systems.
185
176
@@ -201,7 +192,7 @@ final class BrandCreatorAggregate
201
192
}
202
193
```
203
194
204
-
###Constants
195
+
## Constants
205
196
206
197
Constant - is a regular class with constants. Everyone always encounters regular expressions, status IDs, etc. Now, all constants are moved to separate classes for reuse.
207
198
@@ -215,7 +206,7 @@ final class DbConst
215
206
}
216
207
```
217
208
218
-
###Exceptions
209
+
## Exceptions
219
210
220
211
Exceptions - stores named errors. Do not throw the default exception if you need to throw an error - throw a custom error. They should be used and necessary everywhere in Repositories, Services, etc.
221
212
```php
@@ -227,11 +218,11 @@ final class BrandCreatorException extends \Exception
227
218
}
228
219
```
229
220
230
-
###Dependency injection
221
+
## Dependency injection
231
222
232
223
Dependency injection (DI) is a style of object configuration where an object's fields are set by an external entity. In other words, objects are configured by external objects. DI is an alternative to self-configuration of objects.
233
224
234
-
###Repository
225
+
## Repository
235
226
236
227
Repository - these are classes that represent collections of objects. They do not describe storage in databases, caching, or the solution of any other technical problem. Repositories represent collections. How we store these collections is simply an implementation detail. In repositories, we write all queries to databases such as MySQL, MongoDB, ClickHouse, etc. We do not implement methods for deleting/editing/creating entities in repositories. We use UnitOfWork for this.
237
228
```php
@@ -247,7 +238,7 @@ use Symfony\Component\Uid\Uuid;
247
238
final class BrandRepository
248
239
{
249
240
public function __construct(
250
-
private Connection $connection;
241
+
private Connection $connection,
251
242
) {
252
243
}
253
244
@@ -278,11 +269,12 @@ final class BrandRepository
278
269
'name',
279
270
])
280
271
->from(DbConst::BRANDS);
281
-
...
272
+
# some logic
273
+
282
274
foreach ($query->executeQuery()->iterateAssociative() as $brand) {
283
275
284
276
yield new BrandObject(
285
-
Uuid::fromString(brand['guid']),
277
+
Uuid::fromString($brand['guid']),
286
278
$brand['name'],
287
279
);
288
280
}
@@ -304,11 +296,11 @@ final class BrandRepository
304
296
}
305
297
```
306
298
307
-
###UnitOfWork
299
+
## UnitOfWork
308
300
309
301
UnitOfWork - [pattern](https://martinfowler.com/eaaCatalog/unitOfWork.html) is used in Service to save/edit/delete all entities within a single transaction. An example of implementation is [Entity Manager in Doctrine](https://symfony.com/doc/current/doctrine.html).
310
302
311
-
###Services
303
+
## Services
312
304
313
305
Service - is a class or classes that implement business logic and interact with entities. The implementation of the service depends on the use cases. Services can implement their own architecture, implement patterns, and use additional entities created within the service. Upon exiting the service, we still obtain the entities described here: Entity/Aggregate/ValueObject.
314
306
```php
@@ -347,7 +339,7 @@ final class BrandCreator
347
339
}
348
340
```
349
341
350
-
###Collections
342
+
## Collections
351
343
352
344
We recommend using the implementation [https://github.com/ramsey/collection](https://github.com/ramsey/collection) as they are typed. If possible, avoid using arrays.
0 commit comments