Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AbstractFactory in PHP without Method Overload

The Situation

I currently have 4 types of users and we predict at least 3 more in the future. For now they are:

  • Administrator (Group of shop Administrator)
  • Staff (Shop Manager)
  • Staff (Shop Salesman)
  • Customer

In a near future, I'll have to allow both staff to be also customer simultaneously. I'll also have support center and reporter.

The Problem

Creation. Creation. Creation. I'm not worried about Access Control, Permission, etc... The code I have now can do wonders in this area. My problem is only about creation. And it seems that Abstract Factory might be the one for me, but the truth is all those "abstract tutorials" teaching Design Pattern using books and cars is just not helping me bring it to my situation. Either I'm at the wrong Design Pattern or I'm not understanding it.

My Attempt

At the UserFactory class we can see the source of my problem: abstract public function signUp();. It is bad practice and even causes Strict Standard error on PHP 5.4+ to not respect a method signature. In Java, I would have method overload to solve this issue. In PHP, method overload works differently and won't allow me to work this way.

<?php

abstract class UserFactory {

    const ADMIN = 'AdminRecord';
    const MANAGER = 'ManagerRecord';
    const SALESMAN = 'SalesmanRecord';
    const CUSTOMER = 'CustomerRecord';

    public static function manufacture($type) {
        return new $type;
    }

    protected $accountController;
    protected $emailController;
    protected $toolMailer;

    function __construct() {
        $this->accountController = new AccountController();
        $this->emailController = new EmailController();
        $this->toolMailer = new ToolMailer();
    }

    abstract public function signUp();
}

Here is my first use-case: creating a new administrator.

class AdminRecord extends UserFactory {

    protected $accountCompanyController;

    function __construct() {
        parent::__construct();
        $this->accountCompanyController = new AccountCompanyController();
    }

    public function signUp($name, $email, $password, $companyId, $access) {
        $accountId = $this->accountController->add($name, $password);
        $this->emailController->add($email, $accountId);
        $this->accountCompanyController->add($accountId, $companyId, $access);

        $this->toolMailer->adminWelcome($name, $email, $password);
    }

}

Here I create a new abstract class because two of my use-case belongs to the same entity (Salesman and Managers are both Staff with different access level).

abstract class StaffRecord extends UserFactory {

    protected $staffController;

    function __construct() {
        parent::__construct();
        $this->staffController = new staffController();
    }

}

Here, the signature of the SignUp would be the same as the Admin, which rules out working with func_num_args() and func_get_args(). Wait, but in Java you wouldn't be able to use Method Overload to solve this. True, but in Java I could replace int $shopId with Shop shop and int $companyId with Company company.

class ManagerRecord extends StaffRecord {

    public function signUp($name, $email, $password, $shopId, $access) {
        $accountId = $this->accountController->add($name, $password);
        $this->emailController->add($email, $accountId);
        $this->staffController->add($accountId, $shopId, $access);
        $this->toolMailer->managerWelcome($name, $email, $password);
    }

}

Here the SignUp method is different from both cases seen before.

class SalesmanRecord extends StaffRecord {

    public function signUp($name, $email, $password, $cpf, $shopId, $access) {
        $accountId = $this->accountController->addSeller($name, $password, $cpf);
        $this->emailController->add($email, $accountId);
        $this->staffController->add($accountId, $shopId, $access);
        $this->toolMailer->salesmanWelcome($name, $email, $password);
    }

}

Here the SignUp method is even more different than before.

class CustomerRecord extends UserFactory {

    protected $customerController;

    function __construct() {
        parent::__construct();
        $this->customerController = customerController();
    }

    public function signUp($name, $email, $password, $cpf, $phone, $birthday, $gender) {
        $accountId = $this->accountController->addCustomer($name, $password, $cpf, $phone, $birthday, $gender);
        $this->emailController->add($email, $accountId);
        $this->toolMailer->customerWelcome($name, $email, $password);
    }

}
like image 414
Marco Aurélio Deleu Avatar asked Jan 23 '15 20:01

Marco Aurélio Deleu


1 Answers

Here's my implemenation:

I make use of Interface to to make the signUp function accept different types of parameter for every user type;

Interface created:

namespace main;
interface UserInterface { }

You can add method that needs to be implemented per class. For now, just using this as a type hinting object for signUp();

By using type hinting on signUp(User $user), it will solve your problem regarding different type of signatures passed in signup. It can be a user type admin, manager, salesman and customer. Every {User}Record extends and implements the abstract factory but differs on implementation.

I assume for for every user type there is a corresponding / unique behavior. I added extra classes named: AbstractUser.php, UserAdmin.php, UserManager.php, UserSalesman.php and UserCustomer.php. Each class will contain different types of user and attributes but extends an abstract class user which is common for every class (email, name, password);

AbstractUser.php - I notice a common attributes of a user, so I created an Abstract User. common attributes (email, name, password)

<?php

namespace main;

abstract class AbstractUser {
    public $email;
    public $name;
    public $password;

    public function __construct($email, $name, $password) {
        $this->email = $email;
        $this->name = $name;
        $this->password = $password;
    }
}

Let's rewrite your UserFactory.php. But this time, it includes the interface we built UserInterface.php as User;

namespace main;

use main\UserInterface as User;

abstract class UserFactory {
    const ADMIN = 'AdminRecord';
    const MANAGER = 'ManagerRecord';
    const SALESMAN = 'SalesmanRecord';
    const CUSTOMER = 'CustomerRecord';

    public static function manufacture($type) {
        return new $type;
    }

    protected $accountController;
    protected $emailController;
    protected $toolMailer;

    function __construct() {
        $this->accountController = new \stdClass();
        $this->emailController = new \stdClass();
        $this->toolMailer = new \stdClass();
    }

    abstract public function signUp(User $user);
}

notice the method signUp(); I type hint it with the interface created, it means that it will only accept an object user with an instance of User (implements user interface).

I assume the next sets of codes are self-explanatory:

UserAdmin:

<?php
namespace main;

use main\AbstractUser;

class UserAdmin extends AbstractUser implements UserInterface {
    public $companyId;
    public $access;

    public function __construct($email, $name, $password, $companyId) {
        parent::__construct($email, $name, $password);
        $this->companyId = $companyId;
        $this->access = UserFactory::ADMIN;
    }
}

AdminRecord: signUp(User $user) Should only accepts Instance of UserAdmin.php

<?php

namespace main;

use main\UserFactory;
use main\UserInterface as User;

class AdminRecord extends UserFactory {
    protected $accountCompanyController;

    function __construct() {
        parent::__construct();
        $this->accountCompanyController = new \stdClass(); //new AccountCompanyController();
    }

    public function signUp(User $user) {
        $accountId = $this->accountController->add($user->name, $user->password);
        $this->emailController->add($user->email, $accountId);
        $this->accountCompanyController->add($accountId, $user->companyId, $user->access);
        $this->toolMailer->adminWelcome($user->name, $user->email, $user->password);
    }
}

Let's rewrite your abstract StaffRecord.php: (no changes, I think)

<?php
namespace main;

use main\UserFactory;

abstract class StaffRecord extends UserFactory {
    protected $staffController;

    function __construct() {
        parent::__construct();
        $this->staffController = new \stdClass(); //staffController
    }
}

UserManager:

<?php

namespace main;

use main\AbstractUser;

class UserManager extends AbstractUser implements UserInterface {
    public $shopId;
    public $access;

    public function __construct($email, $name, $password, $shopId) {
        parent::__construct($email, $name, $password);
        $this->shopId = $shopId;
        $this->access = UserFactory::MANAGER;
    }
}

ManagerRecord:

<?php

namespace main;

use main\StaffRecord;
use main\UserInterface as User;

class ManagerRecord extends StaffRecord {
    public function signUp(User $user) {
      $accountId = $this->accountController->add($user->name, $user->password);
      $this->emailController->add($user->email, $accountId);
      $this->staffController->add($accountId, $user->shopId, $user->access);
      $this->toolMailer->managerWelcome($user->name, $user->email, $user->password);
    }
}

UserSalesman:

<?php

namespace main;

use main\AbstractUser;

class UserSalesman extends AbstractUser implements UserInterface {
    public $cpf;
    public $access;
    public $shopId;

    public function __construct($email, $name, $password, $cpf, $shopId) {
        parent::__construct($email, $name, $password);
        $this->shopId = $shopId;
        $this->cpf = $cpf;
        $this->access = UserFactory::SALESMAN;
    }
}

SalesmanRecord:

<?php

namespace main;

use main\StaffRecord;
use main\UserInterface as User;

class SalesmanRecord extends StaffRecord {
    public function signUp(User $user) {
      $accountId = $this->accountController->addSeller($user->name, $user->password, $user->cpf);
      $this->emailController->add($user->email, $accountId);
      $this->staffController->add($accountId, $user->shopId, $user->access);
      $this->toolMailer->salesmanWelcome($user->name, $user->email, $user->password);
    }
}

UserCustomer:

<?php

namespace main;

use main\AbstractUser;

class UserCustomer extends AbstractUser implements UserInterface {
    public $cpf;
    public $phone;
    public $birthday;
    public $gender;

    public function __construct($email, $name, $password, $phone, $birthday, $gender) {
        parent::__construct($email, $name, $password);
        $this->phone = $phone;
        $this->birthday = $birthday;
        $this->gender = $gender;
        $this->access = UserFactory::CUSTOMER;
    }
}

CustomerRecord:

<?php

namespace main;

use main\UserInterface;
use main\UserInterface as User;

class CustomerRecord extends UserFactory {
  protected $customerController;

  function __construct() {
    parent::__construct();
    $this->customerController = new \stdClass(); //customerController
  }

  public function signUp(User $user) {
    $accountId = $this->accountController->addCustomer($user->name, $user->password, $user->cpf, $user->phone, $user->birthday, $user->gender);
    $this->emailController->add($user->email, $accountId);
    $this->toolMailer->customerWelcome($user->name, $user->email, $user->password);
  }
}

This is how I use it;

with loader.php:

<?php

function __autoload($class)
{
    $parts = explode('\\', $class);
    require end($parts) . '.php';
}

php main.php

<?php
namespace main;

include_once "loader.php";

use main\AdminRecord;
use main\UserAdmin;
use main\UserFactory;
use main\ManagerRecord;
use main\UserSalesman;
use main\CustomerRecord;


$userAdmin = new UserAdmin('[email protected]', 'francis', 'test', 1);
$adminRecord = new AdminRecord($userAdmin);

$userManager = new UserManager('[email protected]', 'francis', 'test', 1);
$managerRecord = new ManagerRecord($userManager);

$salesMan = new UserSalesman('[email protected]', 'francis',  'test', 2, 1);
$salesmanRecord = new SalesmanRecord($salesMan);

//$email, $name, $password, $phone, $birthday, $gender
$customer = new UserCustomer('[email protected]', 'francis', 'test', '0988-2293', '01-01-1984', 'Male');
$customerRecord = new CustomerRecord($customer);

print_r($adminRecord);
print_r($userManager);
print_r($salesMan);
print_r($salesmanRecord);
print_r($customer);
print_r($customerRecord);

Download files: https://www.dropbox.com/sh/ggnplthw9tk1ms6/AACXa6-HyNXfJ_fw2vsLKhkIa?dl=0

The solution I created is not perfect and still need refactor and improvement.

I hope this solves your problem.

Thanks.

like image 162
Francis Avatar answered Sep 19 '22 12:09

Francis