Understanding key Object Oriented Programming concepts is required to be able to produce Modern, High Quality PHP Software Applications that stick to OOP Best Practices and Principles.

Prerequisites:

As I mentioned in past Article, Objects are instances of a Class that act as a template and provides all the properties and methods for the object to use.

Objects are very useful for encapsulating, organize and reuse code. When building a modern Enterprise Class PHP Software Application, leveraging the power of OOP is key.

When you start a new Application, a new Module or a new Feature, your code must respond to Client Requests or to Events happening. Clients can be either Users, or other Applications. Events can be things like a cron job set to run daily at 11 pm.

To facilitate the implementation process, there are OOP Software Design Patterns1 that are accepted as Standard models to follow in the production of new Code.

Basically, the most important things to keep in mind when developing new code are:

  1. Keep things well organized.
  2. Always use one file for one class.
  3. Use Dependency Injection instead of Class Inheritance as much as possible.
  4. Group Classes in folders based on the main type or functionality.
  5. When developing in a PHP Framework like Symfony, from the entry point of a Request where you start coding, to the exit point with a Response, do not make your code to jump from one method to another more than 3 ~ 5 times, and do not make these jumps to be in more than 3~4 files.
  6. Always use type casting and doc blocks with annotations. This will will help in building a very robust Enterprise Grade Application and also will help your IDE Intelli-Sense to better index and understand you code, facilitating finding and auto-completion.
  7. Implement your code in such way  that each function does only one thing. It is also a must if you are required to implement PHP Unit Testing or building a TDD Application.

 

1. Keeping things well organized is very important for high quality code. The best way to achieve this is to follow industry proven patterns. If you are building Mini Services2 in REST API mode my recommendation for folder Structure is:

 

 There is a main ApiModule that will have all class files that are used by the other Modules, are being used by the System through the Request-Response cycle and other particular actions-events that are not part of any of the other Modules.

All other Modules like the ClientsModule shown above need no more than these folders.

If you plan to use Trait classes, then you may want to put the folder in the ApiModule.

Follow the Mini Services Architecture Part 1 section for more on this.

 

2. The times where we put thousands of lines of code in one single file are long gone. Modern PHP development requires to have a File named the same as its Class, so autoloading and namespaces can be handled in a very efficient way by composer.

Also it is very important to have the code well organized among all different Class files in a project. This is a sample Entity Class for the Zoo project i used in other Articles:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Zoo
 *
 * @ORM\Table(name="zoo")
 * @ORM\Entity
 */
class Zoo
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string|null
     *
     * @ORM\Column(name="zoo_name", type="string", length=45, nullable=true)
     */
    private $zooName;
    
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Animals", mappedBy="zoo", cascade={"persist", "refresh", "remove"}, orphanRemoval=true)
     */
    private $animals;
   
    public function __construct()
    {
        $this->animals = new ArrayCollection();
    }
    
    
    /**
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }
    
    /**
     * @return string|null
     */
    public function getZooName(): ?string
    {
        return $this->zooName;
    }
    
    /**
     * @param string|null $zooName
     * @return $this
     */
    public function setZooName(?string $zooName): self
    {
        $this->zooName = $zooName;

        return $this;
    }

    /**
     * @return Collection<int, Animals>
     */
    public function getAnimals(): Collection
    {
        return $this->animals;
    }
    
    /**
     * @param Animals $animal
     * @return $this
     */
    public function addAnimal(Animals $animal): self
    {
        if (!$this->animals->contains($animal)) {
            $this->animals->add($animal);
            $animal->setZoo($this);
        }

        return $this;
    }
    
    /**
     * @param Animals $animal
     * @return $this
     */
    public function removeAnimal(Animals $animal): self
    {
        if ($this->animals->removeElement($animal)) {
            // set the owning side to null (unless already changed)
            if ($animal->getZoo() === $this) {
                $animal->setZoo(null);
            }
        }

        return $this;
    }
    
}

And the file:

 As mentioned in 1. keeping thing well organized is key. In this case Zoo Class is an Entity Class so it lives in the Entity Folder.

 

 3. Class inheritance is one of the main characteristics of OOP. However, in modern development we have Dependency Injection available and if working with Symfony framework, DI works like magic when auto-wiring and auto-configure are enabled:

    /**
     * UsersService constructor.
     * @param EntityManagerInterface $em
     * @param CustomValidationServiceProvider $customValidation
     * @param ApiResponseTransformer $apiResponse
     * @param ValidatorInterface $validator
     * @param UserPasswordEncoderInterface $passwordEncoder
     * @param ContainerBagInterface $params
     * @param ApiGuzzleClient $client
     * @param DomainRolesService $domainRolesService
     * @param PiiCryptoService $piiCryptoService
     * @param Environment $twig
     * @param AuthorizationManager $authorizationManager
     */
    public function __construct(
        EntityManagerInterface $em,
        CustomValidationServiceProvider $customValidation,
        ApiResponseTransformer $apiResponse,
        ValidatorInterface $validator,
        UserPasswordEncoderInterface $passwordEncoder,
        ContainerBagInterface $params,
        ApiGuzzleClient $client,
        DomainRolesService $domainRolesService,
        PiiCryptoService $piiCryptoService,
        Environment $twig,
        AuthorizationManager $authorizationManager
    )
    {
        $this->em = $em;
        $this->validator = $validator;
        $this->customValidation = $customValidation;
        $this->response = $apiResponse;
        $this->passwordEncoder = $passwordEncoder;
        $this->params = $params;
        $this->httpClient = $client;
        $this->domainRolesService = $domainRolesService;
        $this->piiCryptoService = $piiCryptoService;
        $this->twig = $twig;
        $this->authorizationManager = $authorizationManager;
    }

 In this case, the above code is part of a Users Service Class that is in charge of all Business Logic related to Users. Each one the methods in each one of these Dependencies are available to be used just by doing something like this:

//Execute a Curl Post request using the httpClient
$response = $this->httpClient->getClient()->request('POST', $url, ['headers' => $headers, 'form_params' => $formParams]);


//Use the PII Crypto Service to hash the User Email
//Then use Entity Manager to query for this User in the DB
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $this->piiCryptoService->hashData(base64_decode($email))]);

Also, these Dependencies can be injected in many other Classes. With this we follow PHP Best Practices and Stick to SOLID Principles.

 

 

Avoid as much as possible implementations like this:

<?php

//c.php
Class C
{
    private $someCProperty;
    
    public function __construct($params)
    {
        $this->someCProperty = $params->someCProperty;
    }
    
    public function car($c)
    {
        return 'this car';
    }
    
}


//b.php
Class B extends C
{
    public function __construct($params)
    {
        $paramsForC = $this->getParamsForCconstructor($params);
        parent::__construct($paramsForC);
    }
    
    private function getParamsForCconstructor($params)
    {
        return $params->someOtherPropertiesForC;
    }
    
    public function cars($b, $c)
    {
        return parent::car($c) . 'cars from b' . $b;
    }
}


//a.php
Class A extends B
{
    private $someProperty;
    
    
    public function __construct($params)
    {
        $paramsForB = $this->getParamsForBconstructor($params);
        parent::__construct($paramsForB);
        $this->someProperty = $params->someProperty;
    }
    
    private function getParamsForBconstructor($params)
    {
        return $params->someOtherPropertiesForB;
    }
    
    public function AvailableCars($a, $b, $c)
    {
        return parent::cars($b, $c);
    }
    
}

//some-other-file.php

$carsFromA = new A($formParams);

$availableCars = $carsFromA->AvailableCars($a, $b, $c);

$carsFromBandC = new B($paramsForBnC);

$availableCarsFromBnC = $carsFromBandC->cars($b, $c);














Such implementations are leveraging the power of Class Inheritance, but doing this, you will end with a very messy code, very complicated to understand and very susceptible to  fail every time an update or a new feature is added.

In the next Article, I will keep explaining the next recommendations.

 

 

 

1 For more info on this refer to:

2 Refer to my Articles here:

 

 As I was mentioning in past Article, understanding key Object Oriented Programming concepts is required to be able to produce Modern, High Quality PHP Software Applications that stick to OOP Best Practices and Principles.

Prerequisites:

Basics of Object Oriented Programming in PHP - Part II

 

In this Part I will continue covering  the most important things to keep in mind when developing new code:

  1. Group Classes in folders based on the main type or functionality.
  2. When developing in a PHP Framework like Symfony, from the entry point of a Request where you start coding, to the exit point with a Response, do not make your code to jump from one method to another more than 3 ~ 5 times, and do not make these jumps to be in more than 3~4 files.
  3. Always use type casting and doc blocks with annotations. This will will help in building a very robust Enterprise Grade Application and also will help your IDE Intelli-Sense to better index and understand you code, facilitating finding and auto-completion.
  4. Implement your code in such way  that each function does only one thing. It is also a must if you are required to implement PHP Unit Testing or building a TDD Application.
  5. Avoid as much as possible code obscuring things like functions handling functions.

 

1. Grouping Classes based on its type or main functionality is very important to keep a healthy code-base. It is accepted to name the folder as the type-functionality its Classes have. This way, its very easy to locate and include other Classes with the "use..." statement:

//Single file use statements
use App\ApiBundle\Exceptions\NotFoundException;
use App\ApiBundle\Exceptions\PreconditionFailedException;
use App\ApiBundle\Exceptions\SystemException;
use App\ApiBundle\Services\ApiGuzzleClient;
use App\SecurityBundle\Entity\User;


//Directory Aliasing
use App\UsersBundle\Api\Dtos as DTO;
use App\UsersBundle\Api\Transformers as TRANSFORMER;


class Users
{

//...

   /**
     * @param int $id
     * @return DTO\User
     */
    public function getUser(int $id): DTO\User
    {
        try {
            $user = $this->em->getRepository(User::class)->find($id);
            if ($user) {
                $getUserTransformer = new TRANSFORMER\GetUserTransformer($user);
                $userDto = $getUserTransformer->transform();
                return $this->response->buildOkResponse($userDto);
            } else {
                throw new NotFoundException('No results found');
            }
        } catch (Throwable $e) {
            throw new SystemException($e->getMessage());
        }
    }

//...


}

 

2. It is very common in old code-bases or in not too old but large ones, where over a few years, many different developers have been working on the same code, that an endless thread of calls from one function to another one start showing up.

This create something referred as "Spaguetti Code". It is very important to avoid at all costs falling into this anti-pattern. A High Quality, Highly Efficient Enterprise Software Application, must have the same in its code-base. To do so, once Classes are well organized by type in its corresponding folders, a typical Use Case like a "GET User by Id" shouldn't take more than 3 ~ 5 levels deep.

Example using PHP Symfony Framework:

Incoming Request

   --> UsersController>GetUserById

      --> UsersService>GetUser

               --> GetUserEntityFromDb

         --> TransformUserEntityIntoUserDto

         --> TransformUserDtoIntoJsonresponse

Outgoing Response

 Notice that we are transforming the Entity into something called DTO (Data transfer Object). This is because when building a Enterprise Class REST API, you must have a contract between the parties involved in the "deal". in this case the deal is between a Client and the Backend Application. I use DTOs to ensure all the required data is declared and its validated in terms of Data Types and Data constraints.

DTOs are the best way to ensure Data Integrity and Consistency and are a must when dealing with complex Data Structures where we have nested Dtos within other Dtos to several levels deep:

//Sample of a nested array of other Dtos
//...
    /**
     * @var array Array of CoolStuff
     * @JMS\Type("array<App\ProductsBundle\Api\Dtos\CoolStuff>")
     * @JMS\SerializedName("techStack")
     */
    public $techStack;
//Sample of Dto Validation
    /**
     * @var string Proposed Budget Source
     * @Assert\Length(
     *      max = 255,
     *      maxMessage = "proposedBudgetSource cannot be longer than 255 characters",
     *      allowEmptyString = false
     * )
     * @JMS\Type("string")
     * @JMS\SerializedName("proposedBudgetSource")
     */
    public $proposedBudgetSource;

I will explain DTOs and how to use them in other Articles.

 3. Type casting is one of the most important additions to the newest versions of PHP. When you implement a new function, you always type cast each one of the injected params and the return type. This will ensure consistency and integrity for your application.

Also, PHP Unit testing implementation will be much more easy and modern IDEs like PHPStorm have a feature called Intellisene that uses type casting to better understand your code and help you on code autocompletion, debuging and doc block generation.

 

    /**
     * @param int $id
     * @return DTO\User
     */
    public function getUser(int $id): ?DTO\User
    {
        try {
            $user = $this->em->getRepository(User::class)->find($id);
            if ($user) {
                $getUserTransformer = new GetUserTransformer($user);
                $userDto = $getUserTransformer->transform();
                return $this->response->buildOkResponse($userDto);
            } else {
                throw new NotFoundException('No results found');
            }
        } catch (Throwable $e) {
            throw new SystemException($e->getMessage());
        }
    }

4. It is very important to design your code in such a way that your functions do one thing only. Sometimes when you are writing code for business logic, you will need to evaluate conditions or loop through a collection of items, but always keep consistency with this rule. As best practice, the less arguments that you inject in function, the better, and avoid at all cost returning different object types if them are not exceptions.

This is totally wrong:

    /**
     * @param int $id
     * @return ApiResponse|mixed|void
     */
    public function getTheseguys(int $id)
    {
        try {
            $user = $this->em->getRepository(User::class)->find($id);
            if ($user) {
                //found a user..  return User Dto
                $getUserTransformer = new GetUserTransformer($user);
                $userDto = $getUserTransformer->transform();
                return $this->response->buildOkResponse($userDto);
            }
            $client = $this->em->getRepository(Client::class)->find($id);
            if ($client) {
                //found a client..  return Client Dto
                $getUserTransformer = new GetClientformer($client);
                $clientDto = $getUserTransformer->transform();
                return $this->response->buildOkResponse($clientDto);
            }
            $vendor = $this->em->getRepository(Vendor::class)->find($id);
            if ($vendor) {
                //found a vendor..  return Vendor Dto
                $getUserTransformer = new GetVendorformer($vendor);
                $vendorDto = $getUserTransformer->transform();
                return $this->response->buildOkResponse($vendorDto);
            }
        } catch (Throwable $e) {
            throw new SystemException($e->getMessage());
        }
    }

 Using a function to return 3 different data types will make the calling function to deal with 3 different data structures and it might cause data inconsistencies.

 5. Every time you write code, think that this code will exist for long time, maybe years or decades. So always be very explicit on the intention of each line. Put comments where it make sense, give your variables and functions a name that helps understand what is doing, so in the future other developers may understand your code easily. Avoid as much as possible using things like:

//...
call_user_func($callableNamePassedObtained3LevelsUp,$args);

//Perhaps a better alternative to make it more understandable in the future -- but also wrong
switch ($callableNamePassedObtained3LevelsUp) {
    case 'itsA':
        //...
        $this->serviceA->itsA($args);
        break;
    case 'itsB':
        //...
        $this->serviceB->itsB($args);
        break;
    case 'itsC':
        //...
        $this->serviceC->itsC($args);
        break;
}

 No matter that you use Switch to explicit have the name of each method required based on the value of the evaluated variable, this implementation is much more difficult to write PHP Unit Tests and also, as each called function may or may not return different things, its very likely that this application will have inconsistencies and errors in the future.

 

 

 

 

 

 

 

 

 

 

 

 

Object Oriented Programming is about writing code using constructs called objects. We can say that everything can be called an object if we stick to the accepted definition by the Language Academia1. To understand the concept of object in Computer Programming we may start with some some real life object samples.

Mary is attending Elementary School. In her backpack she carries all her School supplies.

 

 

 We can call each one of the supplies including the Backpack an "Object". Some of these are:

    Backpack
    Sandwich
    Apple
    Banana
    Water Bottle
    Calculator
    Color Crayons
    Pencils
    Ruler
    Notebook

 

We can notice that each "Object" has "Properties".

Some of the properties of the Apple:

    Object Name: "Apple"
    Object Color: "Red"
    Object Type: "Food"
    Object has Seeds? "Yes"
    Object Need to be refrigerated? "No"

The Banana:

    Object Name: "Banana"
    Object Color: "Yellow"
    Object Type: "Food"
    Object has Seeds? "No"
    Object Need to be refrigerated? "No"

 

The Sandwich is an "Object" made of another "Objects" and also it has its own "Properties":

Object "Sandwich":

    Object "Ham":
        Name: "Honey Ham"
        Type: "Food"
        Need to be refrigerated? "Yes"
        Total Amount: 10
        Unit of Measure: Slice
    Object "Bread":
        Name: "Full Grain Bread"
        Type: "Food"
        Need to be refrigerated? "No"
        Total Amount: 25
        Unit of Measure: Slice
    Object "Cheese":
        Name: "Cheddar Cheese"
        Type: "Food"
        Need to be refrigerated? "Yes"
        Total Amount: 12
        Unit of Measure: Slice
    Object "Lettuce":
        Object Name: "Iceberg Lettuce"
        Object Color: "Green"
        Object Type: "Food"
        Object has Seeds? "No"
        Total Amount: 50
        Unit of Measure: Portion
    Ham Slices Count: "Not Defined Yet"
    Bread Slices Count: "Not Defined Yet"
    Cheese Slices Count: "Not Defined Yet"
    Has Mayo?: "Not Defined Yet"

 

 At this moment, we notice that the Sandwich has some "Properties" that say "Not Defined Yet". This is the initial "State" of the Sandwich "Object".

 There are "Actions" that can be taken for some of these "Objects" for changing their "State".

For Example, in the case of the Sandwich there is an "Action" that "Calls" another "Actions" in the included "Objects" and also an "Action" in the own Sandwich "Object":

Action "Make Me a Sandwich":

    From Included "Objects":
        "Action Add Some" of "Ham"
        "Action Add Some" of "Bread"
        "Action Add Some" of "Cheese"
        "Action Add Some" of "Lettuce"
    In "This" "Object":
        "Action Spread Some Mayo"

 

In Object Oriented Programming we call each "Object's" "Actions" a "Method" and each one may contain calls to other "Methods" or just simple logic to transform the "State" of the "Object" itself or another "Object's" "State" from "Stage 1" to "Stage 2".

When we refer to the "Object" itself we use the words "this" or "self".

We call all "Objects" used to make another one "Dependencies". In the case of the Sandwich "Object", its "Dependencies" are the "Ham", "Cheese", "Bread" and "Lettuce".

The action of including "Dependencies" is called "Dependency Injection" or "DI".

We use these "Objects" to encapsulate data and fragments of code in a well organized manner using these "Properties" and "Methods" to build Object Oriented Programs.

 

 

 

1 See Object definition by Merriam-Webster.

 

Once we understand the concept of Software Objects taking physical real life examples as I explained in previous Article, then we need to dive into their characteristics and behavior before we can effectively write Object Oriented Programs.

Prerequisites:

Object Oriented Programming (abbreviated as OOP) has 4 base principles:

  • Encapsulation
  • Abstraction
  • Inheritance
  • Polymorphism

 

Encapsulation.

If we take a look at Mary's Lunch Box, it contains these "Objects":

Sandwich
Banana
Apple
Water Bottle

The Apple "Object" contains these Properties:

    Object Name: "Apple"
    Object Color: "Red"
    Object Type: "Food"
    Object has Seeds? "Yes"
    Object Need to be refrigerated? "No"

The Sandwich is an "Object" made of another "Objects" and also it has its own "Properties":

Object "Sandwich":

    Object "Ham":
        Name: "Honey Ham"
        Type: "Food"
        Need to be refrigerated? "Yes"
        Total Amount: 10
        Unit of Measure: Slice
    Object "Bread":
        Name: "Full Grain Bread"
        Type: "Food"
        Need to be refrigerated? "No"
        Total Amount: 25
        Unit of Measure: Slice
    Object "Cheese":
        Name: "Cheddar Cheese"
        Type: "Food"
        Need to be refrigerated? "Yes"
        Total Amount: 12
        Unit of Measure: Slice
    Object "Lettuce":
        Object Name: "Iceberg Lettuce"
        Object Color: "Green"
        Object Type: "Food"
        Object has Seeds? "No"
        Total Amount: 50
        Unit of Measure: Portion
    Ham Slices Count: "Not Defined Yet"
    Bread Slices Count: "Not Defined Yet"
    Cheese Slices Count: "Not Defined Yet"
    Has Mayo?: "Not Defined Yet"

 

In OOP we call "Encapsulation" to the characteristic of an "Object" of containing "Properties", "Methods" or even other "Objects". 

OOP is about writing code using "Objects" to "Encapsulate" pieces of "Data" or fragments of "Code" in a well organized manner, so we can easily have access to these from anywhere in the Program.

 

Abstraction.

If you think from Mary's stand point, she wants to get her Lunch Box out of her Backpack, and eat the food her Mom packed for her. She needs to execute these actions for eating the Sandwich:

- Get "Object" Lunch Box
- Open "Object" Lunch Box
- Get "Object" Sandwich
- Eat "Object" Sandwich

All the "Properties" and "Methods" used for making the "Object" Sandwich are not needed by Mary to eat the Sandwich, so all of them can be hidden or "Abstracted" in the "Object" Sandwich itself.

In most OOP Languages we declare these "Abstractions" using the word "private" and them are only accessible by the "Object" itself or we declare them as "protected" so them can be accessible to the object itself or their children.

 Because the purpose of OOP is to have code well organized and accessible everywhere, we use the concept of "Abstraction" to prevent misuse of "Objects'" "Properties" and "Methods" giving the Program consistency and integrity.

 

 Inheritance.

Mary's Mom packed her today a Banana and an Apple along with the Sandwich in her Lunch Box. Other days she packs other fruits, such like a Peach, a Pear, a Plum, a Tangerine, etc. Each of these Fruit "Objects" are having many of their "Properties" and "Methods" common to each other. So to not repeat many times the same "Property" or "Method" we place them in a Fruit "Object" that becomes the "Parent" and all fruits like the Apple and the Banana become the "Children". This way, the children may have only "Properties" or "Methods" that are exclusive to each one and have a "Parent" "Object" that shares the common "Properties" and "Methods" with them.

In most OOP Languages we declare a "Parent" using the word "extends", for example: "Object" Banana "extends" "Object" Fruit.

 

 Polymorphism.

The Apple and the Banana that Mary carries in her Lunch Box are both Fruits and we can make these as Children of the "Object" Fruit. We can use something called "Interface" that is like a Blue Print that says which "Methods" and "Properties" an "Object" has to "Implement" to be part of this "Interface".

Lets say we declare the "Interface" Fruits that says that all "Objects" "implementing" it must have a "Method" called "getFruitName".

interface Fruits
{
   ...
   function getFruitName();
   ...
} 

This way if we declare:

class Fruit implements Fruits
...


class Apple extends Fruit
...


class Banana extends Fruit
...

 

we know that all these has to have the "Method" called "getFruitName" regardless of which lines of code each one has within this "Method". These Apple and Banana "Objects" may use the "getFruitName" "Method" that its "Parent" must have or may "Override" it implementing their own "getFruitName" "Method".

So, in other part of the code we could have something like:

getTheSnacks(Fruits $fruit)
{
   ...
   print $fruit->getFruitName();
   ...

 We can pass to the "Method" "getTheSnacks" any $fruit "Object" that is "implementing" the "Fruits interface" and "getFruitName" will be there from it because we know it must be there, no matter the code that each fruit "Object" is using within its own implementation of the  "getFruitName" "Method".

Understanding these 4 OOP principles may take some time and lot of practice working with OOP programs, but is crucial for writing Enterprise Class Software Applications.