Check for undefined PHP method before calling? - php

What can I use other than if(!empty( $product->a_funky_function() )) to check if the method is empty before calling it?
I've tried method_exists() and function_exists() and a whole plethora of conditions. I think the issue is that I need to have my $product variable there.
Please help.

A fairly common pattern is to have two methods on your class, along the lines of getField and hasField. The former returns the value, and the latter returns true or false depending whether or not the value is set (where "set" can mean not null, or not empty, or whatever else you might want it to mean).
An example:
class Foo
{
/** #var string */
private $field;
/**
* #return string
*/
public function getField()
{
return $this->field;
}
/**
* #return bool
*/
public function hasField()
{
return $this->getField() !== null;
}
}
This would then be used like:
if ($foo->hasField()) {
$field = $foo->getField();
...
}
Often, like in this example, the has... method often just delegates to the get... method internally, to save duplicating the logic. If the getter contains particularly heavy processing (e.g. a database lookup or API call), you might want to factor in ways to avoid performing it twice, but that's a bit out of scope.

You need absolutely to call it so it can compute the value which it will return, so I think there is no other way to know if it will return something not empty without calling it...

You have to call it.
$result = $product->getFunction();
if(!empty($result)) {
//your code here
}

Related

What to set as "return" inside function when only handle something?

I have a data import class with many private functions. In most of functions there is json string handle to import data, so I don't have anything to return. Now what is the best practice to set as return of such functions?
Is return; or return true; correctly?
I think, if you don't need to return any value - you must not use return; nor return true;.
In this case your function works not as pure function (it performs some mutation, or update some data in database or in cache or print data into output or stream etc) and it's perfectly ok!) But if you need this return for unit-tests - please don't do it, because it's ridiculous. Test your real behavior, check mutation, check output and so on and so forth...
A return value is not required by a PHP function. It is however, best practice to add a docblock that sais so:
/**
* <function description?
*
* #return void
**/
function doSomething()
{
}
Source: http://php.net/manual/en/functions.returning-values.php
Note:
If the return is omitted the value NULL will be returned.
It's a good practice to use CQS (Command-query separation) approach. It states that every method should either be a command or a query, but not both. So the difference is:
Command - method that changes object state and should not return anything;
Query - method that return some data, but doesn't change object state.
<?php
class Foo
{
private $str = '';
// this is a query
public function getData() : string
{
return $this->str;
}
// this is a command
public function setData(string $str) : void
{
$this->str = $str
}
}
So, in your case functions for importing data are commands and should not return anything.

With php finding out if variable is this exact class instance

I have run into trouble figuring out how to compare two variables which might contain the exact same class instance.
An abstract class (part of which is shown below) has a method fetch_mother() designed to identify the object that should contain it and return that or simply return itself because it is at the bottom of the stack. In theory, that stack should be no more than 5 deep.
Most instances represent things like categories.
With the get get_full_path() method:
Expected output was: [siteurl] /system/drafts/example-one/also-dev-notes/
Actual output was: [siteurl] /drafts/drafts/[snip]/drafts/drafts/example-one/also-dev-notes/
Which means that the sanity check kicks in and breaks a loop. It also means that I have not correctly tested for the returned object being the same as $this.
How can I confirm is $var===$this?
Code where the problem takes place:
<?php
namespace modules\content\classes;
use modules\core\interfaces as i;
use modules\core\classes as c;
abstract class content_object extends c\module_lib {
// vars
// ...
protected $mother;
protected $map
// ... code ...
public function get_object_map(){
return $this->map;
}
/**
* Get the stream holding this item
* #return \modules\content\classes\error|\modules\content\classes\content_object
*/
public function &fetch_mother(){
if(isset($this->mother) && is_object($this->mother)){
return $this->mother;
}
$mother = $this->module()->find_object_stream($this);
if(!($mother instanceof \modules\core\error) && is_object($mother) && $mother != $this){
$this->mother = $mother;
return $mother;
}else{
// I am my own mother ? \\
return $this;
}
}
protected function fetch_full_path_from_mother($path='',$sanity=10){
$map = $this->get_object_map();
$mother = $this->fetch_mother();
$path = $map . '/' . $path;
if($this==$mother || !is_object($mother) || $sanity<1){
return $path;
}
$sanity--;
return $mother->fetch_full_path_from_mother($path,$sanity);
}
public function get_full_path(){
$home = $this->get_core()->factory()->get_config('home');
return $home . $this->fetch_full_path_from_mother();
}
}
The answer here is non-obvious.
<?php
$foo = $this;
if($foo==$this){
echo 'It is';
}else{
echo 'It is not';
}
The output of the above would be It is.
That's because if the two objects are the same instance then the == comparison would be enough to determine this.
Likewise (as per the comments) spl_object_hash($mother)==spl_object_hash($this) is also true only if it is the same object. However, if another object with the same properties were created the above would be false because they are separate objects.
This question and answers deals with that exact same topic: spl_object_hash matches, objects not identical
The assumption in my question (which I did not see at first) is that the lookup function is acting as a factory and caching objects. The differential conclusion must be that a copy or second instance is being returned.
Thus, the problem must be with the fetch_mother() method.
(Further investigation did indeed show that this was the problem.)
Solutions include checking for matching properties (which in this case works as there are several unique fields pulled from the database) or comparing print_r output.
if(print_r($mother,true)==print_r($this,true)){
// code
}
That particular solution is ugly, inelegant and not very reliable.
A better solution would be to implement an object cache higher up the stack. (Which is what I will be proposing).
TL;DR: Objects with identical properties are still not the same.

How to do hasKeys() in PHP Mockery, or create custom parameter expectations?

Mockery has a method hasKey(), which checks if a given parameter has a certain key. I want to make sure that the passed array has multiple keys. I would also like to assert that the array has x amount of elements.
Is there a built-in way to allow for custom parameter expectations? I tried using a closure that returns true or false based on the given parameter, but that didn't work.
Thanks.
Edit:
Example
$obj = m::mock('MyClass');
$obj->shouldReceive('method')->once()->with(m::hasKey('mykeyname'));
What I'm trying to do is to have more insight into what is passed to the method using with(). I want to assert that an array passed to a method has both key a AND key b. It would be great if I could use a closure somehow to create my own assertion, such as counting the number of array elements.
You can user a custom matcher.
Out of the top of my head (not tested) this could look something like this:
class HasKeysMatcher extends \Mockery\Matcher\MatcherAbstract
{
protected $expectedNumberOfElements;
public function __construct($expectedKeys, $expectedNumberOfElements)
{
parent::__construct($expectedKeys);
$this->expectedNumberOfElements =$expectedNumberOfElements;
}
public function match(&$actual)
{
foreach($this->_expected as $expectedKey){
if (!array_key_exists($expectedKey, $actual)){
return false;
}
}
return $this->expectedNumberOfElements==count($actual);
}
/**
* Return a string representation of this Matcher
*
* #return string
*/
public function __toString()
{
return '<HasKeys>';
}
}
and then use it like this:
$obj = m::mock('MyClass');
$obj->shouldReceive('method')->once()->with(new HasKeysMatcher(array('key1','key2'),5));

Can phpunit use multiple data provider

One question in short: can phpunit use multiple data provider when running test?
For example, I have a method called getById, and I need to run both successful and unsuccessful testcases for it.
The successful testcases means that it can return a corresponding record. And for the unsuccessful, the input can fall in two categories: invalid and failed.
The invalid means that the input is not legal, while failed means the input could be valid, but there is no corresponding record with that ID.
So the code goes like this:
/**
* #dataProvider provideInvalidId
* #dataProvider provideFailedId
*/
public function testGetByIdUnsuccess($id)
{
$this->assertNull($this->model->getById($id));
}
But it turned out that only the first data provider has been used, ignoring the second one. Though I am not sure this senario is common or not, but here is the question. Can we use multiple data provider? And if we can, how?
PS: did not find too much help in here
Just an update to the question, a pull request was accepted and now the code:
/**
* #dataProvider provideInvalidId
* #dataProvider provideFailedId
*/
public function testGetByIdUnsuccess($id)
{
$this->assertNull($this->model->getById($id));
}
Will work on PHPUnit 5.7, you'll be able to add as many providers as you want.
You can use a helper function as shown below. The only problem is if the total number of test cases provided by all "sub data providers" is large, it can be tedious to figure out which test case is causing a problem.
/**
* #dataProvider allIds
*/
public function testGetByIdUnsuccess($id)
{
$this->assertNull($this->model->getById($id));
}
public function allIds()
{
return array_merge(provideInvalidId(),provideFailedId());
}
You can also use CrossDataProviders which allows you to use a combination of data providers with each other
<?php
/**
* #dataProvider provideInvalidIdAndValues
*/
public function testGetByIdUnsuccess($id, $value)
{
$this->assertNull($this->model->getById($id));
}
function provideInvalidIdAndValues() {
return DataProviders::cross(
[[1], [2], [3]],
[['Rob'], ['John'], ['Dennis']]
);
}
You can add a comment to your dataProvider array, to provide the same functionality, while not requiring multiple dataProviders.
public static function DataProvider()
{
return array(
'Invalid Id' => array(123),
'Failed Id' => array(321),
'Id Not Provided' => array(NULL),
);
}
well,you could consider it from another side ;)
you know exactly what is your expected,for example getById(1) expected result is $result_expected,not $result_null
so,you could make a dataprovider like this
$dataProvider = array(1, 'unexpected');
then,your test method like this:
public function testGetById($id) {
$this->assertEquals($result_expected, $obj::getById($id));
}
so,test result is:
.F

Print_r to find allowable values for a method?

I stumbled across this page which talks about the very handy new reflection class that ships with PHP5 and returns all the methods and properties of a class:
print_r to get object methods in PHP?
Following on from this, is there any way to determine the allowable values for the methods it returns?
You mean determine the allowed types of return values, and range of return values, for some method in some class? Very very hardly, I think. After all, there is no way of defining or hinting a return value in PHP. So I could do the following:
class .... {
function myFunction()
{
... some code ....
if (condition) return "A";
.... more code .....
if (condition2) return 2;
.... more code ....
if (condition3) return $_POST["number"];
}
}
this is a totally screwed-up example of course, but you get my point. The possible types of the return value are extremely hard to predict because I could return anything at any point.
I think the best one can do is solve this in documentation blocks. If you follow phpDoc notation:
/**
* #desc Searches for a record.
* #return int the number of found records.
*/
function myMethod()
{ ....
many IDEs are able to at least give you a hint about the expected return type when you type a call to the method.
Well, it depends on what you mean by "allowable values". If it's available in the doc-block, you can probably find it with Reflection... To find the return value:
class foo {
/**
* #returns boolean When false
*/
public function bar($x, $y = 'bar') {
}
}
$reflector = new ReflectionMethod('foo', 'bar');
$comment = $reflector->getDocComment();
if (preg_match('##returns (\\S+)#', $comment, $match)) {
echo "Method returns: ".$match[1];
}
produces:
Method Returns: boolean
Then, you'd just need to parse out what you want from that doc comment... Note it can be a type OR a class (or a grouping of multiple, boolean|null|string|DOMNode)...

Categories