OOP - blank interface - php

Let's say that I have these two interfaces (PHP syntax)
interface Renderable_Item
{
}
interface Item_Renderer
{
function render(Renderable_Item $item);
}
I have a set of classes that are all same of a kind, but they just have nothing important (in Item_Renderer point of view) in common. All these classes implements Renderable_Item because they all should be renderable by Item_Renderer. So the Renderable_Item interface is just my way to group these classes.
Classes that implement Item_Renderer would have to first detect the concrete type, "downcast" it and then call methods of the concrete class anyway, so the interface has no meaning here, but in my opinion, it just "suits" there
Is there some better approach?
BTW, I am doing a PHP parser written in PHP. (I know there is some PHP parser in PERL and other, but just for fun .. ;))
First, I explode PHP code into structured chunks (classes that implement blank interface "Code_Chunk" (= Renderable_Item, in my example)). This is for example how class representing Assignment expression looks like -
class Assignment implements Expression
{
/**
* #var Variable
*/
private $variable;
/**
* #var Expression
*/
private $variableValue;
/**
* #return Expression
*/
public function getVariableValue()
{
return $this->variableValue;
}
/**
* #param Expression $variableValue
*/
public function setVariableValue($variableValue)
{
$this->variableValue = $variableValue;
}
/**
* #param Variable $variable
*/
public function setVariable(Variable $variable)
{
$this->variable = $variable;
}
/**
* #return Variable
*/
public function getVariable()
{
return $this->variable;
}
}
Interface Expression is by the way just another blank interface extending Code_Chunk, just to distinguish the code chunks that have some value (= expressions) from other.
However, there is nothing common between these classes that I can use except they are all some kind of "code chunk".
After this procedure, I want to rerender the whole parsed code tree, which is basically tons of structured instances of custom classes implementing Code_Chunk. Every Code_Chunk has its own rendering class (implementing Chunk_Renderer (= Item_Renderer)) accepting only that one certain chunk. For example, this is Assignment rendering class
class Assignment_Renderer implements Chunk_Renderer
{
public function render(Code_Chunk $chunk)
{
if(!($chunk instanceof Assignment)){
throw new Unexpected_Chunk_Exception($chunk);
}
/**
* #var $chunk Assignment
*/
$_ = '';
$_ .= $chunk->getVariable()->toString();
$_ .= '=';
$_ .= Chunk_Renderer_Factory::render($chunk->getVariableValue());
return $_;
}
}
Chunk_Renderer_Factory is a class holding all renderer classes as singletons and deciding, which renderer to use on which chunk.

Related

PHP Replacing static methods in Entities, best practices

<?php
class Entity {
/**
* #var array|stdClass|Collection|string
**/
private $mixed;
public function getMixedAsPhpArray(array $filter) {
return EntityHelper::toPhpArray($this->mixed, $filter);
}
}
Given the above class, how would you remove the static call to EntityHelper::toPhpArray assuming $mixed could by any of the types in the PHPDoc Block? (This is just a simplified example of an issue I'm facing where I have a function to take "dirty in and clean out") (I cannot add it to the Entity as many Entities need this function and cannot extend from an Abstrart Entity with this method as they already extend various others).
I'm using Symfony and thought of DI in the helper but replacing all new Entity with a call to the service container would be a bad (and slow) idea. Another idea would be to return the dirty output and use a service to clean and filter it but I also think that's a bad idea as it takes all the data from the Entity out into the Application then into the Service when I think it should happen in one go and remove mistakes (and maybe memory usage...).
The best solution will depend on your application, but one method you might use is to put your helper code in a trait:
<?php
trait ArrayCleaner {
public function toPhpArray($dataToFilter, $filterArray) {
// ... your code here
}
}
class Entity {
use ArrayCleaner;
/**
* #var array|stdClass|Collection|string
**/
private $mixed;
public function getMixedAsPhpArray(array $filter) {
return $this->toPhpArray($this->mixed, $filter);
}
}
http://php.net/manual/en/language.oop5.traits.php
Well, you could do this with a trait.
trait EntityHelperTrait
{
private function toPhpArray($value, $filter) {
// body of method
}
}
class Entity
{
use EntityHelperTrait;
/**
* #var array|stdClass|Collection|string
**/
private $mixed;
public function getMixedAsPhpArray(array $filter) {
return $this->toPhpArray($this->mixed, $filter);
}
}
But based on your (admittedly simplified) example, you're mixing responsibilities. The job of type conversion, which is what this essentially is, should belong somewhere else, not baked into the Entity class itself.
I think it's totally fine to let the Entity return the "dirty" output for another component to filter/clean/whatever.

How to indicate a parameter that "include trait" in PHPDoc

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.

How do I stub this function in PHP

I have the following class I want to test:
<?php
namespace Freya\Component\PageBuilder\FieldHandler;
use Freya\Component\PageBuilder\FieldHandler\IFieldHandler;
/**
* Used to get a set of non empty, false or null fields.
*
* The core purpose is to use this class to determine that A) Advanced Custom Fields is installed
* and B) that we get back a set of fields for a child page.
*
* The cavete here is that this class requires you to have child pages that then have Custom Fields that
* you want to display on each of those pages. getting other page information such as content, title, featured image
* and other meta boxes is left ot the end developer.
*
* #see Freya\Component\PageBuilder\FieldHandler\IFieldHandler
*/
class FieldHandler implements IFieldHandler {
/**
* {#inheritdoc}
*
* #return bool
*/
public function checkForAFC() {
return function_exists("register_field_group");
}
/**
* {#inheritdoc}
*
* #param $childPageID - the id of the child page who may or may not have custom fields.
* #return mixed - Null or Array
*/
public function getFields($childPageId) {
$fieldsForThisPage = get_fields($childPageId);
if (is_array($fieldsForThisPage)) {
foreach ($fieldsForThisPage as $key => $value) {
if ($value === "" || $value === false || $value === null) {
unset($fieldsForThisPage[$key]);
}
}
return $fieldsForThisPage;
}
return null;
}
}
I can test all of this but one thing I want to do is stub the get_fields() function to say you will return this type of array to then be used how ever the rest of the function uses it, which in this case is looping through it.
The part I don't know how to do in php is stub a function that's being called and then say you will return x.
So how do I stub get_fields?
You cen define such function in global namespace. Take a look at the following example:
namespace {
function getFields($pageId) {
return array($pageId);
}
}
namespace MyNamespace {
class MyClass
{
public function foo(){
var_dump(getFields(5));
}
}
$obj = new MyClass();
$obj->foo();
}
And here is the output:
array(1) {
[0]=>
int(5)
}
The only issue is that this function will exist till end of script. To solve this problem you can use the tearDown method together with runkit library:
http://php.net/manual/en/function.runkit-function-remove.php
that allows you to remove user defined functions.
Unfortunatelly this library does not exist on Windows so there you won't be able to remove the definition and may consider running tests in isolation.
Edit:
You can also consider using this library (it also depends on runkit):
https://github.com/tcz/phpunit-mockfunction
You can use a trick here with the unqualified function name get_fields(). Since you don't use the fully qualified function name \get_fields() PHP will first try to find the function in the current namespace and then fall back to the global function name.
For the definition of qualified and unqualified, see: http://php.net/manual/en/language.namespaces.basics.php (it's similar to absolute and relative filenames)
So what you have to do is define the function in the namespace of the class, together with your test case, like this:
namespace Freya\Component\PageBuilder\FieldHandler;
function get_fields()
{
return ['X'];
}
class FieldHandlerTest extends \PHPUnit_Test_Case
{
...
}
Additional notes:
You can do the same with core functions, as described here: http://www.schmengler-se.de/en/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/
This trick only works with functions, not classes. Classes in the global namespace always must be referenced with leading backslash.

Overriding functions incompatibility

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.

Passing an entire Class as a parameter within another Class

So far I feel like I've understood the concept and the advantages of OOP programming, and I've not really had any difficulties with understanding how to work with classes in PHP.
However, this has left me just a little confused. I think I may understand it, but I'm still uncertain.
I've been following a set of video tutorials (not sure on the rules on linking to outside resources, but I found them on youtube), and they're pretty self explanatory. Except, frustratingly, when the tutor decided to pass one class as a parameter within another class. At least I think that's what's happening;
Class Game
{
public function __construct()
{
echo 'Game Started.<br />';
}
public function createPlayer($name)
{
$this->player= New Player($this, $name);
}
}
Class Player
{
private $_name;
public function __construct(Game $g, $name)
{
$this->_name = $name;
echo "Player {$this->_name} was created.<br />";
}
}
Then I'm instantiating an object of the Game class and calling its method;
$game = new Game();
$game-> createPlayer('new player');
Rather frustratingly, the tutor doesn't really explain why he has done this, and hasn't displayed, as far as I can see, any calls in the code that would justify it.
Is the magic method constructor in Player passing in the Game class as a reference? Does this mean that the entire class is accessible within the Player class by reference? When referencing $this without pointing to any particular method or property, are you referencing the entire class?
If this is what is happening, then why would I want to do it? If I've created a Player inside my Game Class, then surely I can just access my Player Properties and Methods within the Game Class, right? Why would I want my Game Class inside my Player class as well? Could I then, for example, call createPlayer() within the Player class?
I apologise if my explanation has been at all confusing.
I guess my question boils down to; what is it that I'm passing as a parameter exactly, and why would I want to do it in every day OOP programming?
It's called type hinting and he's not passing the entire class as a parameter but ratter hinting the Class Player about the type of the first parameter
PHP 5 introduces type hinting. Functions are now able to force parameters to be objects (by specifying the name of the class in the function prototype), interfaces, arrays (since PHP 5.1) or callable (since PHP 5.4). However, if NULL is used as the default parameter value, it will be allowed as an argument for any later call.
(Extracted from php manual)
Does this mean that the entire class is accessible within the Player class by reference?
Not the entire class, but you can access the an instance of the class you are passing as a parameter
The method DOES expect to get the an object which is an instance of Game anything else, and it will error out.
You are passing an instance of Game just here: New Player($this, $name); The key word $this refers to the object instance you are in.
And last....I (and nobody else for that matter) has any idea why the author did it, as he is not using the Game instance after he passes it.
Why would u pass a Instance of a class?
Imagine a Class that accepts Users as input and according to some logic does something with them. (No comments in code, as function name and class name are supposed to be self-explanatory)
class User{
protected $name,$emp_type,$emp_id;
protected $department;
public function __construct($name,$emp_type,$emp_id){
$this->name = $name;
$this->emp_type = $emp_type;
$this->emp_id = $emp_id;
}
public function getEmpType(){
return $this->emp_type;
}
public function setDep($dep){
$this->department = $dep;
}
}
class UserHub{
public function putUserInRightDepartment(User $MyUser){
switch($MyUser->getEmpType()){
case('tech'):
$MyUser->setDep('tech control');
break;
case('recept'):
$MyUser->setDep('clercks');
break;
default:
$MyUser->setDep('waiting HR');
break;
}
}
}
$many_users = array(
0=>new User('bobo','tech',2847),
1=>new User('baba','recept',4443), many more
}
$Hub = new UserHub;
foreach($many_users as $AUser){
$Hub->putUserInRightDepartment($AUser);
}
/**
* Game class.
*/
class Game implements Countable {
/**
* Collect players here.
*
* #var array
*/
private $players = array();
/**
* Signal Game start.
*
*/
public function __construct(){
echo 'Game Started.<br />';
}
/**
* Allow count($this) to work on the Game object.
*
* #return integer
*/
public function Count(){
return count($this->players);
}
/**
* Create a player named $name.
* $name must be a non-empty trimmed string.
*
* #param string $name
* #return Player
*/
public function CreatePlayer($name){
// Validate here too, to prevent creation if $name is not valid
if(!is_string($name) or !strlen($name = trim($name))){
trigger_error('$name must be a non-empty trimmed string.', E_USER_WARNING);
return false;
}
// Number $name is also not valid
if(is_numeric($name)){
trigger_error('$name must not be a number.', E_USER_WARNING);
return false;
}
// Check if player already exists by $name (and return it, why create a new one?)
if(isset($this->players[$name])){
trigger_error("Player named '{$Name}' already exists.", E_USER_NOTICE);
return $this->players[$name];
}
// Try to create... this should not except but it's educational
try {
return $this->players[$name] = new Player($this, $name);
} catch(Exception $Exception){
// Signal exception
trigger_error($Exception->getMessage(), E_USER_WARNING);
}
// Return explicit null here to show things went awry
return null;
}
/**
* List Players in this game.
*
* #return array
*/
public function GetPlayers(){
return $this->players;
}
/**
* List Players in this game.
*
* #return array
*/
public function GetPlayerNames(){
return array_keys($this->players);
}
} // class Game;
/**
* Player class.
*/
class Player{
/**
* Stores the Player's name.
*
* #var string
*/
private $name = null;
/**
* Stores the related Game object.
* This allows players to point to Games.
* And Games can point to Players using the Game->players array().
*
* #var Game
*/
private $game = null;
/**
* Instantiate a Player assigned to a Game bearing a $name.
* $game argument is type-hinted and PHP makes sure, at compile time, that you provide a proper object.
* This is compile time argument validation, compared to run-time validations I do in the code.
*
* #param Game $game
* #param string $name
* #return Player
*/
public function __construct(Game $game, $name){
// Prevent object creation in case $name is not a string or is empty
if(!is_string($name) or !strlen($name = trim($name))){
throw new InvalidArgumentException('$name must be a non-empty trimmed string.');
}
// Prevent object creation in case $name is a number
if(is_numeric($name)){
throw new InvalidArgumentException('$name must not be a number.');
}
// Attach internal variables that store the name and Game
$this->name = $name;
$this->game = $game;
// Signal success
echo "Player '{$this->name}' was created.<br />";
}
/**
* Allow strval($this) to return the Player name.
*
* #return string
*/
public function __toString(){
return $this->name;
}
/**
* Reference back to Game.
*
* #return Game
*/
public function GetGame(){
return $this->game;
}
/**
* Allow easy access to private variable $name.
*
* #return string
*/
public function GetName(){
return $this->name;
}
} // class Player;
// Testing (follow main comment to understand this)
$game = new Game();
$player1 = $game->CreatePlayer('player 1');
$player2 = $game->CreatePlayer('player 2');
var_dump(count($game)); // number of players
var_dump($game->GetPlayerNames()); // names of players
Rewrote your code in a nicer way and added some missing variables that made that code pointless:
In player class you don't store the game.
In game class, you support only one player.
No error checking... anywhere.
Fixed all those plus:
Added exceptions (to prevent object creation)
Try{} catch(...){} Exception handling any OOP dev should know
Implemented Countable interface to allow count($game) players
Some more tricks that will give you a good read
Follow the comments and I hope your code will make more sense after you read it.

Categories