2 min read

Tags

  • Laravel
  • DTOs
  • PHP
  • Typescript

When working on a PHP codebase, one thing I really miss from the Typescript world is the ability to pass properly typed objects and array into functions.

Been searching around for a proper solution in PHP and remembered the DTO concept today, which led me to Spatie’s data-transfer-object package.

It perfectly embodies what I’ve been looking for, only issue being you can’t just declare an object is PHP with braces like in Javascript 😁. So instead we create a new namespace in the project under which we keep the DTO files, e.g App\DataTransferObjects.

For a use case, I have this service App\Services\MobileMoneyPaymentService. It has a method requestPayment which sends a POST payment request to a payment server. This method currently looks like this:

<?php

namespace App\Services;

use App\AirtimeRequest;
use Zttp\Zttp;
use Zttp\ZttpResponse;

class MobileMoneyPaymentService
{

    /** @var string $paymentServerUrl */
    protected $paymentServerUrl;

    public function __construct()
    {
        $this->paymentServerUrl = env('PAYMENT_SERVER_URL');
    }

    /**
     * @param AirtimeRequest $airtimeRequest
     * @return ZttpResponse
     */
    public function requestPayment(AirtimeRequest $airtimeRequest): ZttpResponse
    {
        $payload = [
            'amount' => $airtimeRequest->amount,
            'phone' => $airtimeRequest->payer,
            'external_id' => '*****',
            'notification_url' => '*****',
            'reference' => '*****',
            'payer_message' => 'Top up',
            'payee_note' => 'Top up',
        ];

         return Zttp::post("$this->paymentServerUrl/mtn-mobile-money", $payload);
    }
}

I would like to refactor requestPayment method to receive a RequestPaymentData object instead, so let’s go ahead and create the \App\DataTransferObjects\RequestPaymentData class.

Please note i’ve already installed the spatie/data-transfer-object package via composer with composer require spatie/data-transfer-object.

<?php


namespace App\DataTransferObjects;


use Spatie\DataTransferObject\DataTransferObject;

class RequestPaymentData extends DataTransferObject
{
    /** @var integer */
    public $amount;

    /** @var string */
    public $phone;

    /** @var integer */
    public $external_id;
    
    /** @var string */
    public $notification_url;
    
    /** @var string */
    public $reference;
    
    /** @var string */
    public $payer_message;
    
    /** @var string */
    public $payee_note;
    
}

Having created the class, we can now refactor the requestPayment method to accept it as follows.

<?php

namespace App\Services;

use App\AirtimeRequest;
use Zttp\Zttp;
use Zttp\ZttpResponse;

class MobileMoneyPaymentService
{

    /** @var string $paymentServerUrl */
    protected $paymentServerUrl;

    public function __construct()
    {
        $this->paymentServerUrl = env('PAYMENT_SERVER_URL');
    }

    /**
     * @param RequestPaymentData $requestPaymentData
     * @return ZttpResponse
     */
    public function requestPayment(RequestPaymentData $requestPaymentData): ZttpResponse
    {
         return Zttp::post("$this->paymentServerUrl/mtn-mobile-money", $requestPaymentData->toArray());
    }
}

With this setup, am always sure all the data passed in is actually set and of the correct type. The package throws a TypeError if the value doesn’t comply with the given type 🙂.

You can learn more by having a look at it’s Readme, there’s a lot more you can do with it!