PHP 8.1 is almost getting released, including support for Enumerations. I was testing some of the enum functionality and couldn't find much documentation about it. Hence my question: how do I get all values of an enum?
For basic enums:
$suits = array_column(Suit::cases(), 'name');
For backed enums where you want the values:
$suits = array_column(Suit::cases(), 'value');
You could then do something like this:
trait EnumToArray
{
public static function names(): array
{
return array_column(self::cases(), 'name');
}
public static function values(): array
{
return array_column(self::cases(), 'value');
}
public static function array(): array
{
return array_combine(self::values(), self::names());
}
}
enum Suit: string
{
use EnumToArray;
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
Suit::array() will return:
Array
(
[H] => Hearts
[D] => Diamonds
[C] => Clubs
[S] => Spades
)
After some research I found the answer. You can use the static method: cases().
enum Status
{
case PAID;
case Cancelled;
}
Status::cases();
The cases method will return an array with an enum (UnitEnum interface) for each value.
Need the values and not Enum instances?
I've written a Composer package for this, othyn/php-enum-enhancements, as the UnitEnum::cases() method wasn't what I was looking for, as that returns an array of MySuperCoolEnum instances instead of the underlying values as their raw type, which is what I wanted.
Its a trait that can be easily added to any enum that does the following:
Adds a new static UnitEnum::valueArray(): array method that returns all values within an Enum as an equally typed array of Enum values
Adds a new static UnitEnum::valueList(string $separator = ', '): string method that returns all values within an Enum as a comma separated list string
In which produces the following for normal Enum's:
<?php
namespace App\Enums;
use Othyn\PhpEnumEnhancements\Traits\EnumEnhancements;
enum TestEnum
{
use EnumEnhancements;
case Alpha;
case Bravo;
case Charlie;
case Delta;
case Echo;
}
var_dump(TestEnum::valueArray());
// Results in the following being printed:
// array(5) {
// [0]=>
// string(5) "Alpha"
// [1]=>
// string(5) "Bravo"
// [2]=>
// string(7) "Charlie"
// [3]=>
// string(5) "Delta"
// [4]=>
// string(4) "Echo"
// }
var_dump(TestEnum::valueList());
// Results in the following being printed:
// string(34) "Alpha, Bravo, Charlie, Delta, Echo"
var_dump(TestEnum::valueList(separator: ':'));
// Results in the following being printed:
// string(30) "Alpha:Bravo:Charlie:Delta:Echo"
... and the following for Backed Enum's, the following being a string example:
<?php
namespace App\Enums;
use Othyn\PhpEnumEnhancements\Traits\EnumEnhancements;
enum TestStringBackedEnum: string
{
use EnumEnhancements;
case Alpha = 'alpha';
case Bravo = 'bravo';
case Charlie = 'charlie';
case Delta = 'delta';
case Echo = 'echo';
}
var_dump(TestStringBackedEnum::valueArray());
// Results in the following being printed:
// array(5) {
// [0]=>
// string(5) "alpha"
// [1]=>
// string(5) "bravo"
// [2]=>
// string(7) "charlie"
// [3]=>
// string(5) "delta"
// [4]=>
// string(4) "echo"
// }
var_dump(TestStringBackedEnum::valueList());
// Results in the following being printed:
// string(34) "alpha, bravo, charlie, delta, echo"
var_dump(TestStringBackedEnum::valueList(separator: ':'));
// Results in the following being printed:
// string(30) "alpha:bravo:charlie:delta:echo"
... and yes it works on int's too!
There are more examples in the Usage part of the package's README.
In addition to UnitEnum::cases() you can use ReflectionEnum with this
$reflection = new ReflectionEnum(Status::class);
$reflection->getCases();
note that in both cases you will not be able to get the enum methods. but as long as the ReflectionEnum is extending the ReflectionClass so you can use the rest of ReflectionClass methods such as getMethods
I think the best options is using a trait for that.
For example:
EnumsToArray.php
<?php
namespace App\Traits;
trait EnumsToArray {
public static function toArray(): array {
return array_map(
fn(self $enum) => $enum->value,
self::cases()
);
}
}
And later, in you enum you should have:
use App\Traits\EnumsToArray;
Enum Currency: string {
use EnumsToArray;
case DOLLAR = "usd";
case EURO = "eur";
}
I have used the following in my project;
public static function toAssociativeArray(): array
{
foreach(self::cases() as $case) {
$array[$case->value] = $case->name;
}
return $array;
}
Which results in an associative array like this;
using strings as values
enum DiaryRole: string
{
case DANGER = 'red';
case WARNING = 'yellow';
case SAFE = 'green';
}
$array = [
'red' => 'DANGER',
'yellow' => 'WARNING',
'green' => 'SAFE'
];
or when using integers as values
enum DiaryRole: int
{
case DANGER = 1;
case WARNING = 2;
case SAFE = 3;
}
$array = [
1 => 'DANGER',
2 => 'WARNING',
3 => 'SAFE'
];
You can now use the array to get any information you need, and you can get only the columns or values using array_keys() or array_values()
I have used this code to easily foreach through them in a form select
I wrapped a slitly changed approach from #Michael up in a small package, cause I needed it in multiple projects:
https://github.com/laracraft-tech/laravel-useful-traits#usefulenums
Install via composer:
composer require laracraft-tech/laravel-useful-traits
This is how it is working:
use LaracraftTech\LaravelUsefulTraits\UsefulEnums;
enum PaymentType: int
{
use UsefulEnums;
case Pending = 1;
case Failed = 2;
case Success = 3;
}
PaymentType::names(); // return ['Pending', 'Failed', 'Success']
PaymentType::values(); // return [1, 2, 3]
PaymentType::array(); // return ['Pending' => 1, 'Failed' => 2, 'Success' => 3]
Related
I have a lot of functions stored in an associative array like this :
$arr['my-title'] = function(){process(146,'My Title');};
$arr['un-cafe-chaud'] = function(){process(857,'Un café chaud');};
$arr['vpn'] = function(){process(932,'VPN');};
$arr['another-example'] = function(){process(464,'Another example');};
Currently I have to encode manually each key.
As the key name is function of the Title, I'd like to automate it.
function assign_keys($title,$id){
$u=str_replace(array(' ','é'),array('-','e'),strtolower($title));
$arr[$u] = function(){process($id,$title);};
}
But it doesn't work, as process function can't get $id and $title value.
Any help on how I could handle this would be highly appreciated ! Thank you.
First of all, you should pass $arr as argument to the function in order to be able to mutate it. Second, you should use use to make those two variables available in the anonymous function, like this:
function assign_keys($title,$id, &$arr){
$u=str_replace(array(' ','é'),array('-','e'),strtolower($title));
$arr[$u] = function() use ($id, $title){process($id,$title);};
}
Then use it like this:
$arr = [];
assign_keys('Some title', 123, $arr);
var_dump($arr);
This should print:
array(1) {
["some-title"]=>
object(Closure)#1 (1) {
["static"]=>
array(2) {
["id"]=>
int(123)
["title"]=>
string(10) "Some title"
}
}
}
You probably want a reference & to get the array outside of the function and use to get the variables into the closure:
function assign_keys($title, $id, &$arr){
$u = str_replace(array(' ','é'), array('-','e'), strtolower($title));
$arr[$u] = function() use($title, $id) { process($id, $title); };
}
assign_keys('My Title', 146, $arr);
I really like the functional programming style of using array map to create an array of objects from another array of objects.
$newObjects = array_map(
function($oldObject) {
return new NewObject($oldObject);
},
$oldObjects
);
Which all works fine but I would really like to be able to set the indices of the array so that they are the ids of the original objects for easier search and retrieval from the array but I cannot think how to do it other then which is not as elegant.
$newObjects = array();
foreach ($oldObjects as $oldObject) {
$newObjects[$oldObject->getId()] = new NewObject($oldObject);
}
Is there a way I can do this?
That is - array_reduce() is exactly what you need:
class Bar
{
protected $id;
public function __construct($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
}
class Foo
{
protected $bar;
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
}
$oldObjects = [new Bar('x'), new Bar('y'), new Bar('z')];
$newObjects = array_reduce($oldObjects, function($current, Bar $obj) {
$current[$obj->getId()] = new Foo($obj);
return $current;
}, []);
This will do all in-place without having to spend memory on additional arrays like for array_combine()
However, I would suggest to use such constructs when they're necessary. Using this just because it "looks better" might be not a good idea - as plain loops are in most cases just more readable.
What if you use array_walk and a temporary array with your new indices.
$array = ['A', 'B', 'C', 'D'];
$reIndexedTemp = [];
array_walk(
$array,
function ($item, $key) use (&$reIndexedTemp) {
// here you can have your logic to assemble your new index
$reIndexedTemp[$key + 100] = $item;
}
);
//$array = $reIndexedTemp;
var_dump($array, $reIndexedTemp);
output (without the commented line) :
array(4) {
[0] =>
string(1) "A"
[1] =>
string(1) "B"
[2] =>
string(1) "C"
[3] =>
string(1) "D"
}
array(4) {
[100] =>
string(1) "A"
[101] =>
string(1) "B"
[102] =>
string(1) "C"
[103] =>
string(1) "D"
}
I think a foreach is probably the most readable solution in this case, but you can use array_map() with array_combine() to achieve what you want. Something like:
// empty array to store the old object ids
$ids = [];
// map over old objects, inheriting $id
// from parent scope by reference
$objs = array_map(function($oldObject) use (&$ids) {
$ids[] = $oldObject->getId();
return new NewObject($oldObject);
}, $oldObjects);
// combine id and object arrays
$newObjects = array_combine($ids, $objs);
Hope this helps :)
Looking around - Looking for array_map equivalent to work on keys in associative arrays
Suggests it might work using array_combine
So I guess it would be
$newObjects = array_combine(
array_map(
function($oldObject) {
return $oldObject->getId();
},
$oldObjects
),
array_map(
function($oldObject) {
return new NewObject($oldObject);
},
$oldObjects
)
);
Hmm probably the best, just this side of overblown but definately a lot more complex than the foreach
I'm trying to get a multi-dimensional array from an Entity.
Symfony Serializer can already convert to XML, JSON, YAML etc. but not to an array.
I need to convert because I want have a clean var_dump. I now have entity with few connections and is totally unreadable.
How can I achieve this?
You can actually convert doctrine entities into an array using the built in serializer. I actually just wrote a blog post about this today:
https://skylar.tech/detect-doctrine-entity-changes-without/
You basically call the normalize function and it will give you what you want:
$entityAsArray = $this->serializer->normalize($entity, null);
I recommend checking my post for more information about some of the quirks but this should do exactly what you want without any additional dependencies or dealing with private/protected fields.
Apparently, it is possible to cast objects to arrays like following:
<?php
class Foo
{
public $bar = 'barValue';
}
$foo = new Foo();
$arrayFoo = (array) $foo;
var_dump($arrayFoo);
This will produce something like:
array(1) {
["bar"]=> string(8) "barValue"
}
If you have got private and protected attributes see this link : https://ocramius.github.io/blog/fast-php-object-to-array-conversion/
Get entity in array format from repository query
In your EntityRepository you can select your entity and specify you want an array with getArrayResult() method.
For more informations see Doctrine query result formats documentation.
public function findByIdThenReturnArray($id){
$query = $this->getEntityManager()
->createQuery("SELECT e FROM YourOwnBundle:Entity e WHERE e.id = :id")
->setParameter('id', $id);
return $query->getArrayResult();
}
If all that doesn't fit you should go see the PHP documentation about ArrayAccess interface.
It retrieves the attributes this way : echo $entity['Attribute'];
PHP 8 allows us to cast object to array:
$var = (array)$someObj;
It is important for object to have only public properties otherwise you will get weird array keys:
<?php
class bag {
function __construct(
public ?bag $par0 = null,
public string $par1 = '',
protected string $par2 = '',
private string $par3 = '')
{
}
}
// Create myBag object
$myBag = new bag(new bag(), "Mobile", "Charger", "Cable");
echo "Before conversion : \n";
var_dump($myBag);
// Converting object to an array
$myBagArray = (array)$myBag;
echo "After conversion : \n";
var_dump($myBagArray);
?>
The output is following:
Before conversion :
object(bag)#1 (4) {
["par0"]=> object(bag)#2 (4) {
["par0"]=> NULL
["par1"]=> string(0) ""
["par2":protected]=> string(0) ""
["par3":"bag":private]=> string(0) ""
}
["par1"]=> string(6) "Mobile"
["par2":protected]=> string(7) "Charger"
["par3":"bag":private]=> string(5) "Cable"
}
After conversion :
array(4) {
["par0"]=> object(bag)#2 (4) {
["par0"]=> NULL
["par1"]=> string(0) ""
["par2":protected]=> string(0) ""
["par3":"bag":private]=> string(0) ""
}
["par1"]=> string(6) "Mobile"
["�*�par2"]=> string(7) "Charger"
["�bag�par3"]=> string(5) "Cable"
}
This method has a benefit comparing to Serialiser normalizing -- this way you can convert Object to array of objects, not array of arrays.
I had the same issue and tried the 2 other answers. Both did not work very smoothly.
The $object = (array) $object; added alot of extra text in my key
names.
The serializer didn't use my active property because it did not have is in front of it and is a boolean. It also changed the sequence of my data and the data itself.
So I created a new function in my entity:
/**
* Converts and returns current user object to an array.
*
* #param $ignores | requires to be an array with string values matching the user object its private property names.
*/
public function convertToArray(array $ignores = [])
{
$user = [
'id' => $this->id,
'username' => $this->username,
'roles' => $this->roles,
'password' => $this->password,
'email' => $this->email,
'amount_of_contracts' => $this->amount_of_contracts,
'contract_start_date' => $this->contract_start_date,
'contract_end_date' => $this->contract_end_date,
'contract_hours' => $this->contract_hours,
'holiday_hours' => $this->holiday_hours,
'created_at' => $this->created_at,
'created_by' => $this->created_by,
'active' => $this->active,
];
// Remove key/value if its in the ignores list.
for ($i = 0; $i < count($ignores); $i++) {
if (array_key_exists($ignores[$i], $user)) {
unset($user[$ignores[$i]]);
}
}
return $user;
}
I basicly added all my properties to the new $user array and made an extra $ignores variable that makes sure properties can be ignored (in case you don't want all of them).
You can use this in your controller as following:
$user = new User();
// Set user data...
// ID and password are being ignored.
$user = $user->convertToArray(["id", "password"]);
I know this can be done in javascript like so:
function doSomething(){
var something, something_else, another_thing;
// do something with these vars
return {
attribute1 : something,
array1 : [
something_else,
another_thing
]
}
}
can it be done in php?
You can create a new object of stdClass(), assign its attributes and return it.
$x = new stdClass();
$x->attribute1 = "something";
$x->array1 = array(1,2,3);
var_dump($x);
return $x;
PHP does not support object literals. However, it does have a generic stdClass class, which you can typecast an array into for a somewhat similar syntax.
function doSomething()
{
$something = 1;
$something_else = 2;
$another_thing = 3;
return (object) [
"attribute1" => $something,
"array1" => [
$something_else,
$another_thing
]
];
}
var_dump(doSomething());
will give (demo)
object(stdClass)#1 (2) {
["attribute1"]=> int(1)
["array1"]=> array(2) {
[0]=> int(2)
[1]=> int(3)
}
}
Note that you can only use short array syntax as of PHP 5.4. Before that you'd use array().
I'm sorry if my question already asked before. I don't know what the search term that match my problem. I've searched 'OOP child', 'PHP object child', etc but I got no clue at all.
So, here we go. I wanted to make something like this:
$school = new School;
var_dump($school);
And resulting something like this:
object(School)#1 (2) {
["name"]=>
string(14) "All Way School"
["object"]=>
object(School)#2 (2) {
["art"]=>
object(School)#3 (1) {
["student"]=>
int(25)
}
["law"]=>
object(School)#4 (1) {
["student"]=>
int(30)
}
}
}
See the output. object has 2 child: art and law, each has a child student. This way, I can access law's total students by $school->object->law->student. But, I don't know what to write in my class. All I know I can only make $school->name in my School class like this:
class School {
$name = "All Way School";
}
I don't know how to make $object and its child.
Note: I'm just guessing the output. I'm really don't understand how to get it. I'm inspired by SimpleXMLElement.
Edit: I'm replacing $class with $object to prevent confusion.
The OOP 'way' would be to have a different object for each School, Subject (you can't use Class, as it's a reserved keyword) and Student. Define them as normal, with specific properties:
Class School {
// methods, vars etc
}
Class Subject {
// ...
}
Then, you can create new instances of Class within School and assign them to a variable:
Class School {
var $subjects;
function __construct() {
$this->subjects = new Array();
}
}
$mySchool = new School();
$mySchool->subjects[] = new Subject();
Then you can get the subjects using:
$mySchool->subjects[1]->name;
Hope that helps
You can make a child the same way you assigned a name:
class School {
$name = "All Way School";
public function __construct() { // executed when creating a new School object
$this->classes = array(
'art' => new SchoolClass('art'),
'law' => new SchoolClass('law')
);
}
}
You can do the same for the SchoolClass class, making it contain all the students;
class SchoolClass {
$name;
public function __construct( $name ) {
$this->name = $name; // law, art, etc
$this->students = array(
new SchoolStudent( 2 ),
new SchoolStudent( 5 )
);
}
}
(Note that you would usually not put the students hard-coded in the constructor like this, but rather load them from a data source.)
Then count the students for instance:
$School = new School();
echo count( $School->classes['law']->students );
class School {
public $name, $class;
public function __construct() {
$this->class = new SchoolClass();
$this->name = "All Way School";
}
}
class SchoolClass {
public $art, $law;
public function __construct() {
$this->art = new SchoolStudent(25);
$this->law = new SchoolStudent(30);
}
}
class SchoolStudent {
public $student;
public function __construct($student) {
$this->student = $student;
}
}
$school = new School();
var_dump($school);
var_dump($school->class->law->student);
Will give you exactly this:
object(School)#1 (2) {
["name"]=>
string(14) "All Way School"
["class"]=>
object(SchoolClass)#2 (2) {
["art"]=>
object(SchoolStudent)#3 (1) {
["student"]=>
int(25)
}
["law"]=>
object(SchoolStudent)#4 (1) {
["student"]=>
int(30)
}
}
}
int(30)
If you don't want to type every class by hand, read about overloading in PHP and beautiful __set()/__get() methods.
Typecasting can be other solution that fits:
$school = (object) array(
"name" => "All Way School",
"object" => (object) array(
"art" => (object) array("student" => 25),
"law" => (object) array("student" => 30),
),
);
var_dump($school);
var_dump($school->object->law->student);
Which gives you
object(stdClass)#4 (2) {
["name"]=>
string(14) "All Way School"
["object"]=>
object(stdClass)#3 (2) {
["art"]=>
object(stdClass)#1 (1) {
["student"]=>
int(25)
}
["law"]=>
object(stdClass)#2 (1) {
["student"]=>
int(30)
}
}
}
int(30)
I understand that all this is not what you're looking for, but I hope that these examples will help you understand your own needs better and ask the right question. Your current question is vague and unclear: even if I gave you literally what you've asked, you ain't going to accept my answer.
You need a seperate class for each object, they can;t all be instances of School. For example, Your class School would have two properties, name and subjects. Then you would have an class called subject which contains a name and a numbver of students. Note that I have used the word 'subject' instead of 'class' - this is because 'class' is a reserved word in PHP and even if it weren't, it would get very confusing if you had a class called 'class'.
So you might do something like this:
<?php
class School {
public $name;
public $subjects = array();
function __construct ($name) {
$this->name = $name;
}
function addSubject ($subject) {
// Adds a new subject object
if (is_string($subject)) $subject = new Subject($subject);
$this->subjects[$subject->name] = $subject;
}
function getSubjectByName ($name) {
// returns a Subject object or FALSE on failure
return (isset($this->subjects[$name])) ? $this->subjects[$name] : FALSE;
}
function getStudentsBySubjectName ($name) {
// returns number of students for a subject or FALSE on failure
return (isset($this->subjects[$name])) ? $this->subjects[$name]->numberOfStudents : FALSE;
}
// ... more methods
}
class Subject {
public $name;
public $numberOfStudents;
function __construct ($name, $students = 0) {
$this->name = $name;
$this->numberOfStudents = $students;
}
// ... more methods
}
$school = new School('Useless City High');
$school->addSubject(new Subject('Math', 2));
$school->addSubject('Stupidity Studies');
$school->subjects['Stupidity Studies']->numberOfStudents = 65;
$school->addSubject(new Subject('Law'));
var_dump($school);
$math = $school->getSubjectByName('Math');
echo $math->numberOfStudents; // outputs '2'
echo $school->getStudentsBySubjectName('Math'); // outputs '2'
$stupidity = $school->subjects['Stupidity Studies'];
echo $stupidity->numberOfStudents; // outputs '65'
echo $school->getStudentsBySubjectName('Stupidity Studies'); // outputs '65'
echo $school->getStudentsBySubjectName('Law'); // outputs '0'
echo $school->subjects['Law']->numberOfStudents; // outputs '0'
?>
While you could write this in a single class that contains more instances, it doesn't make semantic sense to do so in this situation.
A SimpleXML element is one object that contain children that are instances of itself. This is because that is how XML works - any element can contain any other element (within reason). E.g. you can have <div><div><div>Some Data</div></div></div> but equally you could just have <div>Some Data</div>. All those divs are XML elements - they are all the same type of object. They can potentially contain more of themselves, but this situation is rare.
You are dealing with 2 distinct types of object - a School and a Subject. Think of it like the real world - a School won't contain another School, a Suject wont contain another Subject, and a Subject will not exist outside a School.
You could take the above example a step further, and create a class called Student since this is another distict type. A Student might be contained by a School or a Subject, but it will never contain either of them. Equally, say you had a Book class, a Student could have one, a Subject could have one, or a School could have one. But a Book would never contain a whole School - it just doesn't make sense.
OOP is all about objects. Think about it in terms of the real world, it will help you make sense of what classes you need, and how they can interact with each other.