PHPStan : howto get child type returned, not parent - phpstan

I spent time reading PHPStan documentation and trying, but I don't understand how to get the child class detected by PHPStan in this code, or even if it is possible...
<?php declare(strict_types = 1);
abstract class Bar {}
class Foo extends Bar
{
}
class Foofoo extends Bar
{
}
class Baz
{
private Bar $_bar;
/**
* #param Bar $bar
*
* #return void
*/
public function setBar(Bar $bar)
{
$this->_bar=$bar;
}
/**
*
* #return Bar
*/
public function bar(): Bar
{
return $this->_bar;
}
}
$baz=new Baz();
$baz->setBar(new Foo());
\PHPStan\dumpType($baz->bar()); // return type is Bar, I would like Foo
$baz->setBar(new Foofoo());
\PHPStan\dumpType($baz->bar()); // return type is Bar, I would like Foofoo
The link to the playground :
https://phpstan.org/r/53aebcc3-717b-445c-8715-0f277151270b
Thanks for your help !

According to the founder of PHPStan (see Github discussion):
The only way to achieve this is with generics:
https://phpstan.org/r/5ed2bf49-e9fe-43b6-b279-182458ff358a
<?php declare(strict_types = 1);
abstract class Bar {
}
class Foo extends Bar
{
}
class Foofoo extends Bar
{
}
/** #template T of Bar */
class Baz {
private Bar $_bar;
/**
* #param T $bar
*
* #return void
*/
public function setBar(Bar $bar)
{
$this->_bar=$bar;
}
/**
*
* #return T
*/
public function bar(): Bar
{
return $this->_bar;
}
}
/** #var Baz<Foo> */
$baz=new Baz();
$baz->setBar(new Foo());
\PHPStan\dumpType($baz->bar()); // return type is Bar, I would like Foo
$baz->setBar(new Foofoo());
\PHPStan\dumpType($baz->bar()); // return type is Bar, I would like Foofoo

Related

Mapped superclass error when extend abstract class

I have some common fields in almost all tables. So I created an AbstractEntity class where I can add fields that are in common.
/**
* #MappedSuperclass
*/
abstract class AbstractEntity
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer", options={"unsigned"=true})
*/
protected int $id;
/**
* #ORM\Column(name="created_at", type="datetime", nullable=false)
*/
protected string $createdAt;
public function getId(): int
{
return $this->id;
}
public function createdAt(): string
{
return $this->createdAt;
}
public function setShopLocale(string $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
Now, I extend Abstractclass in other EntityClass
class Random extends AbstractEntity
{
}
I am getting following error message when I try to insert record.
Class "App\Entity\Admin\Random" sub class of "App\Entity\Admin\AbstractEntity" is not a valid entity or mapped super class.

How to stub a public setter method for a protected property in Mockery

I want to stub a public setter method (setA()) for a protected property in the same class I am testing, but I am getting this error:
Cannot access protected property Mockery_2_Foo::$a
My goal is just to unit-test method1, but that method is calling setA, so I am not sure how I can stab out setA so it won't in any way interfere with the unit-test result for method1.
class Foo {
protected $a;
public function setA($val) {
$this->a = $val * 2;
}
public function method1($val) {
$this->setA($val);
return $val * 4;
}
............
}
/**
* #coversDefaultClass Foo
*/
class FooTest {
public function setUp(): void {
$this->foo = Mockery::mock(Foo::class)
->shouldAllowMockingProtectedMethods()
->makePartial();
}
/**
* #covers ::method1
*/
public function testMethod1() {
$this->foo->shouldReceive('setA')
->set('a', 4);
$this->assertEquals(8, $this->foo->method1(2));
}
..........
}``
Mockery can only set public properties (see docs).
I don't really get what you are trying to do, but maybe it's just because of your simplified example. Instead of using Mockery you could just create an anonymous class that extends Foo and adds the desired behavior:
class FooTest extends TestCase
{
public function testMethod1()
{
$foo = new class extends Foo {
public function setA($val)
{
$this->a = 4;
}
};
self::assertEquals(8, $foo->method1(2));
}
}

Doxygen class and function in a same member group

Can a class be put into a member group like a function? For example,
/** Some module.
*
* #defgroup Group
* #{
*/
/** #name Same utility
*
*/
/** #{*/
/** Class A.
*
*/
class A
{
};
/** Function.
*
*/
void f();
/** #}*/ // end Same utility
/** Class B.
*
*/
class B
{
};
/** #}*/ // end Group
The above code will put f() into a custom section called "Same utility", but the document for class A is still in the section "class" with class B. So is there a way to group the class A into a member group as well?

doxygen - how to "using" within subclass?

If I have a subclass which reintroduces members of the base class with "using", how do I get doxygen to create entries / docs for these members?
class Foo {
protected:
virtual void hello();
};
class Bar : protected Foo {
public:
/**
* \brief Reintroduced
* This documentation will not show up :-(
*/
using Foo::hello;
};

Cannot get doctrine 2 many-to-many relationships to work

I have 2 entities - a User and a Tag.
This is my user:
<?php
namespace Project\Model;
/**
* #Entity
* #Table(name="users")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"user" = "User", "client" = "Client", "staff" = "Staff"})
**/
class User implements \JsonSerializable {
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="string", name="first_name") **/
protected $firstName;
/**
* #ManyToMany(targetEntity="Project\Model\Tag", inversedBy="users")
* #JoinTable(name="user_tags")
**/
protected $tags;
/**
* Construct a new user.
*/
public function __construct() {
$this->tags = new \Doctrine\Common\Collections\ArrayCollection();
}
// Getters
public function getId() {
return $this->id;
}
public function getFirstName() {
return $this->firstName;
}
public function getTags() {
return $this->tags;
}
// Setters
public function setFirstName($firstName) {
$this->firstName = $firstName;
}
/**
* Add a tag to a user.
* #param Tag
*/
public function addTag(Tag $tag) {
$tag->addUser($this);
$this->tags[] = $tag;
}
}
This is my Tag:
<?php
namespace Project\Model;
/**
* #Entity
* #Table(name="tags")
**/
class Tag implements \JsonSerializable {
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="string") **/
protected $tag;
/**
* #ManyToMany(targetEntity="Project\Model\User", mappedBy="tags")
*/
protected $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
// Getters
public function getId() {
return $this->id;
}
public function getTag() {
return $this->tag;
}
// Setters
public function setTag($tag) {
$this->tag = $tag;
}
public function addUser(User $user) {
$this->users[] = $user;
}
}
If I create a new Tag, a new User, add the Tag to the User, then call the getTag() method, it returns nothing -- can anybody help me out where I am going wrong?
$tag = new Tag();
$tag->setTag('Foo');
$entityManager->persist($tag);
$user = new User();
$user->addTag($tag);
$entityManager->persist($user);
$entityManger->flush();
var_dump($user->getTags());
I think the problem might come from your ManyToMany relation. You use
#ManyToMany(targetEntity="Tag", inversedBy="users")
whereas you should have something like (assuming you are using Symfony 2 of course) :
#ManyToMany(targetEntity="AppBundle\Entity\Tag")
Also, you use a inversedBybut no mappedBy, so your mapping is invalid.
And this last one is more of a detail, but name a property "tag" inside a Tag class is not the cleanest. Maybe change it to "name".
In your Tag class, you reference the User class using:
/**
* #ManyToMany(targetEntity="Bix\Model\User", mappedBy="tags")
*/
Should "Bix" be "Project"? Unless this was a typo in your question, that woud cause issues.
One side should "own" the association and be responsible for setting the inverse association when it is added.
<?php
// Assuming that the User is the "owning side".
class User {
// Mappings as you have them, minus the "Bix" namespace thing.
public function getTags()
{
return $this->tags;
}
public function addTag(Tag $tag)
{
$tag->addUser($this);
$this->tags->add($tag);
}
public function removeTag(Tag $tag)
{
$tag->removeUser($this);
$this->tags->removeElement($tag);
}
}
class Tag {
// Mappings as you have them, minus the "Bix" namespace thing.
public function getUsers()
{
return $this->users;
}
public function addUser(User $user)
{
$this->users->add($user);
}
public function removeUser(User $user)
{
$this->users->removeElement($user);
}
}