This question already has answers here:
Enumerations on PHP
(39 answers)
Closed 1 year ago.
I basically want an enum-like class in PHP. My enum should have a few values (they are all static). Let's say I want to have an enum that has an ID (the ID must never, ever change), and a name (the name must not change during runtime, it can change during compile-time -- e.g. from changes made to the code). The state must not have any side-effects (i.e. they are only getters and instance variables cannot be changed). A state must also not be created during runtime except when initializing the class.
For my concert example, I have three possible states: OPEN, CLOSED, and UNKOWN:
class MyState {
const STATE_OPEN = new MyState(1, "open");
const STATE_CLOSE = new MyState(2, "closed");
const STATE_UNKOWN = new MyState(3, "unkown");
private $name;
private $state;
private function __construct(int $state, string $name) {
$this->state = $state;
$this->name = $name;
}
public function getName() : string {
return $this->name;
}
public function getState() : int {
return $this->state;
}
public function getStateByID(int $state) : MyState { ... }
public function getStateByName(string $name) : MyState { ... }
}
I want to refer to each state unambiguously in my code (MyState::STATE_OPEN, etc). This should also ensure that a compiler/linter/etc can verify that the state actually exists (there is no guarantee that getStateByID does not throw an exception).
There are also some (de)serializers that can load a state after it has been saved (by using the never changing ID).
Unfortunately, I cannot set const or static instance variables if their object is from the same class. I get the following error:
Constant expression contains invalid operations
How can this be best accomplished in PHP? I know I can easily create an enum with just an int (class MyState { const STATE_OPEN = 1; }) but that has a major disadvantage: the enum is not an object and thus cannot have methods. I cannot use type hinting this way and I might pass an invalid enum. E.g., function takeMyState(int $state) can actually use an enum that has never been defined.
Replace your const lines
const STATE_OPEN = new MyState(1, "open");
by public functions:
public function STATE_OPEN() { return new MyState(1, "open"); }
resulting in an example program like this:
class MyState {
public function STATE_OPEN() { return new MyState(1, "open"); }
public function STATE_CLOSED() { return new MyState(2, "closed"); }
public function STATE_UNKNOWN() { return new MyState(3, "unknown"); }
private $name;
private $state;
private function __construct(int $state, string $name) {
$this->state = $state;
$this->name = $name;
}
public function getName() : string {
return $this->name;
}
public function getState() : int {
return $this->state;
}
}
$state = MyState::STATE_CLOSED();
echo $state->getName() . "(" . $state->getState() . ")\n";
Related
I have a DTO with typed PHP variables:
class CreateMembershipInputDto extends BaseDto
{
public bool $is_gift;
public int $year;
public string $name;
public \DateTime $shipping_date;
public ContactInputDto $buyer;
public ?ContactInputDto $receiver;
}
I am trying to make some kind of automapper, which fills the properties, but I need to check the type of the variable but that seems to be impossible.
class BaseDto
{
public function __construct($json)
{
$jsonArray = json_decode($json, true);
foreach($jsonArray as $key=>$value){
$type = gettype($this->$key);
if($type instanceof BaseDto)
$this->$key = new $type($value);
else
$this->$key = $value;
}
}
}
ContactInputDto:
class ContactInputDto extends BaseDto
{
public string $firstname;
public string $lastname;
public string $street_housenumber;
public string $postal_code;
public string $place;
public string $country;
public string $email;
public string $phone;
}
Is it somehow possible to make that line "gettype($this->$key)" work, without php throwing the following error:
Typed property App\Dto\CreateMembershipInputDto::$is_gift must not be accessed before initialization
While the manual does not seem to currently document it, there is a method added to ReflectionProperty to allow you to get the type. This is actually specified in the RFC for typed properties
Here's how you would use it:
class CreateMembershipInputDto extends BaseDto {
public bool $is_gift;
public int $year;
public string $name;
public \DateTime $shipping_date;
public ContactInputDto $buyer;
public ?ContactInputDto $receiver;
}
class BaseDto
{
public function __construct($json)
{
$r = new \ReflectionClass(static::class); //Static should resolve the the actual class being constructed
$jsonArray = json_decode($json, true);
foreach($jsonArray as $key=>$value){
$prop = $r->getProperty($key);
if (!$prop || !$prop->getType()) { continue; } // Not a valid property or property has no type
$type = $prop->getType();
if($type->getName() === BaseDto::class) //types names are strings
$this->$key = new $type($value);
else
$this->$key = $value;
}
}
}
If you want to check if the type extends BaseDto you will need (new \ReflectionClass($type->getName()))->isSubclassOf(BaseDto::class)
Note that getName refers to to ReflectionNamedType::getName. Prior to PHP 8 this was the only possible instance you could get $prop->getType() however starting PHP 8 you may also get a ReflectionUnionType which contains multiple types
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'm trying to access getters of a custom object. I wanted to send the object to the constructor params (I'm currently sending to the build method (but
My class representing an object that I'd like to display its values :
class User{
private $id;
private $name;
private $email;
public function __construct(int $id, string $name, string $email) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
}
public function getName(): string {
return $this->name;
}
... // the other getters
}
In my Page :
$myUser = new User(1, "toto", "toto#url.com");
$blockUser = new UserBlock($sqlConnection);
$blockUser->setUser($myUser);
$blockUser->build();
interface IBlock :
interface IBlock {
function __construct($sqlConnection);
function build();
}
UserBlock class:
class UserBlock implements IBlock {
private $myUser;
public function __construct($sqlConnection) {
...
}
public setUser($myUser) {
$this->myUser = $myUser;
}
function build() {
echo "Name = ".$this->myUser->getName(); // ERROR: Doesn't know the type of $this->myUser so can't access to getName method.
$tempVarUser = $this->myUser;
echo "Name = ".$tempVarUser->getName(); // ERROR: Doesn't know the type of $tempVarUser so can't access to getName method.
}
}
With my code structure, how can I access to the properties and methods of the object User represented by the variable $this->myUser in the UserBlock class ?
I have to remove the implements of the Interface and send the User object to the build method as parameter (which is not allowing by my interface here).
Hope this is clear. Thanks for your help.
How can I figure out in what class a reference to a variable was initiated (and currently exists)?
Example:
<?php
class MyClass {
public $array = array(
"this",
"is",
"an",
"array"
);
}
$class = new MyClass();
$arrayReference = &$class->array;
GetClassForVariable($arrayReference); //Should return "MyClass"
?>
My best bet is some kind of Reflection, but I haven't found any functions that seem suitable for this.
Edit:
A better suited example for what I want is the following:
<?php
class API_Module {
public $module;
public $name;
private $methods = array();
public function __construct($module, $name) {
$this->module = $module;
$this->name = $name;
$this->methods["login"] = new API_Method($this, "login", "Login");
}
public function GetMethod($method) {
return $this->methods[$method];
}
public function GetURL() {
return $this->module; //Should return "session"
}
}
class API_Method {
public $method;
public $name;
private $parentReference;
private $variables = array();
public function __construct(&$parentReference, $method, $name) {
$this->parentReference = $parentReference;
$this->method = $method;
$this->name = $name;
$this->variables["myvar"] = new API_Variable($this, "myvar");
}
public function GetURL() {
return $this->GetParentURL() . "/" . $this->method; //Should return "session/login"
}
public function GetVariable($variableName) {
return $this->variables[$variableName];
}
private function GetParentURL() {
// Need to reference the class parent here
return $this->parentReference->GetURL();
}
}
class API_Variable {
public $name;
private $parentReference;
public function __construct(&$parentReference, $name) {
$this->parentReference = $parentReference;
$this->name = $name;
}
public function GetURL() {
return $this->GetParentURL() . "/" . $this->name; //Should return "session/login/myvar"
}
private function GetParentURL() {
// Need to reference the class parent here
return $this->parentReference->GetURL();
}
}
$sessionModule = new API_Module("session", "Session");
var_dump($sessionModule->GetMethod("login")->GetVariable("myvar")->GetURL()); //Should return "session/login/myvar"
?>
Now, this works fine, but I'd love to be able to do this without using $parentReference in every single subvariable. It might not be possible, but I'd love to know whether it is or not.
For your example:
$class = new MyClass();
$arrayReference = &$class->array;
GetClassForVariable($arrayReference); //Should return "MyClass"
to find out to which variable originally the alias $arrayReference refers to is not possible in PHP. There is no function available resolving the aliases.
Additionally $class->array is just a variable on it's own. So you would also need to find out based on a value in which class it was defined. That is not possible as well, similar to that PHP does not offer anything to resolve a variable alias, it also does not offer anything to learn about the definition of a variable.
So in short PHP does not have a ReflectionVariable class available ;) I wonder if it is even possible.
The get_class() function should work:
http://php.net/manual/en/function.get-class.php
I agree with GRoNGoR that you shouldn't need to get the parent class of a property of an instantiated object. You could instead just get the name of the class before accessing the property. For example:
$class = new MyClass();
$parent_class = get_class($class); // returns "MyClass"
$arrayReference = &$class->array;
Not sure why you'd need the parent class of the property when you have the object instance and can easily get the parent class from there.
I would like one of the attributes of my object to be an array of another type of object.
How do I represent this (i.e. public $someObjectArray;)
What would be the syntax to add an entry to this attribute?
What would be the syntax to reference the object?
To provide some (hopefully) useful context.
Lets assume that the object is property which has some attributes one of which is a number of tenants who will have their own properties like name, age etc...
class Tenant {
// properties, methods, etc
}
class Property {
private $tenants = array();
public function getTenants() {
return $this->tenants;
}
public function addTenant(Tenant $tenant) {
$this->tenants[] = $tenant;
}
}
If the Tenant model has some sort of identifiable property (id, unique name, etc), you could factor that in to provide better accessor methods, eg
class Tenant {
private $id;
public function getId() {
return $this->id;
}
}
class Property {
private $tenants = array();
public function getTenants() {
return $this->tenants;
}
public function addTenant(Tenant $tenant) {
$this->tenants[$tenant->getId()] = $tenant;
}
public function hasTenant($id) {
return array_key_exists($id, $this->tenants);
}
public function getTenant($id) {
if ($this->hasTenant($id)) {
return $this->tenants[$id];
}
return null; // or throw an Exception
}
}