In my entity I have a property that is a value object.
class Meeting
{
/** #var MeetingStatus */
private $status
/**
* #param MeetingStatus|null $status
*/
public function setStatus(MeetingStatus $status = null)
{
$this->status = $status ?: new MeetingStatus(MeetingStatus::DRAFT);
}
}
The only way I see to both typehint and to allow null is by setting null as default parameter value. But it seems very weird to allow a setter without a required parameter.
$meeting->setStatus(); // wut?
Does anyone have an elegant solution for this?
Set public $status to MeetingStatus::DRAFT
public function __construct() {
$this->status = MeetingStatus::DRAFT;
}
and don't call setter if you have it as NULL.
OR
Always set meeting status. You can set it to MeetingStatus::UNDEFINED where const UNDEFINED = null;
You may set default value in constructor, almost like in previous answer:
private $status;
public function __construct()
{
$this->status = new MeetingStatus(MeetingStatus::DRAFT);
}
public function setStatus(MeetingStatus $status)
{
$this->status = $status;
}
Now you doesn't need to make member public or allow null in setStatus()
Related
I wanna to test my entities created from doctrine, but i don`t know how to test id if they are generated themself while object save to database.
class Dish
{
private ?int $id;
private Collection $prices;
public function __construct()
{
$this->prices = new ArrayCollection();
}
public function identifier(): string
{
return $this->id;
}
public function isEquals(Dish $dish): bool
{
return $this->identifier() === $dish->identifier();
}
public function addPrice(float $price)
{
$this->prices->add(new DishPrice($this, $price));
}
}
class DishPrice
{
use DeletedEntityTrait;
private ?int $id;
private float $price;
private Dish $dish;
public function __construct(Dish $dish, float $price)
{
$this->dish = $dish;
$this->price = $price;
}
public function identifier(): string
{
return $this->id;
}
public function isEquals(Dish $dish): bool
{
return $this->identifier() === $dish->identifier();
}
}
How to test isEquals methods in both classes?
Basically you have two options.
First option is functional test. You can create two entities and persist them to the database.
Second option is pass ID to the constructor. To be able to do this you can add method to repository that will provide you next ID for your entity in service layer:
interface DishRepository
{
public function getNextIdentity(): int;
}
$id = $repository->getNextIdentity();
$dish = new Dish($id);
and in unit-tests you can pass to the constructor whatever ID that you want
$testId = 111;
$dish = new Dish($testId);
Try to use uuid instead of int and testing will be easer) also, you will have more benefits while using uuids.
What is the proper way to handle optional parameters on a service request?
Lets say in this scenario i want to have also $title as optional parameter
<?php
namespace Lw\Application\Service\Wish;
class AddWishRequest
{
private $userId;
private $email;
private $content;
public function __construct($userId, $email, $content)
{
$this->userId = $userId;
$this->email = $email;
$this->content = $content;
}
public function userId()
{
return $this->userId;
}
public function email()
{
return $this->email;
}
public function content()
{
return $this->content;
}
}
Example from here
Usually in DDD and following the rules of clean code also, if you have optional parameters, you have multiple constructors, two in this case:
One for just the mandatory arguments.
One for all the arguments including the optional but in this constructor it would be mandatory too.
If you wanna construct the object without the optional argument you call the first one. And if you wanna supply a non null optional argument you use the second one.
Usually you should use factory methods with meaningful names, and hide the constructors.
AddWishRequest.create ( userId, email, content)
AddWishRequest.createWithTitle ( userId, email, content, title )
You can use optional arguments in any function call, also the constructor. Best practice is, to preceed "get" to the getters.
public function __construct($userId, $email, $content, $title = "")
means, $title is an optional argument. When not supplied, it is set to an empty string. You also could provide any other type or value.
namespace Lw\Application\Service\Wish;
class AddWishRequest
{
private $userId;
private $email;
private $content;
private $title;
public function __construct($userId, $email, $content, $title = "")
{
$this->userId = $userId;
$this->email = $email;
$this->content = $content;
$this->title = $title;
}
public function getUserId()
{
return $this->userId;
}
public function getEmail()
{
return $this->email;
}
public function getContent()
{
return $this->content;
}
public function getTitle()
{
return $this->title;
}
}
Update
If you just declare a property like
private $property
then accessing it via $this->property with always be null (until you set a value). You should make the getter responsible for returning the correct values.
Following example will always return an array making use of the NULL-coalesce operator:
if $something is true (or has an array content) will return $something
else will return empty array
public function getSomething() : array {
return $this->something ?? [];
}
I can not load data to properties using this construction I receive null in dump
<?php
namespace App\Domain\Good;
class GoodDto
{
public $name;
public $articul;
public $price;
public $type;
public $qnt;
public $discount;
public $category;
public $description;
public $description2;
public $color;
public function load($data)
{
$this->name = $data['name'];
$this->articul = $data['artikul'];
$this->price = $data['price'];
$this->type = (isset($data['type'])) ? $data['type'] : null;
$this->qnt = $data['count'];
$this->discount = $data['spinner-decimal'];
$this->category = $data['id_cat'];
$this->description = $data['editor1'];
$this->description2 = '';
$this->color = $data['color'];
//$this->user_id = Auth::user()->id;
}
public static function fromRequest($request)
{
dump('inp=>',(new self ())->load($request->input()));
return (new self ())->load($request->input());
}
}
Please explain to me why I receive null while request->input() is an array, I call it from another place
$dto=GoodDto::fromRequest($request);
Method chaining, returns the last return from the chain. The other returns are used to call the next link in the chain.
(new self ())->load()
So load() needs to return $this
public function load($data)
{
...
return $this;
}
Currently it returns null, which is why it returns null.
See you are not saving the instance from the constructor, instead you pass it to load by enclosing it within the (....). By pass it I mean you call the load method on the return from the constructor.
You can test this like so:
class foo{
function load(){
return $this;//return this
}
}
var_dump((new foo)->load());
class bar{
function load(){
//return null
}
}
var_dump((new bar)->load());
Output
//return this
object(foo)#1 (0) {
}
//return null
NULL
sandbox
The second class in the example above class bar, is essentially what you are doing.
PS. forgot to scroll down on your post at first ... lol ... So I had to update my answer.
Bonus
You can also simplify the load code like this:
public function load($data)
{
foreach($data as $prop=>$value){
if(property_exists($this,$prop)) $this->$prop = $value;
}
return $this;
}
This way if you add new properties you don't have to edit the load method ever again, you just have to name the array elements the same as the class properties. You can even throw an error if the property does not exist if you want, by adding an else to the condition etc...
Personally, when I do this I prefer to call a set method like this:
//eg. $data = ['foo' => '2019-06-16']
public function load(array $data)
{
foreach($data as $prop=>$value){
$method = 'set'.$prop; //$method = 'setfoo' using the example above
if(method_exists($this,$method )){
$this->$method($value); //calls 'setfoo' with '2019-06-16'
}else{
throw new Exception('Unknown method '.$method);
}
}
return $this;
}
public function setFoo($date){
$this->foo = new DateTime($date);
}
Then you can apply some transforms to the data etc... PHP method names are not case sensitive. You can even combine these by first checking for a method then a property then throw the error etc...
Cheers.
I have a basic problem with following code:
<?php
interface UserInterface
{
public function getId();
public function getName();
}
class User implements UserInterface
{
private $_id;
private $_name;
public function __construct($id, $name)
{
$this->_id = $id;
$this->_name = $name;
}
public function getId()
{
return $this->_id;
}
public function getName()
{
return $this->_name;
}
}
class UserMapper
{
public function insert(UserInterface $user)
{
... insertion code
}
}
?>
The insert method of the UserMapper expects an UserInterface object. So I create one:
<?php
$user = new User(1, "Chris");
$userMapper = new UserMapper();
$userMapper->insert($user);
?>
My problem is, that the user's id is an auto-increment value that is coming from the database after inserting the object. But the object's constructor forces me to define an id because the object would not be complete without an id. How to solve that general problem?
To pass the id as a second parameter to the constructor woth a default value is not an option, because in my understanding the object would be incomplete without having an id.
Thanks for your help.
You can pass null:
$user = new User(null, "Chris");
$_id is not checked for a valid integer and with null you know that this model has no valid ID yet.
I have a php singleton session class as follows.
class Session {
static private $_instance = NULL;
private function __construct()
{
session_start();
}
/**
* Prevents the class from being cloned
* #return NULL
*/
private function __clone() { }
/**
* Returns the singleton instance of this class
* #return Session
*/
public static function getInstance()
{
if (!self::$_instance) {
self::$_instance = new Session();
}
return self::$_instance;
}
public function __get($key) {
if (isset($_SESSION[$key])) {
return $_SESSION[$key];
}
return NULL;
}
public function __set($key, $value)
{
$_SESSION[$key] = $value;
}
public function __isset($key) {
return isset($_SESSION[$key]);
}
public function __unset($key) {
unset($_SESSION[$key]);
}
}
I can create an object as follows
$session = Session::getInstance();
$session->name = 'some name';
I can also get the value like
echo $session->name;
The problem is, i want to pass an array to this object and it is not working. for example, i wan to set something like
$_SESSION['user']['data'] = array('name'=>'some name',"empId"=>'123');
I am trying like this.
$session->['user']['data'] = array('name'=>'some name',"empId"=>'123');
but it is not working. Could you please suggest what is wrong.
The workaround in this case would be to use:
public function &__get($key) {
if (isset($_SESSION[$key])) {
return & $_SESSION[$key];
}
return NULL;
}
You need to modify the __get() method, because an assignment like
$session->user['data'] = ...
will actually retrieve the [user] key, and then try to assign a new subarray [data] to that temporary array result.
Also note that $session->['user']['data'] is invalid syntax. You either need $session->user['data'] or $session->{'user'}['data'].
Anyway, I think it is probably not a good idea to use a wrapper if you often want to do assignments like that. (I do actually have something very similar.)
$session->user = array('data' => array('name'=>'some name',"empId"=>'123'));
Make sure you don't overwrite anything else in user you want to keep