Code completion in injected classes - php

Here is my sample code which is what I want to get, but in User class I get no codecompletion (for $this->app) in PHPSotrm.
How to change that code to enable code completion? I want to avoid global.
namespace myApp
class app
{
public $pdo = "my PDO";
public function __construct() {
$this->user=new User($this);
$this->test=new Test($this);
echo $this->user->getPDO();
//"my PDO"
echo $this->test->getUserPDO();
//"my PDO"
}
}
class User
{
private $app=null;
public function __construct($app) {
$this->app=$app;
}
public function getPDO() {
return $this->app->pdo;
//no code completion
}
}
class Test
{
private $app=null;
public function __construct($app) {
$this->app=$app;
}
public function getUserPDO() {
return $this->app->user->getPDO;
//no code completion
}
}

There's plenty of information on how to achieve this. All you need to do is to add the PHPDoc describing the property type.
class User
{
/**
* #var App
*/
private $app=null;
/**
* #param App $app
*/
public function __construct($app) {
$this->app = $app;
}
/**
* #return PDO
*/
public function getPDO() {
return $this->app->pdo;
}
}
If properties are implemented via magic getters / setters, you can use the same principles on the class itself.
/**
* #property App $app
* #method Pdo getPdo()
*/
class User
{
// …

Related

PHPStan inheritance & property types

I have trouble passing PHPStan tests because of parent/child classes (besides, code runs well).
These two kind of errors always show up:
Parameter #1 $a of method xxx expects ConcreteChildClass, AbstractParentClass given
Property ClassName::$a (ConcreteChildClass) does not accept AbstractParentClass
Here is the code (must be compatible with PHP 5.6):
<?php
abstract class AbstractClassA
{
/**
* #return static
*/
abstract public function postProcess();
}
class ConcreteClassA extends AbstractClassA
{
/**
* #return ConcreteClassA
*/
public function postProcess()
{
return $this;
}
}
abstract class AbstractClassB
{
/**
* #return AbstractClassA
*/
abstract public function deserialize();
/**
* #return AbstractClassA
*/
public function someMethodA($params)
{
return $this->someMethodB();
}
/**
* #return AbstractClassA
*/
public function someMethodB()
{
$object = $this->deserialize();
return $object->postProcess();
}
}
class ConcreteClassB extends AbstractClassB
{
/**
* #return ConcreteClassA
*/
public function deserialize()
{
$object = new ConcreteClassA();
// some assignements
return $object;
}
}
And how this code is used:
class SomeClass
{
/** #var ConcreteClassA $propertyA */
public $propertyA;
/** #var ConcreteClassB $propertyB */
public $propertyB;
public function someMethod()
{
$this->propertyA = $this->propertyB->someMethodA($params);
$this->someProperty->someOtherMethod($this->propertyA);
// again, someOtherMethod expects ConcreteClassA but AbstractClassA given...
}
I tried to "play" with #template without success...
Please also note that I have limited control over abstract classes.
Thanks,

PhpStorm metadata file for repository classes

In our application, we use repositories for models that are fetched from the database. So, we have an abstract repository that knows about the database, has a loadById method to load a database record and an abstract getEntity method that creates an object for that specific repository. Example code:
abstract class EntityRepository {
/**
* #param int $id
* #return AbstractEntity
*/
public function loadById($id) {
$record = $this->db->loadById($id);
$entity = $this->getEntity();
return $this->inflate($record, $entity);
}
/**
* #return AbstractEntity
*/
protected abstract function getEntity();
}
class PeopleRepository extends EntityRepository {
protected function getEntity() {
return new PeopleEntity();
}
}
abstract class AbstractEntity {
private $id;
/**
* #return int
*/
public function getId() {
return $this->id;
}
/**
* #param int $id;
*/
public function setId($id) {
$this->id = $id;
}
}
class PeopleEntity extends AbstractEntity {
private $name;
/**
* #return string
*/
public function getName() {
return $this->name;
}
/**
* #param string $name;
*/
public function setName($name) {
$this->name= $name;
}
}
When using an instance of PeopleRepository and fetching a model through loadById, PhpStorm is not able to resolve the returned model to a concrete type, but provides only code completion for the functions of AbstractEntity. Is there any simple way to make it work?
In https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata, I've only found ways to make it work for concrete classes and their functions. So, enumerating all repository classes and all their ways of creating an entity might work. But I'd love to see an abstract way of defining like "All instances of EntityRepository will return an entity of that type defined in getEntity() when loading an entity"
I doubt there's a blanket way of doing this. Even using PHPStorm meta you have to be explicit for each case. Perhaps the way of doing this is by doing something like adding a repository facade e.g.
class RepositoryFacade {
public static function __callStatic($method, $args) {
if ($args[0] == People::class) {
array_shift($args);
return new PeopleRepository()->{$method}(...$args);
}
}
}
Then you might be able to typehint this using:
override(RepositoryFacade::loadById(0), type(0));
Of course the facade is not really the best pattern to be using in general so I can see how this might not be ideal.

Does it hurt Demeter's law when model refers to another model?

I have a global container class:
final class Container
{
/**
* #return ForumThread
*/
public static function getForumThread()
{
if (self::$obj1 === null)
{
self::$obj1 = new ForumThread();
}
return self::$obj1;
}
/**
* #return ForumPosts
*/
public static function getForumPosts()
{
if (self::$obj2 === null)
{
self::$obj2 = new ForumPosts();
}
return self::$obj2;
}
}
my models:
class ForumThread
{
/**
* #return bool
*/
public function findBadLanguage ($inWhat)
{
return (bool)rand(0,1);
}
/**
* #return
*/
public function add ($threadName)
{
if (!$this->findBadLanguage ($threadName))
{
INSERT INTO
}
}
}
class ForumPost
{
/**
* #return
*/
public function post ($toThreadId, $comment)
{
// im talking about this:
Container::getForumThread()->findBadLanguage($comment);
}
}
I know findBadLanguage() should be in another class, but lets suppose thats okay. Lets focus on Container::get****() calls. Is it OK to turn to a global container and get objects from it? Doesnt it hury Demeter's law? (those object must be exists only once, and can be DI-ed)
EDIT: you can regard to the Container as a Factory

What OOP pattern do I use to provide defaults without violating the Liskov substitution principle

I have a class that controls an external API that I use in several projects (simplified example):
class PaymentModule
{
public function doPayment($customer_name, $currency, $language)
{
curl_setopt(POSTFIELDS, array($customer_name, $currence, $language));
curl_exec();
}
}
Now in a specific project I would like to "wrap" it and provide sensible defaults for a lot of parameters that I don't use here.
So I thought, I will just extend this class, have my IDE override all methods and then remove the parameters that I don't use, like this:
class MyPaymentModule extends PaymentModule
{
public function doPayment($customer_name)
{
$language = get_current_language();
parent::doPayment($customer_name, 'EUR', $language);
}
}
As I now learned (thanks to PHP strict standards), this violates the Liskov substitution principle, i.e. in OOP in general MyPaymentModule is expected to have the same interface as PaymentModule which in turn means MyPaymentModule::doPayment() is expected to have the same parameters as PaymentModule::doPayment().
I think it's not too uncommon that you want to create a class that provides sensible defaults to another one, so is there any common pattern to use here?
Of course I could go for two completely independent classes, but I would prefer a solution that still hints at the relationship between the two classes... after all they will always have the same methods, just one with less parameters.
Thats not possible with your current class design.
I would suggest to create a new class PaymentOptions which handles currency and language.
Passing the options to doPayment either with a new method setPaymentOptions or as an optional parameter for doPayment.
Something like this should work
class PaymentModule {
/**
* #var PaymentOptions
*/
private $paymentOptions = null;
function doPayment($customer_name, PaymentOptions $options = null) {
if ($options == null) {
$options = $this->paymentOptions;
}
if ($options == null) {
throw new Exception('...');
}
//...
}
/**
* #return PaymentOptions
*/
public function getPaymentOptions()
{
return $this->paymentOptions;
}
/**
* #param PaymentOptions $paymentOptions
*/
public function setPaymentOptions($paymentOptions)
{
$this->paymentOptions = $paymentOptions;
}
}
class MyPaymentModule extends PaymentModule {
function doPayment($customer_name, PaymentOptions $options = null)
{
//...
}
}
class PaymentOptions {
private $currency;
private $language;
/**
* #return mixed
*/
public function getCurrency()
{
return $this->currency;
}
/**
* #param mixed $currency
*/
public function setCurrency($currency)
{
$this->currency = $currency;
}
/**
* #return mixed
*/
public function getLanguage()
{
return $this->language;
}
/**
* #param mixed $language
*/
public function setLanguage($language)
{
$this->language = $language;
}
}

PHP classes and functions/ undefined variable

Why do I get undefined property Takeover::user2 on function takeover?
I'm not sure what I'm doing wrong. Can someone help?
I can call user2->addsaldo() on main file but I can't call it inside another function. Why?
Class user
class User {
/**
* #AttributeType int
*/
private $iduser;
/**
* #AttributeType float
*/
private $saldo=0;
/**
* #AssociationType Portefolio
* #AssociationKind Composition
*/
public $idportefolio;
public function __construct($iduser){
$this->iduser = $iduser;
}
/**
* #access public
*/
public function getid() {
// Not yet implemented
}
/**
* #access public
*/
public function addsaldo($saldo) {
$this->saldo = $saldo;
}
}
Class takeover
class Takeover {
/**
* #AttributeType int
*/
private $idTakeover;
/**
* #AssociationType root
* #AssociationMultiplicity 1
*/
public $Root;
public $IdeasTakerover=array();
public function __construct($idTakeover){
$this->idTakeover = $idTakeover;
}
/**
* #access public
*/
public function GetIdCompraRoot() {
// Not yet implemented
}
public function AddIdeasTakeover($idTakeover, $idideia) {
$this->idTakeover = $idTakeover;
$this->idideia = $idideia;
array_push($this->IdeasTakerover,$idideia);
}
/**
* #access public
*/
public function Takeover() {
$this->user2->addsaldo(200); //USER2 DOES EXIST
}
}
Creating users and calling them:
$takeover = new Takeover(1);
for ($i=0; $i<$conta; $i++ ){
$takeover->AddIdeasTakeover(1,$idsideias[$i]);
}
$takeover->Takeover();
if ($partial == "user") {
$booleanUser = TRUE;
$iduser=substr($buffer, 4);
${'user'.$iduser} = new User($iduser);
}
The problem is not in this class. The problem is that $GLOBALS['user2'] is not defined when referenced here: $GLOBALS['user'.$this->IdeiasTakeover[$i]]. You then call addsaldo() on an undefined array element in $GLOBALS.
On another note, it is impossible to write good code using $GLOBALS. Global variables are bad news. You should not be using/referencing global variables. The exception would be at a low level when referencing $_POST, $_GET, etc. Even still, all the good PHP frameworks wrap these in a request object.
Edit
Dependency Injection is a better way for one class to use another:
class X {
$yInstance;
public function __construct($yInstance)
{
$this->yInstance = $yInstance;
}
public function x()
{
//Call your 'y' method on an instance of Y
$this->yInstance->y();
}
}
class Y {
public function y()
{
echo 'Y::y() called!';
}
}
Call X::x()
$y = new Y();
$x = new X($y);
$x->x();
Output:
Y::y() called!
The easiest way to do this is to use a dependency injection container. Here is my favorite one for PHP: http://symfony.com/doc/current/components/dependency_injection/introduction.html
Also, check out Martin Fowlers classic article about IOC:
http://martinfowler.com/articles/injection.html
Good luck!

Categories