Implement a State Pattern with Doctrine ORM - php

I have a class that is using the State pattern. Here's a simple example
/**
* #Enitity
**/
class Door
{
protected $id;
protected $state;
public function __construct($id, DoorState $state)
public function setState(DoorState $state)
{
$this->state = $state;
}
public function close()
{
$this->setState($this->state->close())
}
...
}
interface DoorState
{
public function close;
public function open;
public function lock;
public function unlock;
}
class DoorAction implements DoorState
{
public function close()
{
throw new DoorError();
}
...
}
then several classes that define the appropriate actions in the states
class OpenedDoor extends DoorAction
{
public function close()
{
return new ClosedDoor();
}
}
So I would have some thing like
$door = new Door('1', new OpenedDoor());
DoctrineDoorRepository::save($door);
$door->close();
DoctrineDoorRepository::save($door);
How would I implement the mapping in Doctrine so I can persist it?
I'm hung up on the $state property. I would like to save the whole DoorAction based object but do I have to the map the DoorAction super class or each individual sub class?
I've looked at implementing it using Embeddable or SuperMapping but run into problems with each.

Doctrine2 DBAL has a feature in the documentation that allows ENUM's
https://www.doctrine-project.org/projects/doctrine-orm/en/current/cookbook/mysql-enums.html#mysql-enums
When we take the Solution 2: Defining a Type as a base, one could create an own type, for instance called doorstatetype or similar to represent the open/closed state. For instance like this:
<?php
namespace Acme\Model\Door;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class DoorStateType extends Type
{
const ENUM_DOORSTATE = 'enumdoorstate';
const STATE_OPEN = 'open';
const STATE_CLOSED = 'closed';
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return "ENUM('" . self::STATE_OPEN . "', '" . self::STATE_CLOSED . "') COMMENT '(DC2Type:" . ENUM_DOORSTATE . ")'";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $value;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (!in_array($value, array(self::STATE_OPEN, self::STATE_CLOSED))) {
throw new \InvalidArgumentException("Invalid state");
}
return $value;
}
public function getName()
{
return self::ENUM_DOORSTATE;
}
}
And then use it like this:
<?php
namespace Acme\Model\Door;
/** #Entity */
class Door
{
/** #Column(type="enumdoorstate") */
private $state;
public function open()
{
if (!DoorStateType::STATE_OPEN === $this->state) {
throw new \LogicException('Cannot open an already open door');
}
$this->state = DoorStateType::STATE_OPEN;
}
public function close()
{
if (!DoorStateType::STATE_CLOSED === $this->state) {
throw new \LogicException('Cannot close an already closed door');
}
$this->state = DoorStateType::STATE_CLOSED;
}
}
This allows searching for states:
$openDoors = $repository->findBy(array('state' => DoorStateType::STATE_OPEN));
You could basically then have the convertToPHPValue method create objects of the desired states that allow for some logic, like checking if an open door can be locked or similar.
In the case where the state has to be a class that contains logic, you could implement it like this:
First we define a normal state from which we can inherit:
<?php
namespace Acme\Model\Door;
abstract class DoorState
{
// Those methods define default behaviour for when something isn't possible
public function open()
{
throw new \LogicException('Cannot open door');
}
public function close()
{
throw new \LogicException('Cannot close door');
}
abstract public function getStateName();
}
Then the OpenState:
<?php
namespace Acme\Model\Door;
class OpenState extends DoorState
{
const STATE = 'open';
public function close()
{
return new ClosedState();
}
public function getStateName()
{
return self::STATE;
}
// More logic
}
And finally the ClosedState:
<?php
namespace Acme\Model\Door;
class ClosedState extends DoorState
{
const STATE = 'closed';
public function open()
{
return new OpenState();
}
public function getStateName()
{
return self::STATE;
}
// More logic
}
We can then, for persistence, simply use different convert methods:
<?php
namespace Acme\Model\Door;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class DoorStateType extends Type
{
// SQL declarations etc.
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === OpenState::STATE) {
return new OpenState();
}
if ($value === ClosedState::STATE) {
return new ClosedState();
}
throw new \Exception(sprintf('Unknown state "%s", expected one of "%s"', $value, implode('", "', [OpenState::STATE, ClosedState::STATE])));
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return $value->getStateName();
}
}

What if you map state as a string and then:
public function setState(DoorState $state)
{
$this->state = serialize($state);
}
and:
private function state()
{
return unserialize($this->state);
}
public function close()
{
$this->setState($this->state()->close())
}

Related

Write a class in PHP Symfony 4.4 using the design pattern Factory Method to instantiate Service classes

Using Symfony 4.4 with autowiring activated, I want to instantiate a class using the design-pattern FactoryMethod.
The class instantiated is a service with autowired arguments passed into the constructor.
It work well if the constructor is the same for each type of class to instantiate inside the factory method.
But, each service to instantiate has to autowire some specific service in order to work.
I found that we could use the "setter dependency injection". Articles describing it:
https://symfonycasts.com/screencast/symfony-fundamentals/logger-trait
https://symfony.com/doc/4.4/service_container/injection_types.html#setter-injection
I tried to implement the setter dependency injection but the code inside is never executed.
Considering the articles, we should enter the setters with the PHPDoc "#required" immediately after the __construct method has been called (from what I understood).
It doesn't work with my code (see below).
Is my implementation correct?
Is there a better way of doing it?
My code looks like:
// Controller
/**
*#Route("/my_action/{param}")
*/
public function my_action (ThingManagerFactory $thingManagerFactory, $param)
{
$thingManager = $thingManagerFactory->get($param);
$thingManager->doSomething();
}
// ThingManagerFactory
class ThingManagerFactory
{
private $firstManager;
private $secondManager;
private $thirdManager;
public function __construct(FirstManager $firstManager, SecondManager $secondManager, ThirdManager $thirdManager)
{
$this->firstManager = $firstManager;
$this->secondManager = $secondManager;
$this->thirdManager = $thirdManager;
}
public function get($param): ThingManagerInterface
{
if($param == 1) {
return new Thing1Manager(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
);
} elseif($param == 2) {
return new Thing2Manager(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
);
}
throw new \InvalidArgumentException("...");
}
}
// ThingManagerInterface
interface ThingManagerInterface
{
public function __construct(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
);
public function doSomething();
}
// Thing1Manager
class Thing1Manager implements ThingManagerInterface
{
(...)
private $spec1Manager;
public function __construct(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
)
{
(...)
}
/**
* #required
*/
public function setSpecificManager(Spec1Manager $spec1Manager)
{
// this code is never called
$this->spec1Manager = $spec1Manager;
}
public function doSomething()
{
// we pass here before going into setSpecificManager
(...)
}
}
// Thing2Manager class
// is similar to Thing1Manager with multiple other specific managers.
Thank you for your help.
In order to use the design-pattern Factory Method with Symfony, use the Service Locator to provide autowire outside a Controller.
Refactor the code to the following:
// Controller
/**
*#Route("/my_action/{param}")
*/
public function my_action (ThingManagerFactory $thingManagerFactory, $param)
{
$thingManager = $thingManagerFactory->get($param);
$thingManager->doSomething();
}
// ThingManagerFactory
use App\Locator\ThingLocator;
class ThingManagerFactory
{
private $locator;
public function __construct(ThingLocator $locator)
{
$this->locator = $locator;
}
public function get($param): ThingManagerInterface
{
if($param == 1) {
return $this->locator->get(Thing1Manager::class);
} elseif($param == 2) {
return $this->locator->get(Thing2Manager::class);
}
throw new \InvalidArgumentException("...");
}
}
// ServiceLocatorInterface
interface ServiceLocatorInterface
{
public function get(string $id);
}
// ThingLocator
class ThingLocator implements ServiceLocatorInterface, ServiceSubscriberInterface
{
private $locator;
public function __ construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public function get(string $id)
{
if (!$this->locator->has($id)) {
throw new \Exception("The entry for the given '$id' identifier was not found.");
}
try {
return $this->locator->get($id);
} catch (ContainerExceptionInterface $e) {
throw new \Exception("Failed to fetch the entry for the given '$id' identifier.");
}
}
public static function getSubscribedServices()
{
return [
Thing1Manager::class,
Thing2Manager::class,
];
}
}
// ThingManagerInterface
interface ThingManagerInterface
{
public function doSomething();
}
// Thing1Manager
class Thing1Manager implements ThingManagerInterface
{
// ...
private $spec1Manager;
public function __construct($firstManager, $secondManager, $thirdManager, $spec1Manager)
{
// ...
}
// This setter is no more needed. This manager can be added to the constructor method.
// **
// * #required
// */
//public function setSpecificManager(Spec1Manager $spec1Manager)
//{
// if not commented, this code would be called thanks to the Service Locator (which is a Symfony Service Container)
// $this->spec1Manager = $spec1Manager;
//}
public function doSomething()
{
// ...
}
}

Fatal error after declaring function abstract

I have a problem with an error I am getting that says:
Class Car contains 1 abstract method and must therefore be decla
red abstract or implement the remaining methods (Car::accelerate) in C:\xampp
\htdocs\php\learn_php_oop\Car.php on line 58.
This is the code in two files I am using:
Car.php
<?php
/**
* represents generic properties and methods for any type of car
*/
class Car
{
protected $colour, $doorNumber, $fuelType, $rightHandDrive, $accelerate;
public function __construct($rightHandDrive = true)
{
$this->rightHandDrive = $rightHandDrive;
}
public function getColour()
{
return $this->colour;
}
public function setColour($colour)
{
$this->colour = $colour;
}
public function getDoorNumber()
{
return $this->doorNumber;
}
public function setDoorNumber($doorNumber)
{
$this->doorNumber = $doorNumber;
}
public function getFuelType()
{
return $this->fuelType;
}
public function setFuelType($fuelType)
{
$this->fuelType = $fuelType;
}
public function getRightHandDrive()
{
return $this->rightHandDrive;
}
public function setRightHandDrive($rightHandDrive)
{
$this->rightHandDrive = $rightHandDrive;
}
abstract protected function accelerate();
}
?>
Sport_car.php
<?php
include ('Car.php');
/**
* represents sport cars
*/
class Sport_car extends Car
{
public function accelerate()
{
$this->accelerate = 5;
}
}
?>
I have spent some time trying to figure out why this is happening but I just do not know why? Please help.
It's an OOP problem, in your case you must declare your Car Object as Abstract like this :
<?php
/**
* represents generic properties and methods for any type of car
*/
abstract class Car
{
protected $colour, $doorNumber, $fuelType, $rightHandDrive, $accelerate;
public function __construct($rightHandDrive = true)
{
$this->rightHandDrive = $rightHandDrive;
}
public function getColour()
{
return $this->colour;
}
public function setColour($colour)
{
$this->colour = $colour;
}
public function getDoorNumber()
{
return $this->doorNumber;
}
public function setDoorNumber($doorNumber)
{
$this->doorNumber = $doorNumber;
}
public function getFuelType()
{
return $this->fuelType;
}
public function setFuelType($fuelType)
{
$this->fuelType = $fuelType;
}
public function getRightHandDrive()
{
return $this->rightHandDrive;
}
public function setRightHandDrive($rightHandDrive)
{
$this->rightHandDrive = $rightHandDrive;
}
abstract protected function accelerate();
}
?>
Explanations :
A class wich is extended with at least one abstract method in it has to be defined as abstract itself, otherwise you'll get an error

Yii2 getters & setters to format string to float

Ok so currently have this function in controller, which is called multiple times.
public function formatFloat($value)
{
return (float)sprintf('%0.6f', $value);
}
So I am trying to use getters and setters so I can just use
$model->$whatever;
and the formatting will be done.
In my model I have
public function getChargePeak()
{
return $this->charge_peak;
}
public function setChargePeak($value)
{
return $this->charge_peak = (float)sprintf('%0.6f', $value);
}
but when doing
$peak = $model->chargepeak;
var_dump($peak);die;
it is still returning as a string
If the charge_peak property is stored as string and you need a float in you app you should use
public function getChargePeak()
{
return floatval($this->charge_peak);
}
Anyway you should store the values in a coherent way as you use the values in your app ..
http://php.net/manual/en/function.floatval.php
So I suggest u another pattern: decorator and helpers. You should use a controller only to get data from request, prepare it for model and send it to view.
Formatting values is a helper logic. So create a new class
\common\helpers\Number.php
namespace common\helpers;
class Number
{
public static function formatFloat($value)
{
return (float)sprintf('%0.6f', $value);
}
}
Then create decorator for your model:
namespace common\models\decorators;
class YourModelDecorator
{
/**
* YourModel
*/
private $model;
public function __construct(YourModel $model)
{
$this->model = $model;
}
public function __get($name)
{
$methodName = 'get' . $name;
if (method_exists(self::class, $methodName)) {
return $this->$methodName();
} else {
return $this->model->{$name};
}
}
public function __call($name, $arguments)
{
return $this->model->$name($arguments);
}
public function getChargePeak()
{
return \common\helpers\Number::formatFloat($this->model->charge_peak);
}
}
and send it to view for example:
public function actionView($id)
{
$model = $this->loadModel($id);
$this->render('view', [
'model' => new \common\models\decorators\YourModelDecorator($model)
]);
}

Assigning child classes to a variable in parent class

I have these related classes:
class cars {
public $cars;
public function addCar($name, $car)
{
$this->cars[$name] = $car;
}
public function getCars()
{
return $this->cars;
}
public function getCar($name)
{
return $this->cars[$name];
}
public function getParams()
{
return $this->params;
}
}
$cars = new cars();
class bmw extends cars {
private static $_instance = null;
protected $params;
function __construct()
{
$this->params['param'] = 'foo';
}
public static function init()
{
if (self::$_instance === null) {
self::$_instance = new self;
}
return self::$_instance;
}
}
$cars->addCar( 'bmw', bmw::init() );
Basically i need to access all child classes from parent class. And use methods defined in parent class on those defined child classes. Parent class should not be modified when adding new child classes.
In the end this should work like this:
foreach( $cars->getCars() as $car )
{
foreach( $car->getParams() as $key => $param )
echo "$key = $param";
}
What is the proper way to do this?
It's really difficult to provide an help since it's not so clear what you're trying to achieve.
It seems to me that you need Registry Class (carDealer), an abstract class with common (for each child) methods and a child (Bmw) of this.
So, something like:
// You seems to need what is called sometimes a Registry.
// Something which deal with keeping and delivering a group of 'related' classes, as a register.
class CarsDealer
{
public $cars;
public function addCar($name, $car)
{
$this->cars[$name] = $car;
}
public function getCars()
{
return $this->cars;
}
public function getCar($name)
{
return $this->cars[$name];
}
}
// then you need a basic contract for each concrete classes
// that will have the same nature and so will extend it
abstract class Car
{
protected $params;
public function getParams()
{
return $this->params;
}
}
// finally the concrete class
class Bmw extends Car
{
public function __construct($params = null)
{
$this->params['param'] = $params;
}
}
$carsDealer = new CarsDealer();
$carsDealer->addCar('bmw', new Bmw('foo'));
foreach ($carsDealer->getCars() as $car)
{
foreach ($car->getParams() as $key => $param) {
echo "$key = $param";
}
}
Please pay attention to some basic rules/good practices/conventions:
class naming, always capitalized
Responsibilities (a class Bmw shouldn't have a method getCars, at least not in this example)
Visibility of method, parameters
http://www.php-fig.org/psr/psr-1/
http://www.php-fig.org/psr/psr-2/
Just one another approach, if you just need get this 'params' :-)
class cars {
public $cars;
public function addCar($name, $car)
{
$this->cars[$name] = $car;
}
public function getCars()
{
return $this->cars;
}
public function getCar($name)
{
return $this->cars[$name];
}
public function getParams($obj)
{
return $obj->params;
}
}
$cars = new cars();
class bmw extends cars {
private static $_instance = null;
protected $params;
function __construct()
{
$this->params['param'] = 'foo';
}
public static function init()
{
if (self::$_instance === null) {
self::$_instance = new self;
}
return self::$_instance;
}
}
$cars->addCar( 'bmw', bmw::init() );
print_r( $cars->getParams($cars->getCar('bmw')));

Dependency injection in base and derived classes

I have an abstract base Controller class and all action controllers are derived from it.
Base Controller class at construction initializes View object. This View object is used by all action controllers. Each action controller have different dependencies (this is solved by using DI container).
The problem is that base Controller class also needs some dependencies (or parameters),
for example, path to view folder. And the question is - where and how to pass parameters to base Controller class?
$dic = new Dic();
// Register core objects: request, response, config, db, ...
class View
{
// Getters and setters
// Render method
}
abstract class Controller
{
private $view;
public function __construct()
{
$this->view = new View;
// FIXME: How / from where to get view path?
// $this->view->setPath();
}
public function getView()
{
return $this->view;
}
}
class Foo_Controller extends Controller
{
private $db;
public function __construct(Db $db)
{
$this->db = $db;
}
public function barAction()
{
$this->getView()->some_var = 'test';
}
}
require_once 'controllers/Foo_Controller.php';
// Creates object with dependencies which are required in __construct()
$ctrl = $dic->create('Foo_Controller');
$ctrl->barAction();
This is just a basic example. Why is the $view private? Is there a good reason?
class View {
protected $path;
protected $data = array();
function setPath($path = 'standard path') {
$this->path = $path;
}
function __set($key, $value) {
$this->data[$key] = $value;
}
function __get($key) {
if(array_key_exists($key, $this->data)) {
return $this->data[$key];
}
}
}
abstract class Controller {
private $view;
public function __construct($path)
{
$this->view = new View;
$this->view->setPath($path);
}
public function getView()
{
return $this->view;
}
}
class Foo_Controller extends Controller {
private $db;
public function __construct(Db $db, $path)
{
// call the parent constructor.
parent::__construct($path);
$this->db = $db;
}
public function barAction()
{
$this->getView()->some_var = 'test';
}
public function getAction() {
return $this->getView()->some_var;
}
}
class DB {
}
$con = new DB;
$ctrl = new Foo_Controller($con, 'main');
$ctrl->barAction();
print $ctrl->getAction();

Categories