How to unit test PHP traits - php

I want to know if there is a solution on how to unit-test a PHP trait.
I know we can test a class which is using the trait, but I was wondering if there are better approaches.
Thanks for any advice in advance :)
EDIT
One alternative is to use the Trait in the test class itself as I'm going to demonstrate bellow.
But I'm not that keen on this approach since there is no guaranty there are no similar method names between the trait, the class and also the PHPUnit_Framework_TestCase (in this example):
Here is an example trait:
trait IndexableTrait
{
/** #var int */
private $index;
/**
* #param $index
* #return $this
* #throw \InvalidArgumentException
*/
public function setIndex($index)
{
if (false === filter_var($index, FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException('$index must be integer.');
}
$this->index = $index;
return $this;
}
/**
* #return int|null
*/
public function getIndex()
{
return $this->index;
}
}
and its test:
class TheAboveTraitTest extends \PHPUnit_Framework_TestCase
{
use TheAboveTrait;
public function test_indexSetterAndGetter()
{
$this->setIndex(123);
$this->assertEquals(123, $this->getIndex());
}
public function test_indexIntValidation()
{
$this->setExpectedException(\Exception::class, '$index must be integer.');
$this->setIndex('bad index');
}
}

You can test a Trait using a similar to testing an Abstract Class' concrete methods.
PHPUnit has a method getMockForTrait which will return an object that uses the trait. Then you can test the traits functions.
Here is the example from the documentation:
<?php
trait AbstractTrait
{
public function concreteMethod()
{
return $this->abstractMethod();
}
public abstract function abstractMethod();
}
class TraitClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$mock = $this->getMockForTrait('AbstractTrait');
$mock->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->concreteMethod());
}
}
?>

You can also use getObjectForTrait , then assert the actual result if you want.
class YourTraitTest extends TestCase
{
public function testGetQueueConfigFactoryWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$config = $obj->getQueueConfigFactory();
$this->assertInstanceOf(QueueConfigFactory::class, $config);
}
public function testGetQueueServiceWithoutInstanceWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$service = $obj->getQueueService();
$this->assertInstanceOf(QueueService::class, $service);
}
}

Since PHP 7 we can now use annonymous classes...
$class = new class {
use TheTraitToTest;
};
// We now have everything available to test using $class

Related

Phpdoc for variable "cast"?

base classes:
abstract class A
{
public function methodA() { echo __FUNCTION__.'<br>'; }
}
class B extends A
{
public function methodB() { echo __FUNCTION__.'<br>'; }
}
so far so good.
A factory which can create any of them:
class Factory
{
/**
* #return A
*/
public static function createAorB()
{
return new B();
}
}
phpdoc is right, since it say "return something comes from A".
Now the tricky part comes:
class OwnClass
{
/**
* #var A
*/
protected $var;
public function __construct()
{
$this->var = \Factory::createAorB();
}
}
class OwnClassB extends OwnClass
{
public function callMe()
{
$this->var->methodA();
$this->var->methodB(); // this will also run, but IDE wont autocomplete with this method
}
}
(new OwnClassB())->callMe();
this will run for sure, but check for the methodB() calling. IDE wont autocomplete with this method. How to tell that var is now B, not A with phpDoc?
You can override inherited properties for PHPStorm's autocompletion:
/**
* #property B $var
*/
class OwnClassB extends OwnClass
{
//...
}
However, I personally think you're trying to solve the wrong problem. If you have a Factory class that returns either A or B, you shouldn't type hint the return value to A. Instead, you should provide both:
class Factory
{
/**
* #return A|B
*/
public static function createAorB()
{
return new B();
}
}
Both options inform PHPStorm enough to let it show you both methods as autocomplete options, but the latter is accurate in terms of what the code actually does (well, not in this example, but given the function name I guess that's what your real-world implementation does).

Silex call controller within controller

When I receive an API request it routes trough the Application.php to the UserController.
The UserController does his thing with the information and I need to call the EmailController, because that is the controller that manages all the emails.
In the EmailController I have a function (its simplified):
class EmailController {
public function getEmail() {
return 1337 ;
}
}
In the UserController I have a function:
class UserController {
public function getUserMail(Request $request, Application $app) {
$number = ???;
return $number;
}
}
What do I have to call within the UserController to get the getEmail function of the EmailController?
If this is not a correct way of doing it, I would love to hear what term I am acutally searching for :)
Edit1:
As #lawrence-cherone pointed out, it should have been in a model.
It was stuck in my head that I had to use the controller for this task.
You could use the dependency injection to share the class that return number.
So your controllers will look like:
class EmailController
{
/**
* #var NumberCalculatorInterface
*/
private $numberCalculator;
/**
* #param NumberCalculatorInterface $numberCalculator
*/
public function __construct(NumberCalculatorInterface $numberCalculator)
{
$this->numberCalculator = $numberCalculator;
}
public function getEmail()
{
return $this->numberCalculator->getNumber();
}
}
and
class UserController
{
/**
* #var NumberCalculatorInterface
*/
private $numberCalculator;
/**
* #param NumberCalculatorInterface $numberCalculator
*/
public function __construct(NumberCalculatorInterface $numberCalculator)
{
$this->numberCalculator = $numberCalculator;
}
public function getUserMail(Request $request, Application $app)
{
$number = $this->numberCalculator->getNumber();
return $number;
}
}
Your class that calculate number or other more complex logic will be
interface NumberCalculatorInterface
{
public function getNumber();
}
class DefaultNumberCalculator implements NumberCalculatorInterface
{
public function getNumber()
{
return 1337;
}
}
Since the number calculation is not a logic proper to your EmailController cause you use the logic in several classes, it make sense to be an external class. You will be able to unit test it properly and to inject in all the classes that need this calculation to be done.
You will be able to declare it as service:
class NumberCalculatorProvider implements ServiceProviderInterface {
public function register(Container $pimple)
{
$pimple['number_calculator'] = function () {
return new DefaultNumberCalculator();
};
}
}
And inject it inside your controller easily (in the following example is use the ServiceControllerServiceProvider to declare controller as services):
class ControllerProvider implements ServiceProviderInterface {
public function register(Container $pimple)
{
$pimple['controller.user'] = function ($pimple) {
return new UserController($pimple['number_calculator']);
};
$pimple['controller.email'] = function ($pimple) {
return new EmailController($pimple['number_calculator']);
};
}
}
note: In my example i use silex 2., since its not specified in your question, you may need to adapt it if you use an older version but the logic remain the same.*
I think you need to make UserController inherit the function getEmail() from EmailController
class UserController extends EmailController {
public function getUserMail(Request $request, Application $app) {
$number = ???;
return $number;
}
}

Slim3 Container good practice?

Hello im learning PHP and i'am Building a REST API with the Slim3 Framework. I Create Routes Like this:
$container['HomeController'] = function () {
return new HomeController();
};
$currentContainer = CurrentContainer::getInstance();
$currentContainer->setContainer($container);
$app->get('/', 'HomeController:index')->setName("index");
My Problem was i had to pass the $container to every Single Controller Class iv'e created, because i need the container context in the Controller for routing etc.
then im build a Singleton Container Class like this:
class CurrentContainer
{
private static $instance;
private $container;
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (self::$instance == null) {
self::$instance = new CurrentContainer();
}
return self::$instance;
}
public function setContainer($container)
{
$this->container = $container;
}
/**
* #return mixed
*/
public function getContainer()
{
return $this->container;
}
}
so now its possible to create a "MainController" like this:
class Controller
{
/**
* #var mixed
*/
protected $view;
/**
* #var
*/
protected $router;
public function __construct()
{
$container = CurrentContainer::getInstance()->getContainer();
$this->view = $container->view;
$this->router = $container->router;
}
}
now all of my Controllers extends from the Controller class...
my question is now ... its that a good idea or is there a reason to not do it like that?
im thankful for every input
I've built some APIs with Slim Framework, and also tried so many method to get it done (of course in right way). I implemented MVC pattern on Slim Framework. The code example below:
For the controller, I created a base controller that injected with container. So the code:
<?php
namespace App\Controller;
use Slim\Container;
class Controller
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function __get($name)
{
return $this->container->get($name);
}
}
I loaded the base controller on dependencies container.
<?php
// controller
$container['controller'] = function ($c) {
return new App\Controller\Controller($c);
};
So I can get the container from the controller.
<?php
namespace App\Controller;
use App\Controller\Controller;
use Slim\Http\Request;
use Slim\Http\Response;
class HomeController extends Controller
{
public function __invoke(Request $request, Response $response, $args)
{
return $this->renderer->render($response, 'home');
}
}
I hope it helps.

PHP/Laravel - Can't initiate extend of abstract class

I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/

Programming Paradigms: Strongly Typed parameter with Inheritance

Warning: might cause TL:DR
I am working with PHP 5.3.10 and have the following problem. I do have an abstract class DataMapper, which is extended for the specific DataModel I want to persist. The following code does this trick:
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public abstract function save(IModel $model); // DISCUSSION
/* more helper functions here */
}
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic ... */ }
public function fetchAll() { /* ...magic ... */ }
public function save(IModel $model) { /* ...magic ... */ } // DISCUSSION
}
interface IModel {
public function setOptions(array $options);
public function toArray();
}
abstract class Model implements IModel {
protected $_fields = array();
protected $_data = array();
public function setOptions(array $options) { /* ...magic ... */ }
public function toArray() { /* ...magic ... */ }
public function __construct(array $options = null) { /* ...magic ... */ }
public function __set($name, $value) { /* ...magic ... */ }
public function __get($name) { /* ...magic ... */ }
}
class PersonModel extends Model {
protected $_fields = array('id', 'name', 'passhash', /*...*/);
public function setId($value) {
/* ...Validation happening... */
$this->_data['id'] = $value;
return $this;
}
public function checkPassword($password) { /* ...magic... */ }
}
This works fine, but is really quirky for my feeling.
As you can see, I've used an interface IModel to be able to tell the DataMapper, that it does need a certain set of parameters and methods. However, some Models do have extra methods needed by the corresponding DataMapper - in the example, a checkPassword() method, which is used test a password against the stored hash value. This method may also instruct the DataMapper to rehash the just tested password and update it due to new requirements (e.g. an increased difficulty for a password hash function).
So what I actually want is to change the signature of PersonMapper to PersonMapper::save(PersonModel $model) - and e.g. in another DataMapper toPostMapper::save(PostModel $model), etc. This is due to these DataMappers needing a certain signature. So my ideal solution looks like this:
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public abstract function save(Model $model); // UPDATED
}
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(PersonModel $model) { /* ...magic... */ } // UPDATED
}
abstract class Model { /* ...unchanged... */ }
class PersonModel extends Model { /* ...unchanged... */ }
Notice the Update save-Methods in the abstract class and its implementation. Since PersonModel is inherited from Model, thus obviously having a common base set of signatures, I would expect this to work just fine. But it doesn't - PHP complains about a changed interface in the childclass PersonMapper
My Questions:
Is there another solution working with PHP 5.3.10 that expresses the relationship better?
Does it work in a later version of PHP, so that it might be worth upgrading the server?
You might try using interfaces instead.
interface OtherModel {
public function getThis();
}
interface OtherOtherModel {
public function getThat();
}
Your Model Class might implement one or more interfaces...
class PersonModel extends Model implements OtherModel {
protected $_fields = array('id', 'name', 'passhash', /*...*/);
public function setId($value) {
/* ...Validation happening... */
$this->_data['id'] = $value;
return $this;
}
public function checkPassword($password) { /* ...magic... */ }
public function getThis() {
// ...
}
}
Your concrete Mapper Class can use the instanceof to check if this Model does what it should.
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(Model $model) {
// verify that certain methods are implemented...
// throw an exception or reacting accordingly
print ($model instanceof PersonModel)? 'yes' : 'no';
print ($model instanceof OtherOtherModel)? 'yes' : 'no';
}
}
Another possible approach might be the following:
<?php
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public function save(Model $model) {
throw new Exception('You have to implement this!');
}
}
Throw an Exception if the save method is not overriden in an inheriting class.
Now you can really use a different typehint.
This will work:
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(PersonModel $model) {
// do something
}
}
I could think of another possible approach, by using interfaces to define the implementation.
Like for example:
interface PersonModelAware {
public function save(PersonModel $model);
}
interface OtherModelAware {
public function save(OtherModel $model);
}
etc. Your abstract method might have a default save method or no save method at all. The inheriting class will implement the interface it needs.
To sum it up, making your type more specific will not work as the abstract method clearly states it expects a Model.

Categories