Recently I ran into an interesting situation when implementing a PHP application using PhpStorm. The following code snippet illustrates the problem.
interface I{
function foo();
}
trait T{
/**
* #return string
*/
public function getTraitMsg()
{
return "I am a trait";
}
}
class A implements I{
use T;
function foo(){}
}
class C implements I{
use T;
function foo(){}
}
class B {
/**
* #param I $input <===Is there anyway to specify that $input use T?
*/
public function doSomethingCool($input){ //An instance of "A" or "C"
$msg = $input -> getTraitMsg(); //Phpstorm freaks out here
}
}
My question is in the comment. How do I indicate that $input parameter implements I and uses T?
It's a lit bit hackly, but you can use class_uses it returns list of used traits. And add T as a #param type in PHPDoc for autocomplete
class B {
/**
* #param I|T $input <===Is there anyway to specify that $input use T?
*/
public function doSomethingCool($input){ //An instance of "A" or "C"
$uses = class_uses(get_class($input));
if (!empty($uses['T'])) {
echo $input->getTraitMsg(); //Phpstorm freaks out here
}
}
}
AFAIK you cannot type hint a trait usage in such way (#param only accepts scalar types or classes/interfaces + some keywords).
The ideal solution for you would be placing getTraitMsg() declaration into I interface.
If this cannot be done .. then you can specify that only instances of A or C can be passed here (as they utilize that trait):
/**
* #param A|C $input
*/
public function doSomethingCool($input)
{
$msg = $input->getTraitMsg(); // PhpStorm is good now
}
If names of such possible classes are unknown in advance (e.g. it's a library code and final classes could be anything in every new project or even added in current project at any time) .. then I suggest to use safeguards, which you should be using with such code anyway (via method_exists()):
/**
* #param I $input
*/
public function doSomethingCool($input)
{
if (method_exists($input, 'getTraitMsg')) {
$msg = $input->getTraitMsg(); // PhpStorm is good now
}
}
Why use safeguard? Because you may pass instance of another class K that implements I but does not use trait T. In such case code without guard will break.
Just to clarify: you could use #param I|T $input to specify that method expects instance that implements I or uses T .. but it's only works for PhpStorm (not sure about other IDEs) -- AFAIK it's not accepted by actual PHPDocumentor and does not seem to fit the PHPDoc proposed standard.
"//Phpstorm freaks out here" -- no, it's not. It just tries to signal you that your code is not correct.
The contract of method doSomethingCool() doesn't require $input to expose any method named getTraitMsg(). The docblock says it should implement interface I but the docblock is not code, it only helps PhpStorm help you with validations and suggestions.
Because you didn't type-hinted the argument $input, the code:
$b = new B();
$b->doSomethingCool(1);
is valid but it crashes as soon as it tries to execute the line $msg = $input -> getTraitMsg();.
If you want to call getTraitMsg() on $input you have to:
declare the type of $input;
make sure the declared type of $input exposes a method named getTraitMsg().
For the first step, your existing code of class B should read:
class B {
/**
* #param I $input
*/
public function doSomethingCool(I $input) {
$msg = $input -> getTraitMsg();
}
}
Please remark the type I in front of argument $input in the parameters list.
The easiest way to accomplish the next step is to declare method getTraitMsg() into the interface I:
interface I {
function foo();
function getTraitMsg();
}
Now, the code:
$b = new B();
$b->doSomethingCool(1);
throws an exception when it reaches the line $b->doSomethingCool(1); (i.e. before entering the function). It is the PHP's way to tell you the method is not invoked with the correct arguments. You have to pass it an object that implements the interface I, no matter if it is of type A or C. It can be of any other type that implements interface I and nobody cares if it uses trait T to implement it or not.
Related
What is the correct way of setting a type for mocked objects?
Example code:
/**
* #dataProvider getTestDataProvider
* #throws Exception
*/
public function testExampleData(
Request $request,
Response $expected,
SomeClass $someClassMock
): void {
$result = $someClassMock->getData($request);
$this->assertEquals($expected, $result);
}
In this example the type of $someClassMock is class SomeClass. Also there is a type called MockObject which is also working properly, but it messes up the autocompletion of functions inside that class.
Which types should I use on these mocked objects? Real object class or MockObject?
What I do to make sure the auto completion works for the mocked class as well as the MockObject, is tell php that it can be either class. This adds the auto complete for both and also makes it quite understandable for anyone reading the code that it is a mock and what object it is mocking.
In your case it would look like this:
/**
* #dataProvider getTestDataProvider
* #throws Exception
*/
public function testExampleData(
Request $request,
Response $expected,
SomeClass|MockObject $someClassMock // <-- Change made here
): void {
$result = $someClassMock->getData($request);
$this->assertEquals($expected, $result);
}
You will still be passing in the MockObject, but your IDE will not complain about any unknown functions.
EDIT: To make it work with PHP versions before 8, I would suggest something like this (minimal example):
class ExampleTest extends TestCase
{
private someClass | MockObject $someClassMock;
public function setUp(): void
{
$this->someClassMock = $this->createMock(SomeClass::Class);
}
public function testMe()
{
$this->someClassMock->expects($this->once())->method('getData')->willReturn('someStuff');
}
}
In this scenario the variable $someClassMock is declared as both types and you can use it throughout your test with autocompletion for both of them. This should also work with your data provider although you might have to rewrite it slightly. I didn't include it in my example to keep it simple.
Given the following code structure is there a way that I can get the return type of FooFactory->createService method with PHP 5.6? I've tried ReflectionClass and ReflectionMethod classes but couldn't find a way.Thanks in advance.
class FooFactory implements FactoryInterface {
public function createService(/*args*/)
{
$foo = new Foo();
// ...
//inject dependencies
// ...
return $foo;
}
}
class Foo {
/*...methods...*/
}
Edit: I need to get the class name without creating an instance of the FooFactory.
Using PHP5 this is not possible.
You could (and this is more of a workaround than a solution) type hint your variable on declaration. This would expose the methods etc belonging to created service to your IDE.
Example:
/* #var \MyFooService $myFooService */
$myFooService = FooFactory->createServixce('MyFooService');
Note it is the #var declaration that will inform your editor of the variable type.
Syntax being:
/* #var [CLASS_NAME] [VARIABLE] */
UPDATE:
Just seen you dont want to create an instance of FooFactory.
By default arn't factory methods static?
So:
$fooFactory = new FooFactory();
$fooService = FooFactory->getService('FooService');
Becomes:
$fooService = FooFactory::getService('FooService');
class myclass {
private $myemail = '';
private $myPrefix = '';
/**
* #return string
*/
public function getmyPrefix()
{
return $this->myPrefix;
}
/**
* #return string
*/
public function getmyEmail()
{
return $this->myemail;
}
/**
* #param string $email
*/
public function setmyEmail($email)
{
$this->myemail = $email;
}
}
I want to write a php unit tests to test the private variables in this class but I am not sure how to test the variable $myPrefix because it does not have a setter ? Do I need to create a mock class ?
Thanks
You can do a simple test to ensure the initial value is null by simply testing the return from the class. Then use your Setter or __construct() to populate the field, and test the return from the getter again.
Optionally, and you do not need to go to this level of testing internal private fields as they should not change from outside the module, you can use Reflection.
I have some library classes that do things based on inputs, and I do use reflection to test the odd setting to be updated/set as I would expect for the function/method to work properly. This is done sparingly as the private values should not be changed external to the code library, but the test is for developer documentation purposes, and to ensure the basic functionality and assumptions in the code are not broken.
From the PHP Manual
$class = new ReflectionClass('myClass');
$property = $class->getProperty('myPrefix');
$this->assertEquals("Something from your setter", $property);
I would only use this to ensure that the set method (or constructor) is directly setting the field, not manipulating it in any way. However, with a get method, this is not needed since the get..() can test this. I only added the quick reference code to show you could test a value that did not have a get method.
I don't know how to phrase this one exactly but what I need is a way to load the type-hinting of a classes methods. Basically I have a base class that has a get function that looks like this:
class Base {
/**
*
* #param type $i
* #return \i
*/
public static function get($i) {
// make sure it exists before creating
$classes = get_declared_classes();
if (!in_array($i, $classes)) {
if (class_exists($i)) {
return new $i();
}
}
return $i;
}
}
Now for an example, say I had a class called test:
class test {
function derp() {
echo 'derp';
}
}
I'd instantiate the test object by something like this:
$test = base::get('test');
Now what I'd like to be able to do is as I type like this:
$test->
The methods (Currently only derp()) should be suggested, I've seen documents all around SO but they don't work :(
What's weird is that if I change the #return comment to the test class name then the suggestions work.
BUT it the classes are all not set, there could be different classes instantiated, hence why I tried #returns \i (suggested by netbeans). Is there any way to achieve this?
EDIT
The reason I need the type hinting is to allow for calling methods like the following:
base::get('test')->derp();
What always works is this:
/** #var ADDTYPEHERE $test */
$test = base::get('test');
What also works is this:
if ($test instanceof CLASS_IT_IS) {
// completion works in here
}
The solution you want can never work, since your IDE (netbeans) cannot know what class you instantiated, without any hint like one of the above.
TL;DR
I want to override offsetSet($index,$value) from ArrayObject like this: offsetSet($index, MyClass $value) but it generates a fatal error ("declaration must be compatible").
What & Why
I'm trying to create an ArrayObject child-class that forces all values to be of a certain object. My plan was to do this by overriding all functions that add values and giving them a type-hint, so you cannot add anything other than values of MyClass
How
First stop: append($value);
From the SPL:
/**
* Appends the value
* #link http://www.php.net/manual/en/arrayobject.append.php
* #param value mixed <p>
* The value being appended.
* </p>
* #return void
*/
public function append ($value) {}
My version:
/**
* #param MyClass $value
*/
public function append(Myclass $value){
parent::append($value);
}
Seems to work like a charm.
You can find and example of this working here
Second stop: offsetSet($index,$value);
Again, from the SPL:
/**
* Sets the value at the specified index to newval
* #link http://www.php.net/manual/en/arrayobject.offsetset.php
* #param index mixed <p>
* The index being set.
* </p>
* #param newval mixed <p>
* The new value for the index.
* </p>
* #return void
*/
public function offsetSet ($index, $newval) {}
And my version:
/**
* #param mixed $index
* #param Myclass $newval
*/
public function offsetSet ($index, Myclass $newval){
parent::offsetSet($index, $newval);
}
This, however, generates the following fatal error:
Fatal error: Declaration of
Namespace\MyArrayObject::offsetSet() must be
compatible with that of ArrayAccess::offsetSet()
You can see a version of this NOT working here
If I define it like this, it is fine:
public function offsetSet ($index, $newval){
parent::offsetSet($index, $newval);
}
You can see a version of this working here
Questions
Why doesn't overriding offsetSet() work with above code, but append() does?
Do I have all the functions that add objects if I add a definition of exchangeArray() next to those of append() and offsetSet()?
abstract public void offsetSet ( mixed $offset , mixed $value )
is declared by the ArrayAccess interface while public void append ( mixed $value ) doesn't have a corresponding interface. Apparently php is more "forgiving"/lax/whatever in the latter case than with interfaces.
e.g.
<?php
class A {
public function foo($x) { }
}
class B extends A {
public function foo(array $x) { }
}
"only" prints a warning
Strict Standards: Declaration of B::foo() should be compatible with A::foo($x)
while
<?php
interface A {
public function foo($x);
}
class B implements A {
public function foo(array $x) { }
}
bails out with
Fatal error: Declaration of B::foo() must be compatible with A::foo($x)
APIs should never be made more specific.
In fact, I consider it a bug that append(Myclass $value) isn't a fatal error. I consider the The fatal error on your offsetSet() as correct.
The reason for this is simple:
function f(ArrayObject $ao) {
$ao->append(5); //Error
}
$ao = new YourArrayObject();
With an append with a type requirement, that will error. Nothing looks wrong with it though. You've effectively made the API more specific, and references to the base class are no longer able to be assumed to have the expected API.
What is basically comes down to is that if an API is made more specific, that sub class is no longer compatible with it's parent class.
This odd disparity can be seen with f: it allows you to pass a Test to it but will then fail on the $ao->append(5) execution. If a echo 'hello world'; were above it, that would execute. I consider that incorrect behavior.
In a language like C++, Java or C#, this is where generics would come into play. In PHP, I'm afraid there's not a pretty solution to this. Run time checks would be nasty and error prone, and rolling your own class would completely obliterate the advantages of having ArrayObject as the base class. Unfortunately, the desire to have ArrayObject as the base class is also the problem here. It stores mixed types, so your subclasses must store mixed types as well.
You could perhaps implement that ArrayAccess interface in your own class and clearly mark that the class is only meant to be used with a certain type of object. That would still be a bit clumsy though, I fear.
Without generics, there's not a way to have a generalized homogeneous container without runtime instanceof-style checks. The only way would be to have a ClassAArrayObject, ClassBArrayObject, etc.