I'm trying to filter an array of objects implementing a specific interface (which simply defines the isComplete(): bool method) based on the result of that method. array_filter doesn't work because it can't call a method on each object to determine whether to filter it (or can it?). I've tried writing a function that takes the splatted array as an argument by reference, this doesn't work either:
function skipIncomplete(CompletableObjectInterface &...$objects): array {
$skipped = [];
foreach ($objects as $index => $item) {
if (!$item->isComplete()) {
$skipped[] = $item->id ?? $index;
unset($objects[$index]);
}
}
return $skipped;
}
The original elements passed in simply don't end up getting unset.
I'm looking for a way that doesn't include creating an entirely new Collection class to hold my CompletableObjects for complexity reasons. I really want to keep the type hint so no one can pass in a generic array, causing runtime errors when the function tries to call $item->isComplete.
Is there any way I can achieve this in PHP 7.3.15?
Added a filter, please comment as to what is wrong with this type of approach:
<?php
interface CompletableObjectInterface {
public function isComplete() : bool;
}
class Foo implements CompletableObjectInterface
{
public function isComplete() : bool
{
return false;
}
}
class Bar implements CompletableObjectInterface
{
public function isComplete() : bool
{
return true;
}
}
$foo = new Foo;
$bar = new Bar;
$incomplete = array_filter([$foo, $bar], function($obj) { return !$obj->isComplete();});
var_dump($incomplete);
Output:
array(1) {
[0]=>
object(Foo)#1 (0) {
}
}
Looks like you got a bit hung up on a wrong understanding of the ... syntax for a variable number of arguments.
You are passing in one array, and the $objects parameter will therefore contain that array in the first index, i.e. in $objects[0].
So in theory you could just change your line
unset($objects[$index]);
to
unset($objects[0][$index]);
However, I do not really see why the variable number of arguments syntax is used at all, since you apparently are just expecting one array of values (objects in this case) as an argument to the function. Therefore I'd recommend you just remove the ... from the argument list and your solution does what you wanted.
Alternatively you can of course add an outer foreach-loop and iterate over all passed "arrays of objects", if that is an use case for you.
Related
I've built a singleton class with chaining methods (to be used in a template).
To make chaining work I need to return new static. It allows the next chain to be added. The problem I have is that I don't want to return the static object if there are no more chains.
Example
<?php
class bread {
public static $array;
public static function blueprints() {
static::$array = array('some', 'values');
return new static;
}
public static function fields() {
return static::$array;
}
}
$blueprints = bread::blueprints();
$fields = bread::blueprints()->fields();
print_r($blueprint) // Returns object - FAIL
print_r($fields ) // Returns array - OK
In the example above I want $blueprints to return an array, because there are no more methods chained on it.
How can that be done?
The simple answer is you cannot do what you want.
Method chaining is not a special thing for Php.
For your example
bread::blueprints()->fields();
This is not different than:
$tmp = bread::blueprints();
$tmp->fields();
So because of the Php does not know the context where the result will be used of it cannot change the return type.
Here is another version of this question:
Check if call is method chaining
However, your class can implement ArrayAccess interface.This will allow you to treat the object like an array without casting and you get total control over how the members are used.
You can try this:
$blueprints = (array)bread::blueprints();
class Test {
public function results() {
$return['first'] = 'one';
$return['second'] = 'two';
return $return;
}
}
$test = new Test;
print_r($test->results()); // Returns entire array
I just want to return a single specified element from the array, such as the value of key "second". How do I do this without sifting through the entire array after it's returned?
I just want to return a single specified element from the array, such as the value of key "second"
Pass in an argument to identify which element to return, and return that (or false if it doesn't exist - for example);
public function results($key = null)
{
$return['first'] = 'one';
$return['second'] = 'two';
// check the key exists
if (!array_key_exists($key, $return)) {
return false;
}
return $return[$key];
}
Then:
print_r($test->results('second')); // two
How do I do this without sifting through the entire array after it's returned?
It's important to note that you do not need to "sift through the entire array" to retrieve a value by its key. You know the key, so you can access it directly.
class Test {
private $arr; //private property of object
__construct(){
//create arr in constructor
$this->arr=[];//create new array
$this->arr['first'] = 'one';
$this->arr['second'] = 'two';
}
/**
/* get array
**/
public function getResults(){
return $this->arr;
}
/**
/* get single array element
**/
public function getResult($key) {
return isset($this->arr[$key])?$this->arr[$key]:null;//return element on $key or null if no result
}
}
$test = new Test();
print_r($test->getResult("second")); // Returns array element
//or second possibility but the same result
print_r($test->getResults()["second"]); // Returns array element
Few advices:
Create data structure in constructor ($arr in this particular case) because creating it on very results method call is not any kind of using objects or objective programming. Imagine that if array is created in results method then on every call new array is located in memory, this is not efficent, not optimal and gives no possibility to modify this array inside class Test.
Next in method results add parameter to get only this key what is needed and hide all array in private class property $arr to encapsulate it in object.
And last my private opinion for naming style:
Use camelCase when naming method names.
In PHP an array value can be dereferenced from the array by its key.
$arr = ["foo" => "bar", "baz" => "quix"];
echo $arr["foo"]; // gives us "bar"
echo $arr["baz"]; // gives us "quix"
If the method/function returns an array the same can be done with the return value, whether by assigning the return value to a variable and using the variable to dereference the value by key, or by using function array dereferencing.
class Test {
public function results() {
return ["foo" => "bar", "baz" => "quix"];
}
}
$test = new Test;
$arr = $test->results();
echo $arr["foo"]; // gives us "bar"
echo $arr["baz"]; // gives us "quix"
// Using FAD (Function Array Dereferencing)
echo $test->results()["foo"]; // gives us "bar"
echo $test->results()["baz"]; // gives us "quix"
Of course there are two important caveats to using function array dereferencing.
The function is executed each time you do it (i.e no memoziation)
If the key does not exist or the function returns something other than array, you get an error
Which means it's usually safer to rely on assigning the return value to a variable first and then doing your validation there for safety... Unless you are sure the function will always return an array with that key and you know you won't need to reuse the array.
In PHP 5.4 and above:
print_r($test->results()['second']);
In older versions which you shouldn't be running as they are out of security maintenance:
$results = $test->results();
print_r($results['second']);
Edit: The first example originally said 5.6 and above but array dereferencing was introduced in 5.4! For the avoidance of doubt, 5.6 is the lowest php version within security maintenance.
Assume this class code:
class Foo {
function method() {
echo 'works';
}
}
Is there any way to store a reference to the method method of a Foo instance?
I'm just experimenting and fiddling around, my goal is checking whether PHP allows to call $FooInstance->method() without writing $FooInstance-> every time. I know I could write a function wrapper for this, but I'm more interested in getting a reference to the instance method.
For example, this pseudo-code would theoretically store $foo->method in the $method variable:
$foo = new Foo();
$method = $foo->method; //Undefined property: Foo::$method
$method();
Apparently, as method is a method and I'm not calling it with () the interpreter thinks I'm looking for a property thus this doesn't work.
I've read through Returning References but the examples only show how to return references to variables, not methods.
Therefore, I've adapted my code to store an anonymous function in a variable and return it:
class Foo {
function &method() {
$fn = function() {
echo 'works';
};
return $fn;
}
}
$foo = new Foo();
$method = &$foo->method();
$method();
This works, but is rather ugly. Also, there's no neat way to call it a single time, as this seems to require storing the returned function in a variable prior to calling it: $foo->method()(); and ($foo->method())(); are syntax errors.
Also, I've tried returning the anonymous function directly without storing it in a variable, but then I get the following notice:
Notice: Only variable references should be returned by reference
Does this mean that returning/storing a reference to a class instance method is impossible/discouraged or am I overlooking something?
Update: I don't mind adding a getter if necessary, the goal is just getting a reference to the method. I've even tried:
class Foo {
var $fn = function() {
echo 'works';
};
function &method() {
return $this->fn;
}
}
But from the unexpected 'function' (T_FUNCTION) error I'd believe that PHP wisely doesn't allow properties to store functions.
I'm starting to believe that my goal isn't easily achievable without the use of ugly hacks as eval().
It is. You have to use an array, with two values: the class instance (or string of the class name if you are calling a static method) and the method name as a string. This is documented on the Callbacks Man page:
A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
Demo (Codepad):
<?php
class Something {
public function abc() {
echo 'called';
}
}
$some = new Something;
$meth = array($some, 'abc');
$meth(); // 'called'
Note this is also works with the built-ins that require callbacks (Codepad):
class Filter {
public function doFilter($value) {
return $value !== 3;
}
}
$filter = new Filter;
$test = array(1,2,3,4,5);
var_dump(array_filter($test, array($filter, 'doFilter'))); // 'array(1,2,4,5)'
And for static methods -- note the 'Filter' instead of an instance of a class as the first element in the array (Codepad):
class Filter {
public static function doFilter($value) {
return $value !== 3;
}
}
$test = array(1,2,3,4,5);
var_dump(array_filter($test, array('Filter', 'doFilter'))); // 'array(1,2,4,5)'
// -------- or -----------
var_dump(array_filter($test, 'Filter::doFilter')); // As of PHP 5.2.3
Yes, you can. PHP has a "callable" pseudo-type, which is, in fact, either just a string or an array. Several functions (usort comes to mind) accept a parameter of the "callback" type: in fact, they just want a function name, or an object-method pair.
That's right, strings are callable:
$fn = "strlen";
$fn("string"); // returns 6
As mentioned, it's possible to use an array as a callback, too. In that case, the first element has to be an object, and the second argument must be a method name:
$obj = new Foo();
$fn = array($obj, "method");
$fn(); // calls $obj->method()
Previously, you had to use call_user_func to call them, but syntax sugar in recent versions make it possible to perform the call straight on variables.
You can read more on the "callable" documentation page.
No, as far as I know it's not possible to store a reference to a method in PHP. Storing object / class name and a method name in an array works, but it's just an array without any special meaning. You can play with the array as you please, for example:
$ref = [new My_Class(), "x"];
// all is fine here ...
$ref();
// but this also valid, now the 'reference' points to My_Other_Class::x()
// do you expect real reference to behave like this?
$ref[0] = new My_Other_Class();
$ref();
// this is also valid syntax, but it throws fatal error
$ref[0] = 1;
$ref();
// let's assume My_Class::y() is a protected method, this won't work outside My_Class
$ref = [new My_Class(), 'y'];
$ref();
this is prone to error as you loose syntax checking due to storing the method name as string.
you can't pass reliably a reference to a private or a protected method this way (unless you call the reference from a context that already has proper access to the method).
Personally I prefer to use lambdas:
$ref = function() use($my_object) { $my_object->x(); }
If you do this from inside $my_object it gets less clunky thanks to access to $this:
$ref = function() { $this->x(); }
this works with protected / private methods
syntax checking works in IDE (less bugs)
unfortunately it's less concise
Quick one:
Is there any way to enforce types for variadic functions in PHP? I'm assuming not, however maybe I've missed something.
As of now, I'm just forcing a single required argument of the needed type, and iterating to check the rest.
public function myFunction(MyClass $object){
foreach(func_get_args() as $object){
if(!($object instanceof MyClass)){
// throw exception or something
}
$this->_objects[] = $object;
}
}
Any better solutions?
Purpose:
A container object that acts as an iterated list of the child objects, with some utility functions. calling it with a variadic constructor would be something like this:
// returned directly from include
return new MyParent(
new MyChild($params),
new MyChild($params),
new MyChild($params)
);
The other option could be an addChild method chain:
$parent = new MyParent;
return $parent
->addChild(new MyChild($params))
->addChild(new MyChild($params))
->addChild(new MyChild($params));
The children take several arguments to their constructor as well, so I'm trying to balance between legibility and processing expense.
This is now possible with PHP 5.6.x, using the ... operator (also known as splat operator in some languages):
Example:
function addDateIntervalsToDateTime( DateTime $dt, DateInterval ...$intervals )
{
foreach ( $intervals as $interval ) {
$dt->add( $interval );
}
return $dt;
}
Well I would say it depends on the number of arguments :) There is nothing like a list (all arguments 1-n as MyClass [before PHP 5.6, for PHP 5.6+ see Variadic functions]), it's more that you need to write each argument (as in your question) and it's allowed to send more but only the ones defined will be checked.
However you can define more and make them optional. Hence why I just wrote it depends on the number of arguments:
public function myFunction(MyClass $object, MyClass $object2=null, MyClass $object3=null, MyClass $object4=null, ...){
foreach(func_get_args() as $object){
if(null === $object){
// throw exception or something
}
$this->_objects[] = $object;
}
}
With such an example, PHP would have thrown the exception already when the argument is not NULL and not MyClass (Passing NULL if given as default value, is possible to pass as well). Optional parameter should not be part of the return value of func_get_args(). I mean, if you don't pass the third argument (here named $object3) it won't appear in the array returned by func_get_args().
No idea if that is more practicable for you than your the code in the question.
If you face more such situations in your code, you can create some sort of helper to validate the input of function calls and delegate to throw the exceptions with nice error message from the helper. That will prevent duplicate code and will give more comfort in development as you can make the exception notices nicer as you write them for multiple cases.
According to your new feedback, if you want first of all the interpreter let the work of checking, a little iteration and the addMember function would do it:
class MyParent {
private $children = array();
public function __construct() {
foreach(func_get_args() as $object) $this->addChild($object);
}
public function addChild(MyChild $object) {
$this->children[] = $object;
}
}
Instantiating the object with a wrong type of object in any of the list will prevent the Collection to be instantiated.
you can then just do this:
// returned directly from include
return new MyParent(
new MyChild($params),
new MyChild($params),
new MyChild($params)
);
I'm looking to create an array or list with elements of a certain type (eg objects the implement a certain interface). I know I can create an object that does the same thing implementing Traversable and Iterator, or override ArrayObject. But maybe there's another way I have missed.
Do you mean something like:
$array=Array();
foreach ($itemsToAdd as $item) {
if ($item instanceof NameOfwantedInterface) {
Array_push($array,$item);
}
}
If you don't, them I'm sorry - it's just that your question isn't too clear.
I would write a custom class that extended ArrayObject and threw an exception if you tried to assign a variable that wasn't the correct type, there's really no better way to do it that I can think of.
PHP as a lanugage is very flexible in terms of type handling and type conversion. You will probably have to put a manual check in if you want any kind of strong type checking, a simple if statement will do.
The array object is designed to be especially flexible (lazy key assignment, automatic increment, string or integer keys, etc.) so you should probably use a custom object of your own.
You could use type hinting:
<?php
interface Shape
{
function draw();
}
class MyArray
{
private $array = array();
function addValue(Shape $shape) //The hinting happens here
{
$array[] = $shape;
}
}
?>
This example is not perfect, but you'll get the idea.
Basically, you are going to want to do a function that checks if the variable you are inserting into the array is an object.
function add($var)
{
if(is_object($var))
{
$this->array[] = $var;
}
}
If you want to make sure it has a specific class name, you would do something like this (for PHP5):
function add(className $var)
{
$this->array[] = $var;
}
or this for previous PHP versions:
function add($var)
{
if($var instanceOf className)
{
$this->array[] = $var
}
}
You might want to look into array_filter() to do this without building an object.
Looking at that page, I've found that you can use array_filter with common functions like is_object. So doing something like this:
$this->array = array_filter($this->array ,'is_object');
Would filter the array to contain only objects.