PhpStorm metadata file for repository classes - php

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.

Related

Getting the child-type of an object from a method inherited from the father

my problem is getting the right type of object from a method, which is returning a "mixed" type due to inhreitance.
I've got a generic list class
class List
{
/**
* #var Item[]
*/
protected $items;
public function __construct()
{
$this->items = array();
}
/**
* #return Item[]
*/
public function getAll()
{
return $this->items;
}
/**
* #return Item
*/
public function getOne($index)
{
if (isset($this->items[$index])) {
return $this->items[$index];
}
return null;
}
}
containing element of type Item, which is a generic class either
class Item
{
/**
* #var string
*/
protected $name;
public function __construct($name)
{
$this->name = $name;
}
}
Such generic classes are extended by N different lists. Just an example
class UserList extends List
{
/* user-specific implementation */
}
class User extends Item
{
/* user-specific implementation */
}
In the client code
$user_list = new UserList();
foreach ($user_list->getAll() as $user) {
echo $user->getEmailAddr();
}
Inside the foreach I don't have code completion, because my getAll method (inherited from the father) is returning Item[], or mixed[], not a User[]. Same problem with getOne method.
I wouldn't like to have to override such methods.
Is there a more clever and elegant solution?
Thank you
I don't think there's any way for the IDE to infer the type automatically. Use a phpdoc type annotation:
foreach ($user_list->getAll() as $user) {
/** #var User $user */
echo $user->getEmailAddr();
}
See the related question PHPDoc type hinting for array of objects?

Most efficient way of accessing a private variable of another class?

There are many methods around that allow you to access private variables of another class, however what is the most efficient way?
For example:
I have this class with some details in:
class something{
private $details =
['password' => 'stackoverflow',];
}
In another class I need to access them, example (although this obviously wouldn't work since the variable isn't in scope to this class):
class accessSomething{
public function getSomething(){
print($something->details['password']);
}
}
Would a function like this be good enough within the class "something" to be used by the access class?:
public function getSomething($x, $y){
print $this->$x['$y'];
}
you should be using proper getters/setters in your classes to allow access to otherwise restricted data.
for example
A class
class AClass {
private $member_1;
private $member_2;
/**
* #return mixed
*/
public function getMember1() {
return $this->member_1;
}
/**
* #param mixed $member_1
*/
public function setMember1( $member_1 ) {
$this->member_1 = $member_1;
}
/**
* #return mixed
*/
public function getMember2() {
return $this->member_2;
}
/**
* #param mixed $member_2
*/
public function setMember2( $member_2 ) {
$this->member_2 = $member_2;
}
}
which is then called as follows:
$newClass = new AClass();
echo $newClass->getMember1();
echo $newClass->getMember2();

Dynamically generate translatable columns using metadata

I would like to make a Translatable behavior for my entities using metadata.
I have a class Article
class Article implements TranslatableInterface {
/**
* #HeidanTranslatable
*/
private $title;
/**
* #HeidanLocale
*/
private $locale;
public function getTitle() {
return $this->title;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getLocale() {
return $this->locale;
}
public function setLocale($locale) {
$this->locale = $locale;
return $this;
}
I would like to have kind of Gedmo Doctrine Extension behavior which will create in database columns depending on property and allowed locales.
For example, with the entity article, I would like that two columns are created : title_fr, title_en.
I'd like this stuff is bridged to Doctrine behavior and I made a loadClassMetadataListener
class LoadClassMetadataListener {
/**
* #param LoadClassMetadataEventArgs $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$metadata = $eventArgs->getClassMetadata();
$metadata
->mapField(array('fieldName' => 'title_fr', 'type' => 'text'))
;
}
When I run a doctrine:schema:update --force I have the following error :
[ReflectionException]
Property Heidan\CoreBundle\Entity\Article::$title_fr does not exist
So I guess they said that the property title_fr does not exist, and that's right.
I do not want to set manually properties (private $title_fr, private $title_en, private $content_fr, private $content_en) for all my entities.
Is there any way to achieve this behavior so far ?
Thanks a lot for your help.

Proper phpdoc comment for iteratable object?

I'm having a bit of a problem trying to get a correct autocompletion for the following code example. I'm using PHPStorm 7 on a Win7 machine.
First just a simple class.
/**
* Class myObject
*/
class myObject
{
/**
* some method
*/
public function myMethod()
{
// do something
}
}
This one is the collection class which can contain multiple instances of the prior class and implements the IteratorAggregate interface.
/**
* Class myCollection
*/
class myCollection implements IteratorAggregate
{
/**
* #var myObject[]
*/
protected $_objects = array();
/**
* #param myObject $object
* #return myCollection
*/
public function add(myObject $object)
{
$this->_objects[] = $object;
return $this;
}
/**
* #return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->_objects);
}
}
And here is the code example.
$collection = new myCollection;
$collection->add(new myObject);
$collection->add(new myObject);
foreach ($collection as $object) {
$object->myMethod(); // gets no autocompletion
}
As you may have guessed (and read in the example) the myMethod() call gets not autocompleted and is beeing listed in the code analysis. The only way i found is adding a comment block for $object, which i find, to be honest, extremely annoying.
/** #var $object myObject */
foreach ($collection as $object) {
$object->myMethod(); // gets autocompletion now, but sucks
}
So, any ideas or fundamented knowledge on how to solve this?
/**
* #return ArrayIterator|myObject[]
*/
public function getIterator()
{
return new ArrayIterator($this->_objects);
}
For extended classes (the base class is above):
/**
* #method myObject[] getIterator()
*/
class ExtendedClass extends BaseCollection
{
}
or
/**
* #method iterable<myObject> getIterator()
*/
class ExtendedClass extends BaseCollection
{
}
I think this will be best way to handle such case. at least it works with PHPStorm
Your
/** #var $object myObject */
block is indeed the correct way to accomplish this. The syntax you are expecting to do the work,
/**
* #var myObject[]
*/
is not standard phpdoc notation, although it is in informal use and has some effort ongoing to standardize. Until such standardization does happen, IDEs recognizing it will probably be hit-or-miss. IDE coverage of your $object local var block is also hit-or-miss, actually.
In your myCollection class, override current() as follows:
/** #return myObject */
public function current() {
return parent::current();
}
Possible workaround (also ugly) is to create static "constructor", that will return myObject. At least it works in eclipse. If you want to see collection methods too, then just add myCollection to return as "#return myObject[]|myCollection"
class myCollection implements \IteratorAggregate
{
/**
* #return myObject[]
*/
public function create()
{
return new static();
}
}

How to implement Repository Pattern in Codeigniter?

When I programmed in ASP.NET MVC, there was a neat pattern called Repository. I want to implment it in Codeigniter but I do not know how. Here is what I actually want:
$mock_repository = new MockRepository();
$mock_repository->add(new Item(‘title1′, ‘description1′, 1));
$mock_repository->add(new Item(‘title2′, ‘description2′, 2));
$mock_repository->add(new Item(‘title3′, ‘description3′, 1));
$controller = new Item_controller($mock_repository);
$items = $controller->get_items_by_user_id(1);
$this->_assert_equals(count($items), 2);
I am using TOAST for Unit Testing. So how do I instantiate a controller within a test? The test is of course, another controller itself.
From what I know, to create a Generic Repository Pattern like in C#, you need 2 things PHP 5.6 dosen't have:
Real Method Overloading.
Generic Interface or Generic Abstract Class in PHP.
Click here for more on Generic Repository Pattern in C#.
However you can still create pseudo method overloading in PHP with the help of magic method __call, and we can type little more code for the generic part of the pattern.
Note: Before creating this pattern in Codeigniter 3.0 you will need to create a table in the database, and create auto loader for folder application/libraries.
First we need to create Interface in application/libraries folder:
<?php
interface IRepository
{
public function getById($id);
public function select($columns);
public function delete($id);
}
Seconde we need to create Abstract Class implementing the Interface and extending the CI_Model to be able to use the Database librarie:
<?php
abstract class Base_repository extends CI_Model implements IRepository
{
/**
* This must be valid table name in the Database.
*
* #var string $table Name of the table.
*/
protected $table;
public function __construct()
{
parent::__construct();
}
/**
* Pseudo method overloading.
* It's called when method is not declared in the abstract class.
*
* #param string $name Name of the method
* #param mixed $arguments Arguments of the method
*/
public function __call($name, $arguments)
{
switch ($name)
{
case 'save':
if ($arguments[0]->id > 0)
{
$this->update($arguments[0]);
}
else
{
$this->insert($arguments[0]);
}
break;
}
}
/**
* Get row with id.
*
* #param integer $id
* #return mixed
*/
public function getById($id)
{
return $this->db->get_where($this->table, ['id' => $id])->row_array();
}
/**
* Select columns.
*
* #param array $columns
* #return mixed
*/
public function select($columns = ['*'])
{
$this->db->select($columns);
return $this->db->get($this->table)->result();
}
/**
* Insert data.
*
* #param object $item
* #return void
*/
private function insert($item)
{
unset($item->id);
$this->db->insert($this->table, $item);
}
/**
* Update data.
*
* #param object $item
* #return void
*/
private function update($item)
{
$this->db->where('id =', $item->id);
unset($item->id);
$this->db->update($this->table, $item);
}
/**
* Delete data.
*
* #param integer $id
* #return void
*/
public function delete($id)
{
$this->db->delete($this->table, ['id' => $id]);
}
}
Third test the repository. Make a new model in application/model, and extend Base_repository, set table name and overload save method, create entity for this model:
<?php
/**
* The entity class.
*/
class Test
{
public $id;
public $info;
}
class Test_model extends Base_repository
{
/**
* Tell what table we are using.
*/
public function __construct()
{
parent::__construct();
$this->table = 'test';
}
/**
* "Overload" save method and call it from the parent.
*
* #param test $item Make use of the Dependency Injection.
* #return void
*/
public function save(Test $item)
{
parent::save($item);
}
}
Try it in the controller. Load the model and try to get, insert, ect...
To create real models is the same procedure. If you need to add more methods that will be the same for every model add them in the abstract class if you need to create methods only for specific model add it only in this model.
I don't recommend Codeigniter freamwork. Here are some patterns for PHP CLICK!
You would have to completely hijack the system files to load a controller from another controller. It can't be done, methinks.
It can be done with HMVC.
$result = Modules::run('controller/get_items_by_user_id', $params);
$this->_assert_equals($result, $expected);

Categories