PHP callback vs template - php

I've built a list rendering class:
class ListRenderer
{
/**
* #param int $columns number of columns
* #param string $element container element
* #param string $styleClass container style
*/
public function __construct($columns,$element='div',$styleClass=''){...}
...
/**
* #param mixed $callback function to render items - should take two
* parameters ($item,$index)
* #param array $list items to render
*/
public function renderArrayList($callback,$list){...}
/**
* #param mixed $callback function to render items - should take 3 parameters
* ($row,$i,$count) $i and $count are the position and total items
* #param string $sql query string
* #param string $errorMessage
* #param int $blanks number of blank items to render. The callback will be
* invoked with a null $row parameter for the blank records.
*/
public function renderQueryList($callback,$sql,$errorMessage,$blanks=0){...}
...
}
The callback function renders a single item.
This could also be accomplished using templates:
class ListRenderer
{
...
//$itemRenderer implements ListItemRenderer
public function renderArrayList($itemRenderer,$list){...}
//$itemRenderer implements ListItemRenderer
public function renderQueryList($itemRenderer,$sql,$errorMessage,$blanks=0){...}
...
}
template ListItemRenderer
{
public function renderArrayItem($item,$index);
public function renderQueryItem($row,$index,$count);
}
class SomeClass implements ListItemRenderer
{
...
public function renderArrayItem($item,$index){...}
public function renderQueryItem($row,$index,$count){...}
...
}
I'm not sure why I went with callbacks on this one; coming from a Java background I would normally be inclined to use the second approach.
It seems to me that:
Callbacks are more flexible
Templates would limit a single class to one renderArrayItem function, for example, where callbacks would allow use of multiple functions per class for that purpose.
The template approach requires the function to be a class member.
Callbacks tend to produce less maintainable code.
Are there any strong reasons to go one way or the other on this?

There can be multiple reasons for the one versus the other and the other way round. Especially for your case I have no clue what the difference is because I don't know your application.
So I ask back: Why one versus the other? If you still don't know which way to go, or unsure if you want the one or the other explicitly, why don't you make a callback variant you can use when needed? You can inject the callbacks when instantiating the class:
class ListItemCallbackRenderer implements ListItemRenderer
{
private $callbacks;
public function __construct(array $callbacks)
{
$this->callbacks = $callbacks;
}
public function renderArrayItem($item,$index)
{
$callback = $this->callbacks[__FUNCTION__];
// ...
}
public function renderQueryItem($row,$index,$count)
{
$callback = $this->callbacks[__FUNCTION__];
// ...
}
}
Done this, the interface stays the same which makes your overall design more fluid and you can decide which variant to use everywhere in your application. No need to degrade yourself to one method actually.

Related

Hooks or Events to reduce coupling between packages

What is the best approach to reduce coupling between modules?
For example, if I have an Invoice package and it is related to theCustomer package.
What I understand is that I would have to use a system of "hooks" to inject, for example, a tab with the list of customer invoices in the edit view of a customer.
And in turn, use an event system to know, from the perspective of the "Invoice" package, when, for example, someone tries to delete a client.
What I want to achieve is to reduce the coupling so that if I delete the invoice package, the client package is not affected.
How can I get it? Using Laravel's event system? Using a custom class like the following?
My Hooks class:
class HookRepository
{
/**
* The repository items.
*
* #var \Illuminate\Support\Collection
*/
protected $items;
/**
* Create a new repository instance.
*
* #return void
*/
public function __construct()
{
$this->items = collect();
}
/**
* Dynamically call methods.
*
* #param string $method
* #param array $arguments
* #return mixed
*/
public function __call(string $method, array $arguments)
{
return $this->items->{$method}(...$arguments);
}
/**
* Register a new hook callback.
*
* #param string|array $hook
* #param callable $callback
* #param int $priority
* #return void
*/
public function register($hook, callable $callback, int $priority = 10): void
{
$this->items->push(compact('hook', 'callback', 'priority'));
}
/**
* Apply the callbacks on the given hook and value.
*
* #param string $hook
* #param array $arguments
* #return mixed
*/
public function apply(string $hook, ...$arguments)
{
return $this->items->filter(function ($filter) use ($hook) {
return !! array_filter((array) $filter['hook'], function ($item) use ($hook) {
return Str::is($item, $hook);
});
})->sortBy('priority')->reduce(function ($value, $filter) use ($arguments) {
return call_user_func_array($filter['callback'], [$value] + $arguments);
}, $arguments[0] ?? null);
}
}
Using interfaces and abstract classes to implement Open/Close principle from SOLID
Identify an interface. What ClassA wants from ClassB
Generalize - when you have an interface you will be able to identify common operations that most classes that need to implement it will need it. ( Don't try to future-proof to much here or it will most likely backfire )
Note: If you are doing this in hope you will avoid refactoring your code. Forget it. :) It will only make it a lot easier which already is a huge benefit.
Avoid using hooks to realize Structural and/or Behavioral patterns.
edit>
Neither Package nor Hook is a part of Laravel or Design Patterns lingo.
This itself should give you a hint.
Let me play a guessing game:
<?php
PackageInterface {
public function enable();
public function disable();
public function isEnabled();
public function getHooks(): HookInterface[];
}
HookInterface {
public function injectView();
// or maybe
public function injectIntoView($view);
}
It purely depends on your circumstances when you will load your packages and inject something into view.
You can for example enable() the Package when $invoice->wasPaid() or when Customer enabled the package himself $customer->enable($package)

Strict standards notices View/Layout class

I am geting a headache with php "Strict Standards" notices in a class:
Parent class:
class View {
/**
*
* #param string $viewFolder
* #param string $viewBasename
* #return boolean
*/
public static function exists($viewFolder, $viewBasename){
$exists = false;
if(\is_string($viewFolder) && \is_string($viewBasename)){
$exists = \is_file(\APPLICATION_PATH."View/$viewFolder/$viewBasename.phtml");
}
return $exists;
}
/**
*
* #param string $viewFolder
* #param string $viewBasename
* #param array $viewVariables
*/
public static function load($viewFolder, $viewBasename,array $viewVariables = []){
extract($viewVariables);
require \APPLICATION_PATH."View/$viewFolder/$viewBasename.phtml";
}
}
Child class:
class Layout extends View{
/**
*
* #param string $viewBasename
*/
public static function exists($viewBasename) {
return parent::exists('_layout', $viewBasename);
}
/**
*
* #param string $viewBasename
* #param array $viewVariables
*/
public static function load($viewBasename, array $viewVariables = array()) {
parent::load('_layout', $viewBasename, $viewVariables);
}
}
I've read this topic and now its clear that the reason are those missing parameters in the child class methods.
Declaration of Methods should be Compatible with Parent Methods in PHP
Is there a way of getting rid of these notices without disabling error reporting, or maybe a better approach of doing this?
Thanks in advance.
The better approach is to write your classes in a clean and sensible way. In terms of OOP practice, your child classes that need to extend the parent's methods should redefine them in the same format (hence your warning from PHP).
In your example, your general workflow for the exists() method implementation appears to be the following:
Parent class has an exists method with a folder and a filename
Child class is cutting corners, because it knows its folder already, and only accepts a filename
Child class passes a predefined variable to the parent method
If you look at this objectively, your goal is that the view should be able to call the exists() method on a Layout class and only pass the one parameter, so then you ask "how can I remove the requirement to pass the folder?" Here are a couple of options:
1: Pass the folder name in as the second argument, and making it optional in the Layout (child) class's implementation:
# Class: Layout
/**
* #param string $viewBasename
* #param string $viewFolder
*/
public static function exists($viewBasename, $viewFolder = '_layout') {
return parent::exists($viewBasename, $viewFolder);
}
# Class: View
public static function exists($viewBasename, $viewFolder) {
// essentially you swap around the order of the params
}
2: Don't pass in the folder at all, but use a class property in the child and take advantage of late static bindings:
# Class: Layout
/**
* Define the folder for your layouts
* #var string
*/
const VIEW_FOLDER = '_layout';
The exists() implementation stays the same as in your example currently.
# Class: View
public static function exists($viewBasename) {
// Get your folder from a child instead of an argument
$viewFolder = static::VIEW_FOLDER;
$exists = false;
if(\is_string($viewFolder) && \is_string($viewBasename)){
$exists = \is_file(\APPLICATION_PATH."View/$viewFolder/$viewBasename.phtml");
}
return $exists;
}
A note here, you could also use a function in place of the constant, either abstract in the View class or not, e.g.:
# Class: View
abstract class View {
/**
* This method should be defined in children to provide the layout name.
* Using an abstract method would ensure that it is defined by children,
* however if View is going to be used on its own then do not use this approach.
* #return string The view's folder name
*/
abstract protected static function getViewFolder();
public static function exists($viewBasename) {
// Get view folder from the children (same as the constant example)
$viewFolder = static::getViewFolder();
// ...
}
}
# Class: Layout
class Layout extends View {
protected static function getViewFolder() {
return '_layout';
}
public static function exists($viewBasename) {
return parent::exists($viewBasename);
}
}
To be honest, the constant option is a little shorter and they both essentially do the same thing, other than if you use a function instead of a constant you would be able to define manipulation logic if required.
If I were you, I'd use a class constant for the view folder and take it out as an argument. You'd then implement the static::VIEW_FOLDER in place of the argument passed into load and exists.

General help about Iterators with OOP in PHP

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;
}

How can I use PHPDoc to type-hint the parameters of a Callable?

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.

Identic lines in each action - should I let them this way or not? - Symfony2

I have a controller which has actions for inserting in the database, updating, deleting and some others, but almost all of the actions contain in them this lines:
$em = $this->getDoctrine()->getEntityManager();
$friend = $em->getRepository('EMMyFriendsBundle:Friend')->find($id);
$user = $this->get('security.context')->getToken()->getUser();
Is this OK, or it's code duplication? I tried to make a property called $em and to have a constructor like this:
public function __construct()
{
$this->em = $this->getDoctrine()->getEntityManager();
}
but it didn't work. As for the queries especially the one with the $id parameter, I don't even know how to separate them in one place, so each action to be able to use them. One way is a function, but is there sense in a function like this? And if yes what should it return? An array?
Please advise me for the optimal way!
What I do, for Symfony2, in the controllers to avoid code duplication is creating a class called Controller.php in which I put the function I often use.
For example :
<?php
namespace YourProject\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller as BaseController;
/**
* Base Controller for xxBundle
*/
class Controller extends BaseController
{
/**
* Get repository
*
* #param string $class class
*
* #return Doctrine\ORM\EntityRepository
*/
protected function getRepository($class)
{
return $this->getDoctrine()->getEntityManager()->getRepository($class);
}
/**
* Set flash
*
* #param string $type type
* #param string $text text
*/
protected function setFlash($type, $text)
{
$this->get('session')->getFlashBag()->add($type, $text);
}
/**
* Returns the pager
*
* #param integer $page Page
* #param integer $perPage Max per page
* #param Doctrine_Query $query Query
*
* #return \Pagination
*/
public function getPager($page = 1, $perPage = 10, $query = null)
{
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$query,
$this->get('request')->query->get('page', 1),
$perPage
);
return $pagination;
}
After creating this controller, you need to make your apps controller extends the controller you've created.
That way, you avoid duplicated code and alias for popular method.
You can do:
private $em;
private $friend;
private $user;
private function init($id==null) {
$this->em = $this->getDoctrine()->getEntityManager();
$this->friend = $id?$this->em->getRepository('EMMyFriendsBundle:Friend')->find($id):null;
$this->user = $this->get('security.context')->getToken()->getUser();
}
Then you can call in your actions
$this->init($id);
or
$this->init();
And you will have
$this->em;
$this->friend;
$this->user;
available. Note that I allowed for the $id parameter not to be set, as I guess that in some actions you will not have it.
If you want this init function to be available in different controllers, create a base controller and extend from it, as suggested in another answer.
The thing you are looking for probably is param converter which maps action param to object directly.
Here is description and some examples:
http://symfony.com/doc/2.0/bundles/SensioFrameworkExtraBundle/annotations/converters.html
edit:
Some more info in an interesting article:
http://www.adayinthelifeof.nl/2012/08/04/multiparamconverter-for-symfony2/
If you have that code only in a couple of controllers you can wrap that code into a protected method for both.
If you think that you can reuse that code in more parts of your application then you should start to think if you need write a validator, use a service or another kind of design

Categories