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.