Laravel Packge Nested Classes and Methods structure in Object-oriented PHP

213 views Asked by At

I'm making a Laravel package, which is a basic API Wrapper to practice. I want my code completely re-usable and neat, well that's the reason we learn OOP I think :P

Let me first attach my code, and I'll explain what I'm trying to achieve via comments.

// This is how I'm calling my class
Shiprocket::
withCredential('other-than-default') // this is optional 
->order(203504661) // pass order id
->details() // finally fetch the details
// This is my main class it's behind a Larvel Facade Accessor
class Shiprocket
{
    protected $credentials;
    protected $token;

    // I'm using it as a constructor to initilize with a different credentil pair.
    public function withCredential($credential_id) 
    {
        $this->credentials = config('shiprocket.credentials')[$credential_id];    
        $this->token = $this->getToken();    
        return $this;
    }


    public function __construct()
    {
        $this->credentials = config('shiprocket.credentials')[config('shiprocket.default_credentials')];
        $this->token = $this->getToken();
    }

    public function order($order_id = null)
    {
        return new OrderResource($order_id); 
        // Here my doubt starts 
        // I want to return another class (OrderResource) for Order related methods
        // so that we can call Order related methods like: 
        // Shiprocket::withCredential('my-credential')->order()->getAll()
        // and those methods will also use methods & properties of this Main class
        // like the token, get(), post()
    }

    public function shipment($shipment_id = null)
    {
        return new ShipmentResource($shipment_id); 
        // and maybe I can also have more child classes like OrderResource 
        // So that I can call similar methods as OrderResource for shipments like ... ->getAll()  
        // or ... ->status()
        // but these methods won't be reusable - they'll be completely different, just sometimes 
        // might have same names. 
    }

    public function getToken(): string
    {
        $duration = config('shiprocket.token_cache') ? config('shiprocket.token_cache_duration') : 0;
        
        return cache()->remember("shiprocket-{$this->credentials['email']}", $duration, function () {
            return Http::post("https://apiv2.shiprocket.in/v1/external/auth/login", [
                'email' => $this->credentials['email'],
                'password' => $this->credentials['password'],
            ])->json()['token'];
        }); 
    }

    public function get($url, $data = null)
    {
        return Http::withToken($this->token)->get($url, $data)->json();
    }

    public function post($url, $data = null)
    {
        return Http::withToken($this->token)->post($url, $data)->json();
    }
    
}

It's okay even if you don't attach any code, maybe just guide me a bit what would be the best way to achieve something like this.

1

There are 1 answers

0
Ahmed Hassan On

The chain methods that you want to apply it's called the Builder pattern

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

you can learn and find snippets from here https://refactoring.guru/design-patterns/builder

back to your case, I cant agree that we need the builder pattern here, but let's try to have the small steps with your code, let's say you want to build Shiprocket object that contains the Order and the Shipment

the simple change you need is to return the Shiprocket so the code should look like this

<?php
class Shiprocket
{
    protected $credentials;
    protected $token;
    private $order;
    private $shipment;

    public function withCredential($credential_id)
    {
        $this->credentials = config('shiprocket.credentials')[$credential_id];
        $this->token = $this->getToken();
        $this->order = null;
        $this->shipment = null;
        return $this;
    }


    public function __construct()
    {
        $this->credentials = config('shiprocket.credentials')[config('shiprocket.default_credentials')];
        $this->token = $this->getToken();
        $this->order = null;
        $this->shipment = null;
    }

    public function order($order_id = null)
    {
        $this->order = new OrderResource($order_id);
        return $this;
    }

    public function shipment($shipment_id = null)
    {
        $this->shipment = new ShipmentResource($shipment_id);
        return $this;

    }
    
    public function getOrder(){
        return $this->order;
    }
    
    public function getShipment(){
        return $this->shipment;
    }

    public function getToken(): string
    {
        $duration = config('shiprocket.token_cache') ? config('shiprocket.token_cache_duration') : 0;

        return cache()->remember("shiprocket-{$this->credentials['email']}", $duration, function () {
            return Http::post("https://apiv2.shiprocket.in/v1/external/auth/login", [
                'email' => $this->credentials['email'],
                'password' => $this->credentials['password'],
            ])->json()['token'];
        });
    }

    public function get($url, $data = null)
    {
        return Http::withToken($this->token)->get($url, $data)->json();
    }

    public function post($url, $data = null)
    {
        return Http::withToken($this->token)->post($url, $data)->json();
    }

}

Note: the code could not be perfect when it comes to the standard and the best practice I just change it to follow your idea

I hope it's helpful