Implementation of object-oriented design in PHP

Hello again!
 
 
Well, the next "new" course, which started in late December, is coming to an end - "Backend developer on PHP" . We took various small roughnesses and started a new one. It remains only to look at the release and everything, we'll put another tick.
 
 
And for now, let's look at one interesting article.
 
 
Go.
 
 
In this article, you will learn how to use PHP to manage your company's next DDD project and effectively model real-world situations to help determine your business logic.
 
 
Object-oriented design (Domain-Driven Design, hereinafter - DDD) is a software development methodology for designing complex software projects to deliver the final product that meets the organization's objectives. In fact, DDD helps to focus the project on an evolving base model.
 
DDD will teach you to effectively model the real world in your application and use OOP to encapsulate the business logic of the organization.
 
 
Implementation of object-oriented design in PHP
 
 
What is a domain model?
 
 
In my opinion, the Domain Model is your perception of the context to which it relates. I'll try to explain in more detail. "Region" itself means the world of business with which you work, and the tasks that it is intended to solve. For example, if you want to develop an application for online food delivery, in your subject area everything (tasks, business rules, etc.) will be about online delivery of food that needs to be implemented in your project.
 
 
The domain model is your structured solution to the problem. It should be a dictionary and key concepts of tasks from the subject area.
 
 
Common language is
 
 
"Ubiquitous Language" is a language used by business professionals to describe a domain model. This means that the development team consistently uses this language in all interactions and in code. The language should be based on the region model. Let me give you an example:
 
 
$ product = new Entity /Product ();
$ product -> setTitle (new Title ('Mobile Phone'));
$ product -> setPrice (new Price ('1000'));
$ this -> em -> persist ($ product);
$ this -> em -> flush ();

 
 
In the above code I create a new product, but in the application the product should be added, not created:
 
 
//add is a static method in product class
$ product = Product :: add (
new Title ('Mobile Phone'),
new Price ('1000')
);

 
 
If someone creates a product in a development team, and someone adds it, it breaks a single language. In this case, if we have additional actions in the method of adding a product, such as sending emails, they will all be skipped, and the definition of adding a product to the team will be changed. We would have two different definitions for one term.
 
 
Multilayer architecture
 
 
In this article, I'm not going to talk about object-oriented design. But DDD suggests the basics of good design. Eric Evans (author of "DDD, structuring complex software systems") believes that developing a good domain model is an art.
 
 
To develop a good domain model, you need to know about Model-Driven Design. Model-Driven Design - combining the model and implementation. Multilayered architecture is one of its blocks.
 
 
Layered Architecture is the idea of ​​isolating each part based on many years of experience and collaboration of developers. The layers are listed below:
 
 
The user interface is
 
Application level
 
The domain level is
 
Infrastructure level
 
 
The user interface is responsible for displaying information to the user and interpreting his commands. In Laravel, the display is a layer of the user interface (presentation). The application layer is a way of communicating with the outside world (outside the domain). This layer behaves like an open API for our application. It does not contain business rules or knowledge. In Laravel controllers are located right here.
 
 
The domain level is the heart of business software. This level is responsible for the presentation of business concepts, information about the business situation and business rules. The infrastructure layer provides general technical capabilities that support higher layers, and also supports a structure of interactions between the four layers (which is why the repositories are in this layer).
 
 
Communication between layers is mandatory, but without losing the benefits of separation. Communication takes place in one direction. As can be seen from the diagram above, the upper layers can interact with lower levels. If the bottom layers are to be connected to the top layer, they should use patterns such as Callback or Observer.
 
 
Objects-values ​​and entities
 
 
I'm a big fan of Value Objects. I think they are the essence of the PLO. Although the DDD value objects seem to be simple, they are a serious source of confusion for many, including myself. I have read and heard so many different ways of describing value objects from different points of view. Fortunately, each of the different explanations, rather, helped me to deepen the understanding of object-values, rather than contradict each other.
 
 
Value objects are accessible by their meaning, and not by the identifier. These are immutable objects. Their values ​​do not change (or change, but rarely) and they do not have a life cycle (this means they are not like rows of database tables that can be deleted), for example, currencies, dates, countries, etc.
 
 
You can create value objects that you do not recognize as value objects. For example, an email address can be a string, or it can be an object-value with its own set of behaviors.
 
 
The code below shows an example of an object-value class:
 
 
//
final class ImagesTypeValueObject {
private $ imageType;
private $ validImageType =['JPEG', 'GIF', 'BMP', 'TIFF', 'PNG'];
public
function __construct ($ imageType) {
Assertion :: inArray ($ this -> validImageType, $ imageType, 'Sorry the entry is wrong please enter valid image type');
$ this -> imageType = $ imageType;
}
public
function __toString () {
return $ this -> imageType;
}
}

 
 
Entities are objects accessible by identifiers in our application. In fact, an entity is a set of properties that have a unique identifier. A good example is a number of database tables. The entity is changeable, because it can modify its attributes (usually using setters and getters), and also has a life cycle, that is, it can be deleted.
 
 
An object is something with continuity and identity - something that is tracked in different states or even in different implementations? Or is it an attribute that describes the state of something else? This is the main difference between an entity and an object-value.
 
 
Aggregates
 
 
A model can contain a large number of domain objects. Regardless of how much we envisage in the modeling of the region, it often happens that many objects depend on each other, creating a set of relationships, and you can not be sure of the result by 100%. In other words, you need to know about the business rule that must always be respected in your domain model; Only with this knowledge can you confidently talk about your code.
 
 
Aggregates help to reduce the number of bidirectional associations between objects in the system, because you are allowed to store links only to the root. This greatly simplifies the design and reduces the number of blind changes in the graph of objects. On the other hand, aggregates help with the decoupling of large structures, establishing rules for relations between entities. (Note: aggregates can also have properties, methods, and invariants that do not fit into one class)
 
 
Eric Evans in his book has established some rules for the implementation of aggregates, and I list them below:
 
 
 
The root object has a global identity and is ultimately responsible for verifying the invariants.
 
Root objects have a global identity. Internal entities have a local identity, unique only within the aggregate.
 
Nothing outside the boundary of the aggregate can contain a reference to anything inside, except for the root object. A root object can bind links to internal objects to other objects, but these objects can only use them temporarily and can not be bound to a link.
 
As a consequence of the above rule, only the base roots can be obtained directly from the database queries. All other objects should be found by traversing the associations.
 
Objects inside the aggregate can contain references to other aggregate roots.
 
The delete operation should delete everything at once within the common boundary (it's easy with garbage collection, because there are no external references to anything except the root, remove the root and everything else will be collected).
 
When a change is made to an object at the boundary of the aggregate, all the invariants of the aggregate must be satisfied.
 
 
 
Factories
 
 
In the PLO world, a Factory is an object that is responsible only for creating other objects. In DDD, factories are used to encapsulate the knowledge required to create objects, and they are especially useful for creating aggregates.
 
 
The root of the aggregate that provides the factory method for creating instances of another type of aggregate (or internal parts) will have the primary responsibility for ensuring its basic aggregating behavior, and the factory method is only one of them. Factories can also provide an important level of abstraction, which protects the client from dependence on a particular class.
 
 
There are times when a factory is not needed, and a simple designer is enough. Use the constructor when:
 
 
 
The design is not complicated.
 
The creation of an object is not associated with the creation of others, and all the necessary attributes are passed through the constructor.
 
The developer is interested in the implementation and, perhaps, wants to choose a strategy for use.
 
Class is a type. There is no hierarchy, so there is no need to choose between a list of specific implementations.
 
 
 
Repositories
 
 
A repository is a layer that is located between the domain of your project and the database. Martin Fowler in his book "Enterprise Application Templates" writes that the storage is an intermediate interaction between the domain and the data mapping layer using an interface similar to the collection for accessing domain objects.
 
This means that you need to think about accessing data in your database just as you would with standard collection objects.
 
 
Let me explain a little more. Imagine that in DDD you may need to create an object either using the constructor or using the factory; you must request it from the root of the unit. The problem is that the developer must have a link to the root. For large applications, this becomes a problem, because you need to make sure that developers always have a reference to the required object. This will lead to the creation of a number of developer associations that are not really needed.
 
 
Another reason why storages are of great importance is access to the database. The programmer does not need to know the details necessary to access it. Since the database is at the infrastructure level, it has to deal with a lot of infrastructure details, not with domain concepts. In addition, if the developer requests the launch of a request, this will lead to the disclosure of even more internal parts required by the request.
 
 
If we do not have a repository, the domain focus will be lost, and the design will be compromised. Therefore, if developers use queries to access data from the database or pull out several specific objects, the domain logic is moved to the queries and developer code, so the aggregates will be useless.
 
 
Finally, the repository acts as a storage location for objects available around the world. The repository can also include a strategy. It can access one permanent repository or another based on the specified strategy.
 
 
Implementation in Laravel
 
 
As you might already know, the best choice for implementing DDD in PHP is Doctrine ORM. To implement aggregates and repositories, we need to make some changes to our entities and create some files on our ownlevel.
 
 
I decided to implement a small part of the application in which the user can create a page or change it. Each page can contain many comments, and each comment can contain some sub-comments. Administrators can approve /reject comments after they are added.
 
 
In the above scenario, the first step is to create a basic repository in the domain level, a basic repository derived from the Doctrine EntityRepository, which will allow us to have all the built-in functions of the Doctrine repository. Here, we can also use our common functionality, and all our repositories must be inherited from it. The implementation is as follows:
 
 
namespace App Domain Repositories Database DoctrineORM;
use App Domain Events Doctrine DoctrineEventSubscriber;
use Doctrine Common Persistence Event LifecycleEventArgs;
use Doctrine ORM EntityRepository;
use Doctrine ORM Mapping ClassMetadata;
use Doctrine ORM EntityManager;
use GeneratedHydrator Configuration;
use Doctrine Common Collections ArrayCollection;
abstract class DoctrineBaseRepository extends EntityRepository {
public $ primaryKeyName;
public $ entityName = null;
public
function __construct (EntityManager $ em) {
parent :: __ construct ($ em, new ClassMetadata ($ this -> entityClass));
$ this -> primaryKeyName = $ em -> getClassMetadata ($ this -> entityClass) -> getSingleIdentifierFieldName ();
}
}

 
 
We have two repositories. The first is the page store, and the second is the comment store. All repositories must have the entityClass property to define the entity class. In this case, we can encapsulate (private property) the object in our repository:
 
 
namespace App Domain Repositories Database DoctrineORM Page;
use App Domain User Core Model Entities Pages;
use App Domain Repositories Database DoctrineORM DoctrineBaseRepository;
class DoctrinePageRepository extends DoctrineBaseRepository {
private $ entityClass = Pages :: class;
//
public
function AddComments ($ pages) {
$ this -> _em -> merge ($ pages);
$ this -> _em -> flush ();
}
}

 
 
I use the Doctrine command line to generate entities:
 
 
namespace App Domain Interactions Core Model Entities;
use App Domain User Comments Model Entities Comments;
use Doctrine Common Collections ArrayCollection;
use Doctrine ORM Mapping as ORM;
/**
* Pages
*
* @ORMTable (name = "pages")
* @ORMEntity
* /
class Pages {
/**
* @var string
*
* @ORMColumn (name = "page_title", type = "string", length = 15? nullable = false)
* /
private $ pageTitle;
/**
* @ORMOneToMany (targetEntity = "AppDomainUserCommentsModelEntitiesComments", mappedBy = "pageId", indexBy = "pageId", cascade = {"persist", "remove"})
* /
private $ pageComment;
/**
* @var integer
*
* @ORMColumn (name = "page_id", type = "integer")
* @ ORMId
* @ORMGeneratedValue (strategy = "IDENTITY")
* /
private $ pageId;
//unnecessary properties are omitted here!
public
function __construct () {
$ this -> pageComment = new ArrayCollection ();
}
/**
* @param Comment
*
* @return void
* /
public
function addComments (Comments $ comment) {
$ this -> pageComment[]= $ comment;
}
//other setters and getters.
}

 
 
namespace App Domain User Comments Model Entities;
use Doctrine ORM Mapping as ORM;
/**
* Comments
*
* @ORMTable (name = "comments")
* @ORMEntity
* /
class Comments {
/**
* @ORMManyToOne (targetEntity = "AppDomainUserCoreModelEntitiesUsers")
* @ORMJoinColumn (name = "users_user_id", referencedColumnName = "id")
* /
private $ usersUserId;
//
/**
* @ORMManyToOne (targetEntity = "comments", inversedBy = "children")
* @ORMJoinColumn (name = "parent_id", referencedColumnName = "comment_id")
* /
private $ parentId;
/**
* @ORMManyToOne (targetEntity = "AppDomainInteractionsCoreModelEntitiespages", inversedBy = "pageComment")
* @ORMJoinColumn (name = "page_id", referencedColumnName = "page_id")
* /
private $ pageId;
/**
* @ORMOneToMany (targetEntity = "comments", mappedBy = "parent")
* /
private $ children;
//
/**
* @param Page
*
* @return void
* /
public
function __construct () {
$ this -> children = new Doctrine Common Collections ArrayCollection ();
}
//
}

 
 
As you can see, in the code above, I define relationships in object annotations. Implementing relationships in Doctrine ORM can seem very complicated, but in fact it's not so difficult when you get to know how everything works. The only way to add comments is to call addComments on the page object, and this method takes only the entity of the comment object as input. This will make us confident in the functionality of our code.
 
 
My unit looks like this:
 
 
namespace App Domain Comment;
use App Domain User Comments Model Entities Comments;
Use App Domain Repositories Database DoctrineORM User DoctrineCommentRepository;
Use App Domain Repositories Database DoctrineORM Interactions DoctrinePagesRepository;
use Assert Assertion;
class PageAggregate {
public $ Pages;
public $ pageResult;
public $ parentId = null;
public $ comments;
public $ DoctrineRepository = DoctrinePagesRepository :: class;
public
function __construct ($ id, $ comments = null, $ administrative = null) {
$ this -> DoctrineRepository = App :: make ($ this -> DoctrineRepository);
Assertion :: notNull ($ this -> pageResult = $ this -> DoctrineRepository -> findOneBy (['pageId' => $id]), 'Sorry the valid page id is required here');
$ commentFacory = new Commentfactory ($ this -> pageResult, $ comments);
return $ commentFacory -> choisir ($ administrative);
}
}

 
 
We need an aggregate that is responsible for restricting access to comments, if PageId is valid; I mean that you can not access comments without PageId. For example, comments without a valid page id have no meaning and are inaccessible. In addition, there is a method of factory comment, which helps us encapsulate business rules.
 
 
Factory method:
 
 
namespace App Domain Comment;
interface CommentTypeFactoryInterface {
public
function confectionner ();
}
namespace App Domain Comment;
interface CommentFactoryInterface {
public
function choisir ();
}

 
 
I have identified two factories. The first is the type of comments, and the second is the comment interfaces, which make it mandatory for each comment when implementing the choisir method.
 
 
namespace App Domain Comment;
use App Application Factory Request RequestFactory;
class Commentfactory implements CommentFactoryInterface {
private $ page;
private $ comment;
private $ parentId;
public
function __construct ($ page, $ comment = null) {
$ this -> page = $ page;
$ this -> comment = $ comment;
}
public
function choisir ($ administrative = null) {
//TODO: Implement choisir () method.
if (is_null ($ administrative)) {
$ comment = new Comment ($ this -> page, $ this -> comment);
return $ comment -> confectionner ();
}
$ comment = new AdministrativeComments ($ this -> page, $ this -> comment, $ this -> parentId);
return $ comment -> confectionner ();
}
}

 
 
The Comment Factory method provides the internal parts of the aggregate.
 
 
namespace App Domain Comment;
use App Domain User Comments Model Entities Comments;
Use App Domain Repositories Database DoctrineORM User DoctrineCommentRepository;
Use App Domain Repositories Database DoctrineORM Interactions DoctrinePagesRepository;
Use App Domain Interactions Core Model Entities Pages;
use App Application Factory Request RequestFactory;
use Assert Assertion;
class Comment implements CommentTypeFactoryInterface {
private $ page;
private $ comments;
public $ DoctrineCommentRepository = DoctrineCommentRepository :: class;
public $ DoctrineRepository = DoctrinePagesRepository :: class;
public
function __construct (Pages $ page, $ comment) {
$ this -> page = $ page;
$ this -> comments = $ comment;
$ this -> DoctrineCommentRepository = App :: make ($ this -> DoctrineCommentRepository);
$ this -> DoctrineRepository = App :: make ($ this -> DoctrineRepository);
}
public
function confectionner () {
if (is_array ($ this -> comments)) {
Request :: replace ($ this -> comments['data']);
App :: make (RequestFactory :: class);
$ this -> addComments ();
}
elseif (is_null ($ this -> comments)) {
return $ this -> retrieveComments ();
}
elseif (is_int ($ this -> comments)) {
$ this -> deleteComment ();
}
return true;
}
private
function addComments () {
if (isset ($ this -> comments['id']) &&! is_null ($ this -> comments['object']= $ this -> DoctrineCommentRepository -> findOneBy (['commentId' => $this - > comments['id']])))
return $ this -> editComment ();
$ this -> comments = $ this -> CommentObjectMapper (new Comments (), $ this -> comments['data']);
$ this -> page -> addComments ($ this -> comments);
$ this -> DoctrineRepository -> AddComments ($ this -> page);
}
private
function editComment () {
$ comment = $ this -> CommentObjectMapper ($ this -> comments['object'], $ this -> comments['data']);
$ this -> page -> addComments ($ comment);
$ this -> DoctrineRepository -> AddComments ($ this -> page);
}
private
function deleteComment () {
$ this -> DoctrineCommentRepository -> delComments ($ this -> comments);
}
private
function retrieveComments () {
return $ this -> page -> getPageComment ();
}
//
}

 
 
 
namespace App Domain Comment;
Use App Domain Interactions Core Model Entities Pages;
Use App Domain Repositories Database DoctrineORM User DoctrineCommentRepository;
Use App Domain Repositories Database DoctrineORM Interactions DoctrinePagesRepository;
use App Domain User Comments;
use Assert Assertion;
AdministrativeComments implements CommentTypeFactoryInterface {
private $ page;
private $ comments;
private $ parentId;
private $ privilege;
public $ DoctrineCommentRepository = DoctrineCommentRepository :: class;
public $ DoctrineRepository = DoctrinePagesRepository :: class;
public
function __construct (Pages $ page, $ comment, $ parentId) {
$ this -> page = $ page;
$ this -> comments = $ comment;
$ this -> parentId = $ parentId;
$ this -> privilege = new Athurization (Auth :: gaurd ('admin') -> user ());
}
public
function confectionner () {
$ action = $ this -> comments['action'];
Assertion :: notNull ($ this -> comments = $ this -> DoctrineCommentRepository -> findOneBy (['commentId' => $this - > comments['id'])), 'No Valid comment Id');
$ this -> $ action;
return true;
}
public
function approve () {
$ this -> privilege -> isAuthorize (__ METHOD__);
$ this -> DoctrineCommentRepository -> approve ($ this -> comments, Auth :: gaurd ('admin') -> user ());
}
public
function reject () {
$ this -> privilege -> isAuthorize (__ METHOD__);
$ this -> DoctrineCommentRepository -> reject ($ this -> comments, Auth :: gaurd ('admin') -> user ());
}
public
function delete () {
$ this -> privilege -> isAuthorize (__ METHOD__);
$ this -> DoctrineCommentRepository -> delete ($ this -> comments, Auth :: gaurd ('admin') -> user ());
}
//
}

 
 
As you can see in the above code, we have two classes: Comment and AdministrativeComments. Commentfactory will decide which class to use. Some unnecessary classes or methods, such as the Authorization class and the reject method, are omitted here. As you can see in the above code, I use RequestFactory for validation. This is another factory method in our application, which is responsible for validating the input data. This type of verification has a definition in DDD, and is also added to laravel 5+.
 
 
Conclusion
 
 
To cover all of these definitions, many articles will be required, but I did my best to generalize them. It was just a simple example of the root of the unit, but you can create your own complex aggregate, and I hope that this example will help you.
 
 
THE END
 
 
As always we wait for comments, questions here or at us on Open day.
+ 0 -

Comments 1

Offline
asdfasdfasdfasdf
asdfasdfasdfasdf 26 September 2018 20:05
asdfasdfasdfasdf

Why are you not implementing it the way it was implemented before on australian assignment help service? I think that you need to try and work on it just the way it seems to have it.

Add comment