How to create view/renderer for the polymorphic model - php

I need to render polymorphic models in different situations.
Model's base class:
abstract class BaseDiscount {
abstract public function createRenderer(IDiscountRendererFactory $factory);
}
IDiscountRendererFactory:
interface IDiscountRendererFactory {
/**
* #return IDiscountRenderer
*/
public function createDiscountRenderer(Discount $model);
/**
* #return IDiscountRenderer
*/
public function createActionRenderer(Action $model);
}
Discount model class:
class Discount extends BaseDiscount {
public function createRenderer(IDiscountRendererFactory $factory) {
return $factory->createDiscountRenderer($this);
}
}
Action model class:
class Action extends BaseDiscount {
public function createRenderer(IDiscountRendererFactory $factory) {
return $factory->createActionRenderer($this);
}
}
IDiscountRenderer:
interface IDiscountRenderer {
public function render();
}
In the client module I have:
class ConcreteDiscountRenderer implements IDiscountRenderer {
public function __construct(Discount $model) {
//do something
}
public function render() {
//do something
}
}
class ConcreteActionRenderer implements IDiscountRenderer {
public function __construct(Action $model) {
//do something
}
public function render() {
//do something
}
}
class ConcreateDiscountRendererFactory implements IDiscountRendererFactory {
public function createDiscountRenderer(Discount $model) {
return new ConcreteDiscountRenderer($model);
}
public function createActionRenderer(Action $model) {
return new ConcreteActionRenderer($model);
}
}
$repository = new DiscountRepository();
/** #var BaseDiscount[] $discounts */
$discounts = $repository->findAll();
$factory = new ConcreateDiscountRendererFactory();
foreach ($discounts as $discount) {
$discount->createRenderer();
$renderer = $discount->createRenderer($factory);
$renderer->render();
}
In other application parts may be other implementatations.
I think I got some combination of Visitor and AbstractFactory patterns.
Is it a right approach or is there a better solution?
UPD
If I add new model class, like DiscountProgramm extends BaseDiscout, I have to refactor IDiscountFactoryInterface and all it's realizations. Is there an approach which allows to avoid that?

Related

Laravel UnitTest not working when pass constructor has Interface

I'm new to UnitTest and trying to integrate it into my Laravel application, but I'm getting the below error:
Call to a member function findOne() on null
at app/Services/User/UserService.php:32
28▕ $this->userWebsiteRepository = $userWebsiteRepository;
29▕ }
30▕
31▕ public function findOne($data = []){
➜ 32▕ return $this->userRepository->findOne($data);
33▕ }
34▕
This is my code.
AuthController.php
class AuthController extends Controller {
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function show($id){
return $this->userService->findOne(['id' => $id]);
}
}
UserService.php
class UserService
{
public $userRepository;
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
}
UserRepositoryInterface.php
interface UserRepositoryInterface
{
public function findOne($data);
}
UserRepository.php
use App\Models\User;
class UserRepository implements UserRepositoryInterface
{
private $model;
public function __construct(User $user)
{
$this->model = $user;
}
public function findOne($data)
{
if (empty($data)) return false;
$query = $this->model->with(['userWebsites', 'userWebsites.website', 'role']);
if(!empty($data['id'])) $query = $query->where('id', $data['id']);
return $query->first();
}
}
RepositoryServiceProvider.php
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
}
}
AuthControllerTest.php
class AuthControllerTest extends TestCase
{
public $authController;
public $userRepositoryInterfaceMockery;
public $userServiceMokery;
public function setUp(): void{
$this->afterApplicationCreated(function (){
$this->userRepositoryInterfaceMockery = Mockery::mock(UserRepositoryInterface::class)->makePartial();
$this->userServiceMokery = Mockery::mock((new UserService(
$this->app->instance(UserRepositoryInterface::class, $this->userRepositoryInterfaceMockery)
))::class)->makePartial();
$this->authController = new AuthController(
$this->app->instance(UserService::class, $this->userServiceMokery)
);
}
}
public function test_abc_function(){
$res = $this->authController->abc(1);
}
}
I was still able to instantiate the AuthController and it ran to the UserService. but it can't get the UserRepositoryInterface argument. I think the problem is that I passed the Interface in the constructor of the UserService. .What happened, please help me, thanks
I don't know where $userService comes from to your controller's constructor, but it seems like it comes from nowhere. You need to pass it as argument, so Laravel can resolve its instance in service container.
class AuthController extends Controller {
private $userService;
public function __construct(
private AuthService $authService,
UserRepositoryInterface $userRepository
) {
$this->userService = new UserService($userRepository);
}
public function show($id)
{
return $this->userService->findOne(['id' => $id]);
}
}
Also there is literally no findOne method in UserService. You need one there.
class UserService
{
public function __construct(private UserRepositoryInterface $userRepository)
{
}
public function findOne(array $data)
{
return $this->userRepository->findOne($data);
}
}
Update
In that case you need this in service provider:
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
$this->app->bind(UserService::class, function ($app) {
return new UserService($app->make(UserRepositoryInterface::class));
});

Interface Segregation Principle: How to split big interface with a lot of optional methods

Suppose I have an interface:
interface WorkerInterface
{
public function doCommonAction1(CommonAction1Params $params): CommonAction1Result;
public function doCommonAction2(CommonAction2Params $params): CommonAction2Result;
/**
* #return void
*
* #throws UnsupportedMethodException
*/
public function doSpecificAction1(SpecificAction1Params $params): SpecificAction1Result;
/**
* #return void
*
* #throws UnsupportedMethodException
*/
public function doSpecificAction2(SpecificAction2Params $params): SpecificAction2Result;
}
The problem is that methods doSpecificAction1 and doSpecificAction2 are optional and supported not by all workers. Worker can support doCommonAction1 and doCommonAction2 only, as well as doCommonAction1, doCommonAction2 and doSpecificAction1, or doCommonAction1, doCommonAction2, doSpecificAction2, or all methods together.
Also I have a WorkerFactory:
class WorkerFactory
{
public function createWorker(string $workerId): WorkerInterface
{
// worker is created here
}
}
Then I have a controller:
class ActionController {
public function commonAction1(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
$worker->doCommonAction1(new CommonAction1Params());
}
public function commonAction2(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
$worker->doCommonAction2(new CommonAction2Params());
}
public function specificAction1(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
try {
$worker->doSpecificAction1(new SpecificAction1Params());
} catch (UnsupportedMethodException $e) {
// do something
}
}
public function specificAction2(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
try {
$worker->doSpecificAction2(new SpecificAction2Params());
} catch (UnsupportedMethodException $e) {
// do something
}
}
}
It's obvious that now my code violates Interface Segregation Principle. I would like to refactor it.
OK, I try doing something like this:
interface WorkerInterface
{
public function doCommonAction1(CommonAction1Params $params): CommonAction1Result;
public function doCommonAction2(CommonAction2Params $params): CommonAction2Result;
}
interface SpecificAction1AwareInterface
{
public function doSpecificAction1(SpecificAction1Params $params): SpecificAction1Result;
}
interface SpecificAction2AwareInterface
{
public function doSpecificAction2(SpecificAction2Params $params): SpecificAction2Result;
}
So now my workers will look like this:
class Worker1 implements WorkerInterface {}
class Worker2 implements WorkerInterface, SpecificAction1AwareInterface {}
class Worker3 implements WorkerInterface, SpecificAction1AwareInterface, SpecificAction2AwareInterface {}
And now my controller changes to this:
class ActionController {
public function commonAction1(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
$worker->doCommonAction1(new CommonAction1Params());
}
public function commonAction2(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
$worker->doCommonAction2(new CommonAction2Params());
}
public function specificAction1(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
if ($worker instanceof SpecificAction1AwareInterface) {
$worker->doSpecificAction1(new SpecificAction1Params());
} else {
// do something
}
}
public function specificAction2(string $workerId, WorkerFactory $factory)
{
$worker = $factory->createWorker($workerId);
if ($worker instanceof SpecificAction2AwareInterface) {
$worker->doSpecificAction1(new SpecificAction2Params());
} else {
// do something
}
}
}
But this code seems ugly I think. I'm not sure that using instanceof is a good idea especially because SpecificAction1AwareInterface and SpecificAction2AwareInterface aren't related to WorkerInterface at all.
So are there any design patterns suitable for my situation? Thank you in advance.

Php override construction error in extends controller mvc structure

In php mvc structure I have this base controller class and add the constructor like this :
namespace App\Core;
/**
* Controller class
*/
class Controller
{
/** #var View View The view object */
public $View;
public $templates;
public $app;
/**
* Construct the (base) controller. This happens when a real controller is constructed, like in
* the constructor of IndexController when it says: parent::__construct();
*/
public function __construct()
{
$this->app = \App\Core\System\App::instance();
$this->Language = new Language('en-gb');
$this->templates = new \League\Plates\Engine(Config::get('PATH_VIEW'));
$this->Url = new \App\Core\Url(Config::get('URL'),Config::get('URL'));
}
public function loadModel($name) {
$path = '\App\Catalog\Model\\'.$name;
$this->model = new $path;
return $this->model;
}
public function loadController($name) {
$path = '\App\Catalog\Controller\\'.$name;
$this->controller = new $path;
return $this->controller;
}
}
Now in action (ie edit account) controller i have :
namespace App\Catalog\Controller\Account;
use App\Core\Config;
use App\Core\Csrf;
use App\Core\Response;
use App\Core\Session;
class EditAccount extends \App\Core\Controller
{
public function __construct()
{
parent::__construct();
//Auth::checkAuthentication();
}
public function index()
{
}
public function action()
{
}
}
Now, I work in PhpStorm and see this override error:
How do can in Fix this error?
Note: If I remove extends \App\Core\Controller from EditAccount class, error fixed But I need to extends \App\Core\Controller.

Can I read input from GET inside a Controller Factory?

This question is not explicitly about ZF2, but I often take ques from ZF2 for my code. That said, most ZF2 examples I have seen process input inside a Controller Action.
Example:
class YourController extends AbstractActionController
{
public function doStuffAction()
{
// ZF2's way to get input from $_GET variable
$product = $this->getEvent()->getRouteMatch()->getParam('product');
// Process
$processor = (new ProcessorFactory())->getProcessor($product);
$output = $processor->processInput($data);
}
}
Now, I would like to inject a Processor into my Controller. Not create it inside the controller like I am doing above. But since Processor depends on knowing the $product, which is only gotten from $_GET, I do not see any other way.
If I want to inject Processor into Controller, I have to move the line that populates $product variable outside of the Controller as well.
How can I do so without breaking OOP, ZF2, design patterns badly? As in, I am under the impression that anything to do with $_GET is to be done inside a Controller, and not inside a ControllerFactory. Unless perhaps I can break this pattern?
If you just want to apply the Dependency Inversion principle. Applying the D of SOLID acronym, only a few changes are needed.
class YourController
{
/**
* #var ProcessorFactory
*/
protected $processorFactory;
public function __construct(ProcessorFactory $processorFactory)
{
$this->processorFactory = $processorFactory;
}
public function doStuffAction()
{
$product = $this->getEvent()->getRouteMatch()->getParam('product');
$processor = $this->processorFactory->getProcessor($product);
}
}
You could improve by typehinting to an Interface (SOLID)
class YourController
{
/**
* #var ProcessorFactoryInterface
*/
protected $processorFactory;
public function __construct(ProcessorFactoryInterface $processorFactory)
{
$this->processorFactory = $processorFactory;
}
public function doStuffAction()
{
$product = $this->getEvent()->getRouteMatch()->getParam('product');
$processor = $this->processorFactory->getProcessor($product);
}
}
Now, if you want don't want your Controller to be responsible of initiating the creating process (SOLID), you can split it up some more.
class YourController
{
/**
* #var ProcessorInterface
*/
protected $processor;
public function __construct(ProcessorInterface $processor)
{
$this->processor = $processor;
}
public function doStuffAction()
{
$processor = $this->processor;
}
}
class ControllerFactory
{
/**
* #var ProcessorFactory
*/
protected $processorFactory;
public function __construct(ProcessorFactory $processorFactory)
{
$this->processorFactory = $processorFactory;
}
public function create()
{
return new YourController($this->processorFactory->getProcessor());
}
}
class ProcessorFactory
{
/**
* #var RouteMatch
*/
protected $routeMatch;
public function __construct(RouteMatch $routeMatch)
{
$this->routeMatch = $routeMatch;
}
public function getProcessor()
{
$processor = $this->createProcessor();
// do stuff
return $processor;
}
protected function createProcessor()
{
$product = $this->routeMatch->getParam('product');
// create processor
return $processor;
}
}
The following code would get you your controller.
$controllerFactory = new ControllerFactory(new ProcessorFactory(new RouteMatch()));
$yourController = $controllerFactory->create();
Now above code is more general code and not adapted for ZF2. A good move would then to involve the ZF2's servicemanager.
class YourController extends AbstractActionController
{
/**
* #var ProcessorInterface
*/
protected $processor;
public function __construct(ProcessorInterface $processor)
{
$this->processor = $processor;
}
public function doStuffAction()
{
$processor = $this->processor;
}
}
class YourControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllers)
{
$services = $controllers->getServiceLocator();
$processorFactory = $services->get('ProcessorFactory');
return new YourController($processorFactory->getProcessor());
}
}
class ProcessorFactory
{
/**
* #var RouteMatch
*/
protected $routeMatch;
public function __construct(RouteMatch $routeMatch)
{
$this->routeMatch = $routeMatch;
}
public function getProcessor()
{
$processor = $this->createProcessor();
// do stuff
return $processor;
}
protected function createProcessor()
{
$product = $this->routeMatch->getParam('product');
// create processor
return $processor;
}
}
class ProcessorFactoryFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $services)
{
return new ProcessorFactory($services->get('RouteMatch'));
}
}
Above services/controllers and their factories should be registered with their ServiceManager/ControllerManager
$config = [
'controllers' = [
'factories' [
'YourController' => 'YourControllerFactory',
],
],
'service_manager' = [
'factories' [
'ProcessorFactory' => 'ProcessorFactoryFactory',
],
],
];
When a request gets dispatch to YourController, the ControllerManager returns a YourController instance with a Processor injected. Which Processor it gets depends on the request (a parameter inside RouteMatch).

Programming Paradigms: Strongly Typed parameter with Inheritance

Warning: might cause TL:DR
I am working with PHP 5.3.10 and have the following problem. I do have an abstract class DataMapper, which is extended for the specific DataModel I want to persist. The following code does this trick:
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public abstract function save(IModel $model); // DISCUSSION
/* more helper functions here */
}
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic ... */ }
public function fetchAll() { /* ...magic ... */ }
public function save(IModel $model) { /* ...magic ... */ } // DISCUSSION
}
interface IModel {
public function setOptions(array $options);
public function toArray();
}
abstract class Model implements IModel {
protected $_fields = array();
protected $_data = array();
public function setOptions(array $options) { /* ...magic ... */ }
public function toArray() { /* ...magic ... */ }
public function __construct(array $options = null) { /* ...magic ... */ }
public function __set($name, $value) { /* ...magic ... */ }
public function __get($name) { /* ...magic ... */ }
}
class PersonModel extends Model {
protected $_fields = array('id', 'name', 'passhash', /*...*/);
public function setId($value) {
/* ...Validation happening... */
$this->_data['id'] = $value;
return $this;
}
public function checkPassword($password) { /* ...magic... */ }
}
This works fine, but is really quirky for my feeling.
As you can see, I've used an interface IModel to be able to tell the DataMapper, that it does need a certain set of parameters and methods. However, some Models do have extra methods needed by the corresponding DataMapper - in the example, a checkPassword() method, which is used test a password against the stored hash value. This method may also instruct the DataMapper to rehash the just tested password and update it due to new requirements (e.g. an increased difficulty for a password hash function).
So what I actually want is to change the signature of PersonMapper to PersonMapper::save(PersonModel $model) - and e.g. in another DataMapper toPostMapper::save(PostModel $model), etc. This is due to these DataMappers needing a certain signature. So my ideal solution looks like this:
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public abstract function save(Model $model); // UPDATED
}
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(PersonModel $model) { /* ...magic... */ } // UPDATED
}
abstract class Model { /* ...unchanged... */ }
class PersonModel extends Model { /* ...unchanged... */ }
Notice the Update save-Methods in the abstract class and its implementation. Since PersonModel is inherited from Model, thus obviously having a common base set of signatures, I would expect this to work just fine. But it doesn't - PHP complains about a changed interface in the childclass PersonMapper
My Questions:
Is there another solution working with PHP 5.3.10 that expresses the relationship better?
Does it work in a later version of PHP, so that it might be worth upgrading the server?
You might try using interfaces instead.
interface OtherModel {
public function getThis();
}
interface OtherOtherModel {
public function getThat();
}
Your Model Class might implement one or more interfaces...
class PersonModel extends Model implements OtherModel {
protected $_fields = array('id', 'name', 'passhash', /*...*/);
public function setId($value) {
/* ...Validation happening... */
$this->_data['id'] = $value;
return $this;
}
public function checkPassword($password) { /* ...magic... */ }
public function getThis() {
// ...
}
}
Your concrete Mapper Class can use the instanceof to check if this Model does what it should.
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(Model $model) {
// verify that certain methods are implemented...
// throw an exception or reacting accordingly
print ($model instanceof PersonModel)? 'yes' : 'no';
print ($model instanceof OtherOtherModel)? 'yes' : 'no';
}
}
Another possible approach might be the following:
<?php
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public function save(Model $model) {
throw new Exception('You have to implement this!');
}
}
Throw an Exception if the save method is not overriden in an inheriting class.
Now you can really use a different typehint.
This will work:
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(PersonModel $model) {
// do something
}
}
I could think of another possible approach, by using interfaces to define the implementation.
Like for example:
interface PersonModelAware {
public function save(PersonModel $model);
}
interface OtherModelAware {
public function save(OtherModel $model);
}
etc. Your abstract method might have a default save method or no save method at all. The inheriting class will implement the interface it needs.
To sum it up, making your type more specific will not work as the abstract method clearly states it expects a Model.

Categories