AbstractFactory in PHP without method overload

Situation

Currently, I have 4 types of users, and in the future we forecast at least 3 more. So far they are:

  • Administrator (store administrators group)
  • Staff (store manager)
  • Staff (shop assistant)
  • Customer

In the near future, I will have to allow both employees to be a client at the same time. I will also have a support center and a reporter.

Problem

Creature. Creature. Creature. I am not worried about access control, permission, etc. The code that I have can now work wonders in this area. My problem is only creating. And it seems that the abstract Factory may be the only one for me, but the truth is that all these "abstract tutorials" that teach design patterns using books and cars just don't help me bring it into my situation. Either I am mistaken in the design template, or I do not understand it.

My attempt

In the UserFactory class, we can see the source of my problem: abstract public function signUp(); . This is bad practice and even raises a Strict Standard error in PHP 5.4+ so as not to respect the method signature. In Java, I would have overloaded a method to solve this problem. In PHP, method overloading works differently and won't let me work that 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 cases relate to the same object (Salesman and Managers - both Personnel with different access levels).

 abstract class StaffRecord extends UserFactory { protected $staffController; function __construct() { parent::__construct(); $this->staffController = new staffController(); } } 

Here the SignUp signature will be the same as the Administrator, which does not work with func_num_args() and func_get_args() . Wait, but in Java you cannot use the Overload method to solve this problem. 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 differs from both of the cases discussed earlier.

 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 from the previous one.

 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); } } 
+6
source share
2 answers

Here is my implementation:

I use the interface so that the signUp function accepts different types of parameters for each type of user;

Created Interface:

 namespace main; interface UserInterface { } 

You can add a method that must be implemented for each class. For now, just using this as an hinting object for signUp ();

Using a hint like signUp (User $ user), it will solve your problem regarding the different types of signatures passed in registration. It can be user types admin, manager, salesman and customer. Each {User} entry extends and implements an abstract factory, but differs from the implementation.

I assume that for each type of user there is a corresponding / unique behavior. I added additional classes with the name: AbstractUser.php, UserAdmin.php, UserManager.php, UserSalesman.php and UserCustomer.php. Each class will contain different types of users and attributes, but it expands the user of the abstract class, which is common for each class (email address, name, password);

AbstractUser.php - I notice common user attributes, so I created an abstract user. common attributes (email address, 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; } } 

Rewrite your UserFactory.php. But this time it includes the interface that we created 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); } 

Pay attention to the signUp () method; I am printing a hint with the created interface, this means that it will only accept the user of the object with the User instance (implements the user interface).

I assume the following code 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 accept an 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); } } 

Rewrite your abstract StaffRecord.php: (no change, 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); } } 

Here 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(' francis@email.com ', 'francis', 'test', 1); $adminRecord = new AdminRecord($userAdmin); $userManager = new UserManager(' francis@email.com ', 'francis', 'test', 1); $managerRecord = new ManagerRecord($userManager); $salesMan = new UserSalesman(' francis@email.com ', 'francis', 'test', 2, 1); $salesmanRecord = new SalesmanRecord($salesMan); //$email, $name, $password, $phone, $birthday, $gender $customer = new UserCustomer(' francis@email.com ', '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); 

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

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

Hope this solves your problem.

Thanks.

+3
source

I think you are missing a factory point. The classes that you create from the factory do not extend the factory, factory also produces an instance of the object corresponding to the class or subclass. You can expand the factory to create a more specific factory, but this is a different topic.

AdminRecord , ManagerRecord , etc. should distribute the common abstract tag UserRecord , not UserRecordFactory . factory just create the corresponding user record.

The easiest way to get around various signatures is to pass an options object or an array containing the necessary properties. I am showing an array below, but you may need the UserSignupConfig class, which can be extended for a specific use, for example, AdminSignupConfig and passed instead of a shared array.

 abstract class UserRecord { 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(array $config = array()); //or via optional object of type UserSignupConfig // "abstract public function signUp(UserSignupConfig $config = null);" 

}

In the most basic example, UserRecordFactory can have a build method for constuct UserRecords (or any child class).

  //Create the factory $userRecordFactory = new UserRecordFactory(); //Grab me something that extends UserRecord $adminRecord = $userRecordFactory->churnOutUserRecord("AdminRecord"); 

The churnOutUserRecord method can be a simple switch:

 public function churnOutUserRecord($type){ $record = null; switch($type){ case "AdminRecord": $record = new AdminRecord(); break; case "ManagerRecord": $record = new ManagerRecord(); break; ///... } return $record; // ...Or just "return new $type;" // if you are 100% sure all $types are valid classes } 

One final note: all this use of abstract classes is not the way I prefer for secondary code reuse. Instead, I would recommend using interfaces if possible, but this is a deeper topic.

+2
source

Source: https://habr.com/ru/post/981472/


All Articles