Is there a way to properly typehint a \SimpleXMLElement? So that I do not have to typehint all what it accesses also a a \SimpleXMLElement?
If I want to have typehinting all the way, I currently have to do it this way:
/**
* #var \SimpleXMLElement $values (this is not! an array, yet it is traversable)
*/
$values = $response->params->param->value->array->data->value;
foreach ($values as $row) {
$row = $row->array->data->value;
/**
* #var \SimpleXMLElement $row
*/
$entry = $row[0];
/**
* #var \SimpleXMLElement $entry
*/
$xmlString = $entry->asXML();
}
This seems utterly verbose and redundant. Is there a way to typehint a SimpleXMLElement so that all what it returns will also be coreclty typehinted?
If you Ctrl-click through to the "definition" of SimpleXMLElement in PHPStorm, you will see that it has a stub class definition which it uses for auto-completion and code analysis.
In older versions of PHPStorm, the overloaded -> operator was represented in that stub as follows (taken from PHPStorm 9.0):
/**
* Provides access to element's children
* #param $name child name
* #return SimpleXMLElement[]
*/
function __get($name) {}
Note that the return type here is SimpleXMLElement[], i.e. "an array of SimpleXMLElement objects". This allows it to correctly auto-complete if you write something like $node->childName[0]->grandChild[0]->asXML(), but not if you use the short-hand form of $node->childName->grandChild->asXML()
This could be classed as a bug in the IDE, and was filed in their public tracker as WI-15760, which is now fixed.
As of PHPStorm 2018.1.2, the stub instead declares the return type of __get() as SimpleXMLElement and also declares implements ArrayAccess with offsetGet() also returning SimpleXMLElement.
/**
* Provides access to element's children
* #access private Method not callable directly, stub exists for typehint only
* #param string $name child name
* #return SimpleXMLElement
*/
private function __get($name) {}
/**
* Class provides access to children by position, and attributes by name
* #access private Method not callable directly, stub exists for typehint only
* #param string|int $offset
* #return SimpleXMLElement Either a named attribute or an element from a list of children
*/
private function offsetGet ($offset) {}
This should correctly auto-complete for both explicit [0] and short-hand cases.
(The #access private is a hack to stop the method showing up in auto-complete results, since you can't actually call $node->__get() or $node->offsetGet() in real PHP code.)
Related
I have been working on transitioning from procedural -> oop for a few months now. I decided to avoid jumping right into a framework. I didn't want to rely on the behind the scenes magic, so during my spare time i decided to convert an old project and build a small framework to run it. For tutorials i have mainly turned to googling laravel/laracast tutorials since it is the easiest to find information on. I would say i am at a point where my 'framework' is a smaller simplified ( by this i mean not as feature packed/complex ) version of laravel. I haven't wrapped my head around all of the things symfony/laravel offer but things are starting to click as i progress. At the moment i believe i will be working with laravel once i finish converting my old project.
Ok now. One of the things i continuously have doubts about is dependency injection. All/most injectors that i have found inject via construct using type hinting. I started out with a static registry and slowly iterated until i had the following class
<?php
/**
*------------------------------------------------------------------------------
*
* Framework Container - Class Builder + Injector + Container
*
* The Container Is Responsible For Dependency Injection Via Class Constructors
* Through Abstract 'Alias' Keys Defined Within The Class. It Is Also Used To
* Hold Various Data Including Closures, Or Cached Object Instances.
*
*/
namespace eSportspCMS;
use \eSportspCMS\Helpers\Support\Base;
use \eSportspCMS\Contracts\Container as ContainerContract;
class Container extends Base implements ContainerContract {
/**
* Alias Container
* Contains Alias Keys Defining Classes To Instantiate See $this->make()
*/
private $aliases = [];
/**
* Binding Container
* Contains Custom Closure Bindings See $this->make()
*/
private $bindings = [];
/**
* Cache Container
* Contains Previously Instantiated Classes See $this->make()
*/
private $cache = [];
/**
* Closure Container
* Contains Custom Closures To Avoid Hardcoding Within Class
*/
private $closures = [];
/**
* Class Dependency Key
* Public Var Within Classes Defining The Dependency List
*/
private $varkey = 'dependencies';
/**
* Define Class Dependency Key
*
* #param string $varkey Class Dependency Key
*/
public function setvarkey($varkey) {
$this->varkey = $varkey;
}
/**
* Set Data Within Containers
*
* #param string $key Key To Use When Setting Container Var
* #param mixed $value Value To Use When Setting Container Var
*/
public function alias($key, $value) { $this->aliases[$key] = $value; }
public function bind ($key, $value) { $this->bindings[$key] = $value; }
public function cache($key, $value) { $this->cache[$key] = $value; }
/**
* Add New Closure Within Container
*
* #param string $key Closure Key
* #param callable $value Callable Function
*/
public function closure($key, callable $value) {
if (method_exists($this, $key)) {
throw new \Exception($key . ' Already Exists As A Method. Which
Means This Closure Would Not Be Accessible If Set!');
}
$this->closures[$key] = $value;
}
/**
* Access Closure If Method Being Called Does Not Exist
*
* #param string $key Closure Key
* #param array $params Params To Pass To Closure
* #return mixed Closure Response If Exists
*/
public function __call($key, $params = []) {
if (isset($this->closures[$key]) && is_callable($this->closures[$key])) {
return call_user_func_array($this->closures[$key], $params);
}
return false;
}
/**
* Create New Class Instance
*
* Forces $this->make() To Instantiate A New Class Instead Of Returning
* A Cached Instance.
*
* #see $this->make() Comments
*/
public function makeNew($class, $params = []) {
return $this->make($class, $params, false, true);
}
/**
* Pull Class From Cache Based On Key, Call Binding Or Instantiate Class
* Resolve Dependencies And Pass Via Construct.
*
* #param string $key Alias Key | Binding Key | Class To Make
* #param array $params Additional Params Passed Via Constructor
* #param bool $cache Determines If Class Can Be Cached
* #param bool $new Forces New Instance Of Object
* #return object Instantiated Or Cached Object
*/
public function make($key, $params = [], $cache = true, $new = false) {
/**
* Params Indicate Cached Instance Can Be Used
*/
if (!$new && isset($this->cache[$key])) {
return $this->cache[$key];
}
/**
* If Binding Is Defined And Key Matches Return It Instead Of Building
* The Class Directly. Replace Params With App
*/
if (isset($this->bindings[$key]) && is_callable($this->bindings[$key])) {
$instance = call_user_func_array($this->bindings[$key], $this->make('app'));
$cache ? $this->cache($key, $instance) : '';
return $instance;
}
/**
* Cache And Binding Statement Failed! Attempt To Build Class.
*
* If Class Exists Instantiate, Resolve/Pass Dependencies,
* Cache ( If Allowed ), And Return.
*
* Else Throw Exception!
*/
$classname = isset($this->aliases[$key]) ? $this->aliases[$key] : $key;
if (class_exists($classname)) {
$instance = new $classname($this->resolveDependencies($classname, $params));
$cache ? $this->cache($key, $instance) : '';
return $instance;
}
// All Statements Failed! Class Couldn't Be Created
throw new \Exception('Container Could Not Create Class: ' . $classname);
}
/**
* Resolve/Build Class Dependencies
*
* Dependencies Cascade To Simplify/Unify Dependency Setting Within Grouped
* Classes. ( Classes Like Controllers Which Would Extend Base Controller )
*
* #param string $classname Class Being Instantiated
* #param array $params Additional Params Being Passed
* #return array Assoc Array With Class Dependencies
*/
private function resolveDependencies($classname, $params = []) {
// Define Class Tree
$classes = array_reverse((array) class_parents($classname));
$classes[] = $classname;
// Retrieve Class Dependencies From Tree ( Alias Keys ) & Build
$dependencies = $this->dependencies($classes);
foreach ((array) $dependencies as $dependency) {
$dependencies[$dependency] = $this->make($dependency);
}
// Return Merged Dependencies
return array_merge($dependencies, $params);
}
/**
* Retrieve Class Dependency List ( Alias Keys )
*
* #param array $classes Array Containing Classes
* #return array Class Dependencies ( Alias Keys )
*/
private function dependencies($classes = []) {
if (!$classes) { return; }
$aliases = [];
foreach ((array) $classes as $c) {
$vars = get_class_vars($c);
if (isset($vars[$this->varkey])) {
$aliases = array_merge($aliases, $vars[$this->varkey]);
}
}
return array_unique($aliases);
}
}
How I Use It
The framework application class will extend the container. During application bootstrap the alias keys for dependencies will be loaded ( i use the same configuration setup of laravel - loading config from returned arrays within config directory ) When a class is instantiated using the container it will search for the dependency list 'varkey', it will iterate and merge parent class dependencies ( for grouped dependencies like models - models extend base model ) it will then build the dependency tree, instantiate the class and cache if allowed. The dependencies are passed to the class via construct as an assoc array ['alias' => object]. Classes extending the '\helpers\support\base' class have the dependencies set for them. The base construct class iterates through the array setting the dependency within the class using the alias key as the class key.
Concerns
When looking at other injectors i see typehinting being used and i am not sure if that is due to a flaw with this type of dependency injection that i am not seeing yet. The way i see it the alias keys are the 'interfaces' on top of that the dependencies that are used implement contracts so when i work on changing the class the contract still defines the class requirements.
Questions
Is there a reason to:
* stray away from using this injector? Why? ( I am not looking for "Dont reinvent the wheel" answers i am trying to further my knowledge and understand what i am doing )
* implement service providers "laravel tutorials" when using this? Currently i manipulate dependencies within construct ( if i need specific data from a dependency i use getters/setters to retrieve the data and set within the class )
Do i need to create a contract/interface for EVERY class that i create including controllers, or are interfaces used on classes that are dependencies only? ( I have found lots of mixed feedback so i am torn atm thought i would throw this in )
Class Example
The following class is my 404 error controller. It is one of the dependencies used in other controllers since i have pages dependent on db data ( users ) i display 404 errors if the user does not exist.
<?php
/**
*------------------------------------------------------------------------------
*
* Error Controller
*
*/
namespace Application\Controllers;
use \eSportspCMS\Helpers\Support\Base;
class Error extends Base {
public $dependencies = ['http', 'log', 'metadata', 'view'];
/**
* 404 Error Page Not Found
*/
public function display404($msg = '', $params = []) {
// If Msg Present Log Error
!$msg ?: $this->log->error($msg, $params);
// Define 404 HTTP Header
$this->http->set('header', 'HTTP/1.1 404 Not Found');
// Set 404 Sitetitle
$this->metadata->sitetitle('error', 'display404');
// Return View File + View Data
return $this->view->display('www/custompages', $this->custompage->info(1));
}
}
Thank you in advance for all feedback.
In a legacy project, I was confused when I tried finding the usage of a method in phpstorm and without success.
/**
* #param SomeEntity[] $someEntity
*
* #return bool
*/
protected function warmupSomeEntity(array $brandUniverses)
{
// I want to find this method's usage
}
Debugging a bit further, I found that the method is called on the fly in a rather abstract way via dynamic class method invocation:
/**
* #param string $warmer
* #param array $objects
*
* #throws RuntimeException
*/
public function warmupType($warmer, array $objects)
{
$method = "warmup$warmer";
if (method_exists($this, $
$this->{$method}($objects);
} else {
throw new RuntimeException("There is no warmer '$warmer'");
}
}
Is there a phpdoc syntax where I can document that the warmUpType method will call warmupSomeEntity, or warmupSomeOtherEntity so that I can find its usage again if I want to jump to the calling code block again?
The #uses keyword was what I was looking for as it:
Display a link to the documentation for an element, and create a backlink in the other element's documentation to this
It is supported by PhpStorm and the caller is found again.
/**
* #param string $warmer
* #param array $objects
* #uses warmupSomeEntity
* #uses warmupSomeOtherEntity
* #throws RuntimeException
*/
public function warmupType($warmer, array $objects)
{
...
}
In my php application I have been comparing objects with the usual equality comparison operator, e.g.:
if ($objectA == $objectB) { ... }
Recently I implemented proxies (for objects which are expensive to load) however this means the equality operator no longer works. Is there a simple way around this? One that doesn't rely on reflection?
For the moment, I have resorted to testing the unique identifier of each object, e.g.
if ($objectA->getId() == $objectB->getId) { ... }
But this has two problems: 1) I need to refactor all existing code, and 2) in the future I may need to compare objects which are value objects (not entities).
I'm not hopeful of an easy solution since I think it would require a new magic method...
Here's my AbstractProxy class. Any help appreciated...
abstract class KOOP_Base_AbstractProxy
implements KOOP_Base_iDomain
{
use KOOP_Trait_Helper_Helper;
/**
* #var integer Object identifier
*/
protected $_id = null;
/**
* #var KOOP_Base_AbstractMapper
*/
protected $_mapper = null;
/**
* #var KOOP_Base_AbstractDomain Actual object
*/
protected $_subject = null;
/**
* Store object id for lazy loading
*
* #param integer $id Object identifier
* #param string $mapper Mapper by which to retrieve object
*/
public function __construct($id, $mapper)
{
$this->_id = $id;
$this->_mapper = $mapper;
}
/**
* Get subject
*
* #return KOOP_Base_AbstractDomain
*/
protected function getSubject()
{
if (!$this->_subject) {
$this->_subject = $this->getMapper($this->_mapper)->find($this->_id);
}
return $this->_subject;
}
/**
* Get property
*
* #param string $property
* #return mixed
*/
public function __get($property)
{
return $this->getSubject()->$property;
}
/**
* Set property
*
* #param string $property
* #param mixed $value
* #return void
*/
public function __set($property, $value)
{
$this->getSubject()->$property = $value;
}
/**
* Is property set?
*
* #param $property
* #return boolean
*/
public function __isset($property)
{
return isset($this->getSubject()->$property);
}
/**
* Unset property
*
* #param string $property
* #return mixed
*/
public function __unset($property)
{
unset($this->getSubject()->$property);
}
/**
* Call method
*
* #param string $method Method to call
* #param array $params Parameters to pass
* #return mixed
*/
public function __call($method, array $params)
{
return call_user_func_array(array($this->getSubject(), $method), $params);
}
/**
* Get id
*
* Saves having to retrieve the entire object when only the ID is required.
*/
public function getId()
{
return $this->_id;
}
}
Proxies do break object equality, and there's no utterly clean way to fix this. In a fully object oriented language you would handle this by operator overloading (which I don't recommend) or implementing a custom .equals() function (as in Java). Sadly, PHP simply does not support object orientation at this level, so you will have some decisions to make.
1) I would prefer to have your proxy class provide an equals() function which takes as input a reference to the object you want to test against and compares it to the proxied object - which shouldn't be much more 'expensive' than it was to not use a proxy at all. Example in pseudo-PHP code (my apologies if my reference syntax is off, it's been a while):
public function equals (&$toCompare)
{
if ($_subject == $toCompare)
{
return true;
}
else
{
return false;
}
}
The downside is simple: you have to refactor your code that involves this proxied object, and you have to remember that "==" does not work on this proxied object type while you are working. If you don't deal with these objects much, or if you deal with them all the time, this is fine. If you deal with them regularly but intermittently, or if others must work with them on occasion, then this will cause bugs when you/they forget about this equality problem.
2) Use an Operator Overloading extension to the language. I haven't done this, I don't know if it works, and it might be a nightmare. I include it for theoretical completeness.
Personally, I think I'd just hack it with the pseudo-Java approach call it a day, as I think it would actually work and require nothing more than using the function correctly (and remembering to use it in the first place).
I am just learning about OOP in PHP from a book and the section on Iterators and Iteration has me stumped.
For what I understand, I think in order to loop through an object's attributes, you need to implement the built-in class Iterator. Then implement the IteratorAggregate interface and create a getIterator method within. However, I am currently confused on the role each of these elements play and in what format they need to be written. In other words, I am just looking for a simply (plain-English) explanation of these concepts and a simple example. Any help would be greatly appreciated!!!!!
And thank you for your time and help in advance!!!!
The thing you want to loop over must implement Traversable. However, you can't implement Traversable directly; you have to implement one of its subtypes. How you'll do things depends on how the object will be used.
If you wanted, you could just implement Iterator. That works well enough if your type is intended to be a forward-only list of stuff already. If you decide to build an iterator, you'll have 5 methods to implement:
current, which retrieves the value at the current location;
key, which retrieves the current location's key;
next, which advances to the next location;
rewind, which resets the current location to the first; and
valid, which returns false if iteration has fallen off the end of the list of stuff being iterated over.
PHP calls those methods over the course of iteration. Specifically, let's say you have code like this:
foreach ($it as $key => $value) {
doStuffWith($key, $value);
}
This is rather equivalent to the following:
for ($it->rewind(); $it->valid(); $it->next()) {
$value = $it->current();
$key = $it->key(); // only if the foreach has a `$key =>`
doStuffWith($key, $value);
}
Basically you just need to build a type that implements Iterator and responds properly to those methods being invoked in roughly that order. Ideally it should also be possible for something to happen in between...but that's usually not an issue unless you're passing references around.
If you don't need custom iteration, you could instead just implement IteratorAggregate, and return an existing iterator type if it's able to do what you need. (For example, if you want to allow looping over an internal array, there's already an ArrayIterator made for the job. No need to roll your own.) For IteratorAggregate, you only have to implement getIterator, which returns something that's traversable. This is a better solution if you have something that can already be traversed by one of the built-in SPL iterators, or can easily be reduced to an array or something.
That same loop above, if called on an IteratorAggregate, would equate to something like
foreach ($it->getIterator() as $key => $value) {
doStuffWith($key, $value);
}
getIterator() has to return either an implementation of Iterator or some primitive traversable thingie, like an array.
As for Java-style iterators, you might build an Iterator subtype that can remember its place and loop over your collection, and then implement IteratorAggregate on your collection to return an instance of the iterator type.
For basic functionality you only really need to implement Iterator and add the relevant functionality for the rewind, valid, key, current, and next methods. See below for an example:
/**
* Awesome
*
* Do awesome stuff
*/
final class Awesome implements Iterator
{
/**
* An array of data
*
* #access private
* #var array $_data
*/
private $_data;
/**
* Store the initial data
*
* #access public
* #param array $data
*/
public function __construct(array $data = array())
{
$this->_data = $data;
}
/**
* Rewind the iterator
*
* #access public
*/
public function rewind()
{
reset($this->_data);
}
/**
* Validate the existence of the next element
*
* #access public
* #return boolean
*/
public function valid()
{
return isset($this->_data[$this->key()]);
}
/**
* Return the current key
*
* #access public
* #return integer
*/
public function key()
{
return key($this->_data);
}
/**
* Return the current value
*
* #access public
* #return mixed
*/
public function current()
{
return current($this->_data);
}
/**
* Increment the iteration index
*
* #access public
*/
public function next()
{
next($this->_data);
}
}
// Instantiate a new Awesome object
$awesome = new Awesome(array('Michael', ' ', 'Rushton', ' ', 'is', ' ', 'awesome', '!'));
// Iterate over the awesome object and output a universal truth
foreach ($awesome as $almost_awesome)
{
echo $almost_awesome;
}
If you wish to instead iterate over the object's properties simply change the __construct to:
/**
* Construct the object
*
* #access public
*/
public function __construct()
{
// Get the properties
$object_vars = get_object_vars($this);
// Unset the data reference
unset($object_vars['_data']);
// Set the data
$this->_data = $object_vars;
}
I have a method that accepts a callback as a parameter. I would like to provide a signature in the PHPDoc for the class method that outlines the parameters for the callback function to be passed to that method so that my IDE (PHPStorm) can produce valid type hints for functions that are passed to my method, or at least someone looking at the code can determine the signature of the callback they're intended to provide.
For example:
class Foo {
public $items = [];
/**
* #param Callable(
* #param ArrayObject $items The list of items that bar() will return
* ) $baz A callback to receive the items
**/
public function bar(Callable $baz) {
$items = new ArrayObject($this->items);
$baz($items);
}
}
The method bar has one parameter, $baz, which is a callback function. Any function passed as an parameter to bar() must accept an ArrayObject as its only parameter.
Ideally, it should be possible to include multiple parameters for the Callable, just like for any other method.
When I write the following code:
$foo = new Foo();
$foo->bar(function(
...I should then receive a parameter list that correctly hints the type (ArrayObject) of the accepted parameter for this function call.
Is such a thing possible? Does PHPStorm or another IDE support it? Is there a recommended/standard way of documenting this even if there is no IDE support?
PHP 7+:
Using an interface for the callable combined with anonymous classes will do the trick. It's not very handy and leads to bit too complex code for the class-consumer, but currently it's the best solution in terms of static code-analysis.
/**
* Interface MyCallableInterface
*/
interface MyCallableInterface{
/**
* #param Bar $bar
*
* #return Bar
*/
public function __invoke(Bar $bar): Bar;
}
/**
* Class Bar
*/
class Bar{
/**
* #var mixed
*/
public $data = null;
}
/**
* Class Foo
*/
class Foo{
/**
* #var Bar
*/
private $bar = null;
/**
* #param MyCallableInterface $fn
*
* #return Foo
*/
public function fooBar(MyCallableInterface $fn): Foo{
$this->bar = $fn(new Bar);
return $this;
}
}
/**
* Usage
*/
(new Foo)->fooBar(new class implements MyCallableInterface{
public function __invoke(Bar $bar): Bar{
$bar->data = [1, 2, 3];
return $bar;
}
});
If you're using PhpStorm it will even auto-generate the __invoke-Method's signature & body within the anonymous class.
I overcame this problem by defining a static function within the class using the callable. That function has its own doc-block and I just refer to it in the method that's requiring my callable using PHPDoc's #see tag.
class Foo
{
/**
* Description of the "bar" callable. Used by {#see baz()}.
*
* #param int $index A 1-based integer.
* #param string $name A non-empty string.
* #return bool
* #see baz()
* #throws \Exception This is a prototype; not meant to be called directly.
*/
public static barCallable($index, $name)
{
throw new \Exception("barCallable prototype called");
}
/**
* Description of the baz() method, using a {#see barCallable()}.
*
* #param callable $bar A non-null {#see barCallable()}.
* #see barCallable()
*/
public function baz(callable $bar)
{
// ...
call_user_func($bar, 1, true);
// ...
}
}
This works well in PhpStorm 10. the Quick Documentation allows to navigate from the method documentation to the prototype documentation easily.
I make my prototype function throw an exception to make it clear it's not meant to be called. I could use a protected or private scope, but then PHPDoc would not always pick the doc-block for documentation generation.
Unfortunately PhpStorm can't track usage of callbacks. It doesn't provide Parameter Info either when using the method requiring a callback, but the callback is at least formally documented.
This approach also has the added benefit of validating a callback definition from the reflection of the prototype at run-time.
It is not possible in PhpStorm by now. I can't even think of other solution which do relatively the same by other means.