In PHP 8.1, BackedEnum offer a from and tryFrom method to get an enum from a value. How can the same be achieved by non backed enums?
Example BackedEnum:
enum MainType: string
{
case Full = 'a';
case Major = 'b';
case Minor = 'c';
}
var_dump(MainType::tryFrom('a')); // MainType::Full
var_dump(MainType::tryFrom('d')); // null
However this doesn't exist for regular enums.
How would I retrieve a "normal" Enum by name, like:
enum MainType
{
case Full;
case Major;
case Minor;
}
$name = (MainType::Full)->name
var_dump(name); // (string) Full
One option I've found is to simply add a tryFromName function, accepting a string and looping over all the cases like this:
enum MainType
{
case Full;
case Major;
case Minor;
public static function tryFromName(string $name): ?static
{
foreach (static::cases() as $case) {
if ($case->name === $name) {
return $case;
}
}
return null;
}
}
$name = (MainType::Full)->name
var_dump(name); // (string) Full
var_dump(MainType::tryFromName($name)); // MainType::Full
This works, however it seams counter intuitive to enable a foreach loop going over all possibilities just to create an enum.
Therefore the question is, what is the right way to get an Enum in PHP from the name.
You can use Reflection:
trait Enum {
public static function tryFromName(string $name): ?static
{
$reflection = new ReflectionEnum(static::class);
return $reflection->hasCase($name)
? $reflection->getCase($name)->getValue()
: null;
}
}
enum Foo {
use Enum;
case ONE;
case TWO;
}
var_dump( Foo::tryFromName('TWO') ); // enum(Foo::TWO)
var_dump( Foo::tryFromName('THREE') ); // null
Works also for Backed Enums.
My two cents: My package https://github.com/henzeb/enumhancer does that for you, including several other things that might be useful.
Related
I want to get the value of an Enum in PHP by its name.
My enum is like:
enum Status : int
{
case ACTIVE = 1;
case REVIEWED = 2;
// ...
}
Status::from(2) can be used to get "REVIEWED", but how can I resolve the value from the name stored in a string ?
Well, it seems there is not any built-in solution in PHP. I've solve this with a custom function:
enum Status : int
{
case ACTIVE = 1;
case REVIEWED = 2;
// ...
public static function fromName(string $name): string
{
foreach (self::cases() as $status) {
if( $name === $status->name ){
return $status->value;
}
}
throw new \ValueError("$name is not a valid backing value for enum " . self::class );
}
}
Then, I simply use Status::fromName('ACTIVE') and get 1
If you want to mimic the from and tryFrom enum functions, you can also add:
public static function tryFromName(string $name): string|null
{
try {
return self::fromName($name);
} catch (\ValueError $error) {
return null;
}
}
You can use reflection for Backed case:
$reflection = new ReflectionEnumBackedCase(Status::class, 'REVIEWED');
$reflection->getBackingValue(); // 2
$reflection->getValue() // Status::REVIEWED if you need case object
Or enum reflection:
$reflection = new ReflectionEnum(Status::class);
$reflection->getCase('REVIEWED')->getValue()->value // 2
see also ReflectionEnumUnitCase
To get value from the name:
enum Status : int
{
case ACTIVE = 1;
case REVIEWED = 2;
// ...
}
print(Status::REVIEWED->value);
Enum documentation
I use a custom method too, but I return an enum.
The from method returns an enum, not a value. I think the fromName method should return an enum too. Then you have access to all methods of the enum. You don't need use another method - from.
public static function fromName(string $name): self
{
foreach (self::cases() as $status) {
if( $name === $status->name ){
return $status;
}
}
throw new \ValueError("$name is not a valid backing value for enum " . self::class );
}
public static function tryFromName(string $name): self|null
{
try {
return self::fromName($name);
} catch (\ValueError $error) {
return null;
}
}
The constant() function can return the value of a constant using a string variable.
This also applies to Enum class constants as mentioned in the basic enumeration section of the PHP Manual.
$name = 'REVIEWED';
$status = constant("Status::{$name}");
If anyone cares, I have created a library around that.
https://github.com/henzeb/enumhancer
Just add the trait to your enum
enum Status : int
{
use Henzeb\Enumhancer\Enhancers;
case ACTIVE = 1;
case REVIEWED = 2;
// ...
}
Status::get('ACTIVE');
Status::tryGet('ACTIVE');
You then don't have to use values at all. In that case you can also simply use from.
enum Status
{
use Henzeb\Enumhancer\Enhancers;
case ACTIVE;
case REVIEWED;
// ...
}
Status::from('ACTIVE')->key(); // returns 0
Status::tryFrom('Reviewed')->key(); // returns 1
It does so much more than that, but it defeats copying and pasting snippets around.
I just found something "kinda strange" about PHP 7.4 and I am not sure if it is just me missing something or maybe if it is an actual bug. Mostly I am interested in your opinion/confirmation.
So in PHP, you can iterate over objects properties like this:
class DragonBallClass
{
public $goku;
public $bulma = 'Bulma';
public $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
foreach ($dragonBall as $character) {
var_dump($character);
}
RESULT
NULL
string(5) "Bulma"
string(6) "Vegeta"
Now if we start using strongly typed properties like that:
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
foreach ($dragonBall as $character) {
var_dump($character);
}
We will get a different result:
string(5) "Bulma"
string(6) "Vegeta"
Now what is different:
When you DO NOT assign a default value to strongly typed property it will be of Uninitialized type. Which of course makes sense. The problem is though that if they end up like this you cannot loop over them they will simply be omitted - no error, no anything as you can see in the second example. So I just lose access to them.
It makes sense but just imagine that you have a custom Request/Data class like this:
namespace App\Request\Request\Post;
use App\Request\Request\Request;
class GetPostsRequest extends Request
{
public string $title = '';
}
Do you see that ugly string assignment? If I want to make my properties on the class iterable then I have to either:
drop types
assign dummy values
I might want to have an object with typed properties without any values in them to loop over them and populate them if that makes sense.
Is there any better way of doing this? Is there any option to keep types and keep em iterable without having to do this dummy value abomination?
If you want to allow a typed attribute to be nullable you can simply add a ? before the type and give NULL as default value like that:
class DragonBallClass
{
public ?string $goku = NULL;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
In this case NULL is a perfectly legitimate value (and not a dummy value).
demo
Also without using ?, you can always merge the class properties with the object properties lists:
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
$classProperties = get_class_vars(get_class($dragonBall));
$objectProperties = get_object_vars($dragonBall);
var_dump(array_merge($classProperties, $objectProperties));
// array(3) {
// ["goku"]=>
// NULL
// ["bulma"]=>
// string(5) "Bulma"
// ["vegeta"]=>
// string(6) "Vegeta"
// }
Before we start - I think that the answer accepted by me and provided by Casimir is better and more correct than what I came up with(that also goes for the comments).
I just wanted to share my thoughts and since this is a working solution to some degree at least we can call it an answer.
This is what I came up with for my specific needs and just for fun. I was curious about what I can do to make it more the way I want it to be so don't freak out about it ;P I think that this is a quite clean workaround - I know it's not perfect though.
class MyRequest
{
public function __construct()
{
$reflectedProperties = (new \ReflectionClass($this))->getProperties();
foreach ($reflectedProperties as $property) {
!$property->isInitialized($this) ??
$property->getType()->allowsNull() ? $property->setValue($this, null) : null;
}
}
}
class PostRequest extends MyRequest
{
public ?string $title;
}
$postRequest = new PostRequest();
// works fine - I have access here!
foreach($postRequest as $property) {
var_dump($property);
}
The downfall of this solution is that you always have to make types nullable in your class. However for me and my specific needs that is totally ok. I don't mind, they would end up as nullables anyway and it might be a nice workaround for a short deadline situation if someone is in a hurry.
It still keeps the original PHP not initialized error though when the type is not nullable. I think that is actually kinda cool now. You get to keep all the things: Slim and lean classes, PHP error indicating the true nature of the problem and possibility to loop over typed properties if you agree to keep them nullable. All governed by native PHP 7 nullable operator.
Of course, this can be changed or extended to be more type-specific if that makes any sense.
Update: this answer may be obsolete, but the comments contain an interesting discussion.
#Robert's workaround is buggy; in this part:
foreach ($reflectedProperties as $property) {
!$property->isInitialized($this) ??
$property->getType()->allowsNull() ? $property->setValue($this, null) : null;
}
the ?? must be corrected to &&.
Moreover that's a misuse of the ternary conditional; just use a classic if:
foreach ($reflectedProperties as $property) {
if (!$property->isInitialized($this)
&& $property->getType()->allowsNull()
) {
$property->setValue($this, null);
}
}
or:
foreach ($reflectedProperties as $property) {
if (!$property->isInitialized($this) && $property->getType()->allowsNull()) {
$property->setValue($this, null);
}
}
Probably not what you want, but you could use reflection:
<?php
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$ob = new DragonBallClass;
$reflector = new ReflectionClass($ob);
foreach($reflector->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
echo $prop->name, ':', ($ob->{$prop->name} ?? 'NOT INITIALISED'), "\n";
}
Output:
goku:NOT INITIALISED
bulma:Bulma
vegeta:Vegeta
In PHP I need to pass some arguments to a function by reference.
I don't want to write 2 different methods for similar behaviour.
So i need to select behaviour by argument.
But I can't pass null by reference.
So I created a dummy array.
So i run it either by
$temp[0]=-1;
$this->doSomething($bigIds, $temp);
or
$temp[0]=-1;
$this->doSomething($temp, $smallIds);
public function doSomething(&$bigIds, &$smallIds) {
if ($bigIds[0] != -1) {
// make some thing
}
if ($smallIds[0] != -1) {
// make some thing
}
}
Is there a better/ elegant way to do this?
I would suggest an enum but this is PHP. So this should do it for you:
class YourClass
{
const DoSomethingSmall = 0;
const DoSomethingBig = 1;
public function doSomething(&$ids, $actionType) {
// can also use a switch here
if($actionType == self::DoSomethingSmall) {
// do small
}
else if($actionType == self::DoSomethingBig) {
// do big
}
}
}
Then you can do:
$this->doSomething($bigIds, self::DoSomethingBig);
$this->doSomething($smallIds, self::DoSomethingSmall);
From outside the class you can use YourClass::DoSomethingBig and YourClass::DoSomethingSmall
There could be loads of things you might rather do, for instance what #ad7six says in a comment, and you could also just give it some sort of setting and just one array..
public function doSomething(&$bIds, $mode) {
switch($mode){
case 1: do smallthing;break;
case 2: do bigthing;break;
case 3: do both;break;
default: do nothing;break;
}
It all depends on what you need really
So, I have a object with structure similar to below, all of which are returned to me as stdClass objects
$person->contact->phone;
$person->contact->email;
$person->contact->address->line_1;
$person->contact->address->line_2;
$person->dob->day;
$person->dob->month;
$person->dob->year;
$album->name;
$album->image->height;
$album->image->width;
$album->artist->name;
$album->artist->id;
etc... (note these examples are not linked together).
Is it possible to use variable variables to call contact->phone as a direct property of $person?
For example:
$property = 'contact->phone';
echo $person->$property;
This will not work as is and throws a E_NOTICE so I am trying to work out an alternative method to achieve this.
Any ideas?
In response to answers relating to proxy methods:
And I would except this object is from a library and am using it to populate a new object with an array map as follows:
array(
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
);
and then foreaching through the map to populate the new object. I guess I could envole the mapper instead...
If i was you I would create a simple method ->property(); that returns $this->contact->phone
Is it possible to use variable variables to call contact->phone as a direct property of $person?
It's not possible to use expressions as variable variable names.
But you can always cheat:
class xyz {
function __get($name) {
if (strpos($name, "->")) {
foreach (explode("->", $name) as $name) {
$var = isset($var) ? $var->$name : $this->$name;
}
return $var;
}
else return $this->$name;
}
}
try this code
$property = $contact->phone;
echo $person->$property;
I think this is a bad thing to to as it leads to unreadable code is is plain wrong on other levels too, but in general if you need to include variables in the object syntax you should wrap it in braces so that it gets parsed first.
For example:
$property = 'contact->phone';
echo $person->{$property};
The same applies if you need to access an object that has disalowed characters in the name which can happen with SimpleXML objects regularly.
$xml->{a-disallowed-field}
If it is legal it does not mean it is also moral. And this is the main issue with PHP, yes, you can do almost whatever you can think of, but that does not make it right. Take a look at the law of demeter:
Law of Demeter
try this if you really really want to:
json_decode(json_encode($person),true);
you will be able to parse it as an array not an object but it does your job for the getting not for the setting.
EDIT:
class Adapter {
public static function adapt($data,$type) {
$vars = get_class_vars($type);
if(class_exists($type)) {
$adaptedData = new $type();
} else {
print_R($data);
throw new Exception("Class ".$type." does not exist for data ".$data);
}
$vars = array_keys($vars);
foreach($vars as $v) {
if($v) {
if(is_object($data->$v)) {
// I store the $type inside the object
$adaptedData->$v = Adapter::adapt($data->$v,$data->$v->type);
} else {
$adaptedData->$v = $data->$v;
}
}
}
return $adaptedData;
}
}
OOP is much about shielding the object's internals from the outside world. What you try to do here is provide a way to publicize the innards of the phone through the person interface. That's not nice.
If you want a convenient way to get "all" the properties, you may want to write an explicit set of convenience functions for that, maybe wrapped in another class if you like. That way you can evolve the supported utilities without having to touch (and possibly break) the core data structures:
class conv {
static function phone( $person ) {
return $person->contact->phone;
}
}
// imagine getting a Person from db
$person = getpersonfromDB();
print conv::phone( $p );
If ever you need a more specialized function, you add it to the utilities. This is imho the nices solution: separate the convenience from the core to decrease complexity, and increase maintainability/understandability.
Another way is to 'extend' the Person class with conveniences, built around the core class' innards:
class ConvPerson extends Person {
function __construct( $person ) {
Person::__construct( $person->contact, $person->name, ... );
}
function phone() { return $this->contact->phone; }
}
// imagine getting a Person from db
$person = getpersonfromDB();
$p=new ConvPerson( $person );
print $p->phone();
You could use type casting to change the object to an array.
$person = (array) $person;
echo $person['contact']['phone'];
In most cases where you have nested internal objects, it might be a good time to re-evaluate your data structures.
In the example above, person has contact and dob. The contact also contains address. Trying to access the data from the uppermost level is not uncommon when writing complex database applications. However, you might find your the best solution to this is to consolidate data up into the person class instead of trying to essentially "mine" into the internal objects.
As much as I hate saying it, you could do an eval :
foreach ($properties as $property) {
echo eval("return \$person->$property;");
}
Besides making function getPhone(){return $this->contact->phone;} you could make a magic method that would look through internal objects for requested field. Do remember that magic methods are somewhat slow though.
class Person {
private $fields = array();
//...
public function __get($name) {
if (empty($this->fields)) {
$this->fields = get_class_vars(__CLASS__);
}
//Cycle through properties and see if one of them contains requested field:
foreach ($this->fields as $propName => $default) {
if (is_object($this->$propName) && isset($this->$propName->$name)) {
return $this->$propName->$name;
}
}
return NULL;
//Or any other error handling
}
}
I have decided to scrap this whole approach and go with a more long-winded but cleaner and most probably more efficient. I wasn't too keen on this idea in the first place, and the majority has spoken on here to make my mind up for me. Thank for you for your answers.
Edit:
If you are interested:
public function __construct($data)
{
$this->_raw = $data;
}
public function getContactPhone()
{
return $this->contact->phone;
}
public function __get($name)
{
if (isset($this->$name)) {
return $this->$name;
}
if (isset($this->_raw->$name)) {
return $this->_raw->$name;
}
return null;
}
In case you use your object in a struct-like way, you can model a 'path' to the requested node explicitly. You can then 'decorate' your objects with the same retrieval code.
An example of 'retrieval only' decoration code:
function retrieve( $obj, $path ) {
$element=$obj;
foreach( $path as $step ) {
$element=$element[$step];
}
return $element;
}
function decorate( $decos, &$object ) {
foreach( $decos as $name=>$path ) {
$object[$name]=retrieve($object,$path);
}
}
$o=array(
"id"=>array("name"=>"Ben","surname"=>"Taylor"),
"contact"=>array( "phone"=>"0101010" )
);
$decorations=array(
"phone"=>array("contact","phone"),
"name"=>array("id","name")
);
// this is where the action is
decorate( $decorations, &$o);
print $o->name;
print $o->phone;
(find it on codepad)
If you know the two function's names, could you do this? (not tested)
$a = [
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
];
foreach ($a as $name => $chain) {
$std = new stdClass();
list($f1, $f2) = explode('->', $chain);
echo $std->{$f1}()->{$f2}(); // This works
}
If it's not always two functions, you could hack it more to make it work. Point is, you can call chained functions using variable variables, as long as you use the bracket format.
Simplest and cleanest way I know of.
function getValueByPath($obj,$path) {
return eval('return $obj->'.$path.';');
}
Usage
echo getValueByPath($person,'contact->email');
// Returns the value of that object path
I find in my PHP pages I end up with lines and lines of code that look like this:
$my_id = isset($_REQUEST['my_id']) ? $_REQUEST['my_id'] : '';
$another_var = isset($_REQUEST['another_var']) ? $_REQUEST['another_var'] : 42;
...
Is there a better, more concise, or more readable way to check this array and assign them to a local variable if they exist or apply a default if they don't?
EDIT: I don't want to use register_globals() - I'd still have the isset problem anyway.
How about wrapping it in a function?
<?php
function getPost($name, $default = null) {
return isset($_POST[$name]) ? $_POST[$name] : $default;
}
a better method might be to create a singleton/static class to abstract away the details of checking the request data.
Something like:
class Request {
private $defaults = array();
private static $_instance = false;
function getInstance () {
if (!self::$_instance) {
$c = __CLASS__;
self::$_instance = new $c;
}
return self::$_instance;
}
function setDefaults($defaults) {
$this->defaults = $defaults;
}
public function __get($field) {
if (isset($_REQUEST[$field]) && !empty($_REQUEST[$field])) {
return $_REQUEST['field'];
} elseif (isset($this->defaults[$field])) {
return $this->defaults[$field];
} else {
return ''; # define a default value here.
}
}
}
you can then do:
# get an instance of the request
$request = Request::getInstance();
# pass in defaults.
$request->setDefaults(array('name'=>'Please Specify'));
# access properties
echo $request->name;
echo $request->email;
I think this makes your individual scripts loads cleaner and abstracts away the validation etc. Plus loads of scope with this design to extend it/add alternate behaviours, add more complicated default handling etc etc.
First, use $_POST for POSTed variables. $_REQUEST is a mashup of many different incoming variables, not just $_POST and could cause problems.
One solution for your question would be to create a function that handles the isset() logic.
function ForceIncomingValue($Key, $Default) {
if (!isset($_POST[$Key]))
return $Default;
else return $_POST[$Key];
}
first of all, NEVER use the $_REQUEST variable, it'll lead to bugs and other problems during development
function getPOST($key) {
if(isset($_POST[$key])) {
return $_POST[$key];
}
}
note that this code leaves the variable empty when $_POST[$key] was not set
you could also adapt that code to enable you to instead provide you with a (sensible) default when the value could not be loaded.
function getPOST($key, $default = NULL) {
if(isset($_POST[$key])) {
return $_POST[$key];
} else {
return $default;
}
}
Is the set of variables you're expecting known at the time of the script's writing, or do you want to do this for an arbitrary set of values? If the former is true, you could do something like this:
# This array would hold the names of all the variables you're expecting
# and a default value for that variable name
$variableNames = array (...);
foreach ($variableNames as $key => $default) {
if (isset ($_REQUEST[$key])) $$key = $_REQUEST[$key];
else $$key = $default;
}
Basically, this takes advantage of PHP's ability to evaluate variables to create other variables (hence the double-dollar for $$key--this means create a new variable whose name is the value of $key).
I haven't yet come up with a good solution to the latter situation.
PHP's null coalescing operator!
$username = $_GET['user'] ?? 'nobody';
For a lot of variables, with a requirement check, anyone is free to use my expect function.