Array of functions as a private property of onject - php

I would like to know the reason of "Unexpected T_FUNCTION" error in this php code:
class T
{
private $array_of_functions = array(
'0' => function() { return true; }
);
}

You can not use such construction as default property value. Default property value can be only constant expression - so it can not contain closure definition since it's dynamic (i.e. evaluated when constructed at runtime). Instead you should initialize it inside class constructor:
class T
{
private $array_of_functions = [];
public function __construct()
{
$this->array_of_functions = [
function() { return true; }
];
}
}

Related

PHP use ENUM in Attributes

Look at the following code:
<?php
enum Types:string {
case A = 'a';
case B = 'b';
}
#[Attribute(Attribute::TARGET_CLASS)]
class MyAttribute {
public function __construct(public readonly array $mapping)
{
}
}
#[MyAttribute(mapping: [Types::A->value => ''])]
class Entity {
}
It has error Constant expression contains invalid operations. I would like to use Enum value in my attribute for defining configuration. Seem like it is bug in php. Should it be reported or something?
The problem is that when we call Types::A->value it actually creates instance of an enum, which is not a constant value.
To solve this problem define a constant and reference it.
<?php
abstract class Type {
public const A = 'a';
public const B = 'b';
}
enum TypesEnum:string {
case A = Type::A;
case B = Type::B;
}
#[Attribute(Attribute::TARGET_CLASS)]
class MyAttribute {
public function __construct(public readonly array $mapping)
{
}
}
#[MyAttribute(mapping: [Type::A => ''])]
class Entity {
}
Watch out for this issue in php

Why PHP `__invoke` Not Working When Triggered from an Object Property

I wonder whether this is a bug or normal. Let’s say I have a class with some magical functions:
class Foo {
public function __toString() {
return '`__toString` called.';
}
public function __get($key) {
return '`__get(' . $key . ')` called.';
}
public function __invoke($x = "") {
return '`__invoke(' . $x . ')` called.';
}
}
And then create an instance in an object property like this:
$object = (object) [
'foo' => 'bar',
'baz' => new Foo
];
Then test it:
echo $object->baz;
echo $object->baz->qux;
echo $object->baz('%'); // :(
It is broken in the last echo: Call to undefined method stdClass::baz()
Currently, the only solution I can do is to store the __invoke part in a temporary variable and then call that variable as a function like this:
$x = $object->baz;
echo $x('%'); // :)
It works fine when I instantiate the class in an array property:
$array = [
'baz' => new Foo
];
echo $array['baz'];
echo $array['baz']->qux;
echo $array['baz']('%'); // :)
By the way, I need this ability on my object for something related to API:
$foo = (object) ['bar' => new MyClass];
echo $foo->bar; → should trigger __toString
echo $foo->bar->baz; → should trigger __get
echo $foo->bar(); → should trigger __invoke
echo $foo->bar->baz(); → should trigger __call
All of them should return a string.
Can this be done in PHP completely? Thanks.
No can do.
The line in question is simply ambigous, and the error message shows you how ... It is more logical to try to access the baz() method of your $object object.
That's just the context given by the parser when it sees $object->baz()
As already mentioned in the comments, you can remove that ambiguity, help the parser by telling it that $object->baz is itself an expression that needs to be executed first:
($object->baz)('arg');
PHP is also itself a program, and has to know how to execute something before executing it. If it could blindly try every possible "magic" method on every object in a $foo->bar->baz->qux chain, then it wouldn't be able to tell you what the error is when it is encountered - it would just silently crash.
I have solved my problem by detecting the existence of an __invoke method inside the __call method of a class.
class MyStdClass extends stdClass {
protected $data = [];
public function __construct(array $array) {
$this->data = $array;
}
public function __get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
public function __call($key, $args = []) {
if (isset($this->data[$key])) {
$test = $this->data[$key];
// not an object = not an instance, skip!
if (!is_object($test)) {
return $this->__get($key);
}
if (!empty($args) && get_class($test) && method_exists($test, '__invoke')) {
// or `return $test(...$args)`
return call_user_func([$test, '__invoke'], ...$args);
}
}
return $this->__get($key);
}
public function __set($key, $value = null) {
$this->data[$key] = $value;
}
public function __toString() {
return json_encode($this->data);
}
public function __isset($key) {}
public function __unset($key) {}
}
So, instead of converting the array into object with (object), here I use:
$object = new MyStdClass([
'foo' => 'bar',
'baz' => new Foo
]);

Return variable class name from php factory

I've got a factory that I want to return a ::class from. However, the factory could potentially return a couple dozen different types (determined by the type of object passed into the factory), named TypeOneObject, TypeTwoObject etc. Is it possible to return the class using a variable, something like this?
$type = $myrequiredclass->getType();
return $type."Object"::class; // wanting TypeOneObject::class
It seems like no matter how I construct this return statement I always get PHP Parse error: syntax error, unexpected '::'
I know it'd be easy enough to do with a big if/then or switch but I'd like to avoid that.
Here's a more fleshed out scenario:
class TypeOneObject
{
public static function whoAreYou()
{
return 'Type One Object!';
}
}
class MyRequiredClass
{
public function getType()
{
return 'TypeOne';
}
}
class MyFactory
{
public static function getFactoryObject(MyRequiredClass $class)
{
$type = $class->getType()."Object";
return $type::class;
}
}
$object = MyFactory::getFactoryObject(new MyRequiredClass());
$object::whoAreYou();
The best way to get the class name from the $type instance is to use php get_class_methods function. This will get us all the methods inside the class instance. from there we can filter and use call_user_func to call the method and get the right values.
class TypeOneObject
{
public static function whoAreYou()
{
return 'Type One Object!';
}
}
class MyRequiredClass
{
public function getType()
{
return 'TypeOne';
}
}
class MyFactory
{
public static function getFactoryObject(MyRequiredClass $class)
{
$methods = get_class_methods($class);
$method = array_filter($methods, function($method) {
return $method == 'getType';
});
$class = new $class();
$method = $method[0];
$methodName = call_user_func([$class, $method]);
$objectName = sprintf('%sObject', $methodName);
return new $objectName;
}
}
$object = MyFactory::getFactoryObject(new MyRequiredClass());
echo $object::whoAreYou();
Output
Type One Object!

Add anonymous function to array in class

I'm new to PHP but have worked with JavaScript quite a bit. I was trying to do the following:
class MyClass {
private $someAnonymousFunction = function($any){
return $any;
};
$data = [
['some String', $someAnonymousFunction]
];
}
But it comes up with an error when I create someAnonymousFunction saying it doesn't like that I put function (unexpected 'function' (T_FUNCTION)) in there like that. I've tried different scenarios beyond the above example but it seems the same errors happen.
Update Why I want to do this. I want to do this because I want to abstract away much of the boiler plate in creating classes. If I'm going to be writing code over and over, I would like to make it at easy and straight forward as possible. Below is my code in full (note that this was just for class and I'm taking it far beyond what it needs to be):
abstract class Protection {
const Guard = 0;
const Open = 1;
}
abstract class __ {
public function identity($any) {
return $any;
}
}
// This is the core logic behind the class
trait Property {
public function get($name) {
if ($this->data[$name][1] == Protection::Open) {
return $this->data[$name][0];
}
else {
//throw error
}
}
public function set($name, $set) {
if ($this->data[$name][1] == Protection::Open) {
//Guard function can throw error or filter input
$func = $this->data[$name][2];
$this->data[$name][0] = $func($set);
return $this;
}
else {
// throw error
}
}
}
class Monster {
use Property;
//Just trying to get this to work. Throws error.
private $identity = function($any) {
return $any;
};
private $data = [
'hairColor' => [
'brown',
Protection::Open,
ucwords
],
'killType' => [
'sword',
Protection::Open,
__::identity //Doesn't work either thinks it's a constant.
]
];
}
$generic = new Monster();
echo '<br> Hair color: ' . $generic->get('hairColor') . ', Kill type: '
. $generic->get('killType') . '<br>';
$generic->set('hairColor', 'blue')->set('killType', 'silver bullet');
Update 2: Here's the "final" code:
<?php
class Utils {
static function identity($any) {
return $any;
}
}
// Property abstracts much of the boiler plate away
// from making classes with getters and setters.
// It is chainable by default.
// It takes a variable named `data` which holds an
// associative array, with the keys being the names
// of the properties/methods. The key holds a value
// which is an indexed array where:
// Index 0 == Value of property.
// [Index 1] == Optional string name of guard function.
// [Index 2] == Optional string name of method called
// with `get` method.
// It has two public methods:
// `get` returns value of property or calls a method.
// `set` gives a new value to a property.
trait Property {
// Create a new property with attributes in an array.
private function createNewProperty($name, $set) {
$this->data[$name] = (is_array($set)) ? $set : [$set];
}
// Return property value
public function get($name) {
// If the property doesn't exist throw an error.
if (!isset($this->data[$name])) {
throw new Exception('No such property or method '.$name);
}
// copy by reference value into differently
// named variable to make code more concise.
$prop =& $this->data[$name];
// determine if property is a method.
if (isset($prop[2]) && $prop[2]) {
// call method with property value
return call_user_func($prop[2], $prop[0]);
}
else {
//return plain property value
return $prop[0];
}
}
// Set property value
public function set($name, $set) {
// If property isn't set then create one
if (!isset($this->data[$name])) {
createNewProperty($name, $set);
}
// copy by reference value into differently
// named variable to make code more concise.
$prop =& $this->data[$name];
// determine if guards exist when setting property
if (isset($prop[1]) && $prop[1]) {
$prop[0] = call_user_func($prop[1], $set);
return $this; // make chainable
}
else {
// set plain property
$prop[0] = $set;
return $this; // make chainable
}
}
}
class Monster {
use Property;
private $data = [
'hairColor' => ['brown', ['Utils', 'identity']],
'killType' => ['sword', 'ucwords'],
'simple' => ['simple value', null, 'ucwords'],
];
}
$generic = new Monster();
echo '<br> Hair color: ' . $generic->get('hairColor') . ', Kill type: '
. $generic->get('killType') . '<br>';
$generic->set('hairColor', 'blue')->set('killType', 'silver bullet');
echo '<br> Hair color: ' . $generic->get('hairColor') . ', Kill type: '
. $generic->get('killType') . '<br>';
echo '<br>Simple: '.$generic->get('simple');
$generic->set('simple', 'simple value changed!');
echo '<br>Simple: '.$generic->get('simple');
?>
You cannot assign arbitrary expressions in a property declaration like that, only constant values, or with recent changes to the parser, certain constant-valued expressions.
However, declaring properties with an initial value is actually just shorthand for declaring the property and then initialising it in the constructor, since the initial assignment occurs whenever a new instance is created.
So the two classes below will behave identically:
class Foo1 {
private $bar = 42;
}
class Foo2 {
private $bar;
public function __construct() {
$this->bar = 42;
}
}
Since there is no restriction on the assignments you can make in the constructor, this allows you to rewrite your code as this (you were missing the access specifier on $data, so I've assumed private):
class MyClass {
private $someAnonymousFunction;
private $data;
public function __construct() {
$this->someAnonymousFunction = function($any){
return $any;
};
$this->data = [
['some String', $this->someAnonymousFunction]
];
}
}
You can only initialize a field with a compile-time constant value. I am guessing that an anonymous function doesn't count as a constant value.

initilize array in class constructor or when defined?

I have a $rawText class property that is supposed to be array. It's assigned a value during object construction, however the value may not be found so the variable remains without a value.
class TextProcessor {
public $rawText;
public function __construct($idText) {
if ($idText !== NULL) {
$this->$rawText = $this->getRawText();
}
}
}
Do I need to assign an empty array then?
public function __construct($idText) {
if ($idText !== NULL) {
$this->$rawText = $this->getRawText();
} else {
$this->$rawText = Array();
}
Or maybe it's even better to assign empty array when the property is defined?
class TextProcessor {
public $rawText = Array();
}
class TextProcessor {
public $rawText = array();
public function __construct($idText) {
if (! empty($idText)) {
$this->rawText = $this->getRawText();
}
}
}
I think that's what you trying to do.
I think the construct method is used to define some variables private usually. If you want to give $rawText a new value, after you instantiate this class, you can write a set property method to change its value.

Categories