phpspec - method returns object instead of string - php

I'm still fresh in phpspec but usually I'm finding a solution when I struggle with something but this one is tough.
I've tried many different approaches and I haven't found a solution. I'm using Symfony2.
I have a class that I want to test:
class MyClass
{
public function getDataForChildren(MyObject $object)
{
foreach ($object->getChildren() as $child) {
$query = \json_decode($child->getJsonQuery(), true);
$data = $this->someFetcher->getData($query);
$child->setData($data);
}
return $object;
}
}
And here's how look my spec class:
class MyClassSpec
{
function let(SomeFetcher $someFetcher)
{
$this->beConstructedWith($someFetcher);
}
function it_is_initializable()
{
$this->shouldHaveType('MyClass');
}
function it_should_get_data_for_children_and_return_object(
MyClass $object,
MyClass $child, // it means that MyClass has a self-reference to MyClass
$someFetcher
)
{
$query = '{"id":1}';
$returnCollection = new ArrayCollection(array($child));
$object->getChildren()->shouldBeCalled()->willReturn($returnCollection);
$child->getJsonQuery()->shouldBeCalled()->willReturn($query);
$someFetcher->getData($query)->shouldBeCalled();
$this->getDataForChildren($object);
}
}
And after running phpspec I'm getting this error:
warning: json_decode() expects parameter 1 to be string, object given in
I have no idea how to solve this problem. If anyone has a clue, please help.

This is a common stumbling block with PhpSpec, the declaration:
MyClass $child
means that a Collaborator object of $child will be set up with the same interface of MyClass.
When child->getJsonQuery() is called in the SUT (class you're testing), it will return a MethodProphecy not the string you expect it to return.
What you want to say is that your ArrayCollection will contain not $child itself (which is a Collaborator object), but the real object that the collaborator is wrapped around. You do it like this:
$returnCollection = new ArrayCollection(array($child->getWrappedObject()));
In addition, you should not be using (i.e. is is superfluous) both
shouldBeCalled() and willReturn() on the same Collaborator, one or the
other is sufficient. If you've specified what the collabrator will
return, it is clear that it is going to be called witin the SUT.
shouldBeCalled() should be used in the "assert" part of the test in
order to confirm that the Collaborator was called with the expected
arguments, or at the right time.
Your final SUT and spec should look something like this:
class MyClass
{
/**
* #var SomeFetcher
*/
private $someFetcher;
public function getDataForChildren(MyObject $object)
{
foreach ($object->getChildren() as $child) {
$query = \json_decode($child->getJsonQuery(), true);
$data = $this->someFetcher->getData($query);
$child->setData($data);
}
return $object;
}
public function getJsonQuery()
{
}
public function setData()
{
}
public function __construct(SomeFetcher $someFetcher)
{
$this->someFetcher = $someFetcher;
}
}
class MyClassSpec extends ObjectBehavior
{
function let(SomeFetcher $someFetcher)
{
$this->beConstructedWith($someFetcher);
}
function it_should_get_data_for_children_and_return_object(
MyObject $object,
MyClass $child, // it means that MyClass has a self-reference to MyClass
SomeFetcher $someFetcher
)
{
$query = '{"id":1}';
$returnCollection = new ArrayCollection(array($child->getWrappedObject()));
$object->getChildren()->willReturn($returnCollection);
$child->getJsonQuery()->willReturn($query);
$child->setData(Argument::any())->shouldBeCalled();
$someFetcher->getData(array('id' => 1))->shouldBeCalled();
$this->getDataForChildren($object);
}
}
Also, the line
$query = \json_decode($child->getJsonQuery(), true);
Will produce a associated array in $query, i.e. array('id' => 1) (this is what the second 'true' argument to json_encode stipulates), therefore you'd expect $someFetcher->getData() to be called with the latter, hence:
$someFetcher->getData(array('id' => 1))->shouldBeCalled();

Related

Cannot build a class called statically in PHP

I have this structure:
class MyCollection extends BaseCollection
{
public function getEntityCLass() : string
{
return Item::class;
}
}
The base collection looks like this:
abstract class BaseCollection {
public function __construct(array $elements = array())
{
foreach ($elements as $entity) {
$this->add($entity);
}
}
}
This makes no sense to me ; getEntityClass should return a string but returns Item::class.
Sending an array to the class works but I honestly don't know why. Anyone care to explain?
Item::class is a short way of giving a string for the full class, it isn't a method or an object.
For instance.
Instead of Big\Massive\Long\Namespaced\ClassOfSomeSort, so long as you have imported that class with a use statement you can say ClassOfSomeSort::class which will give you the long string.

Assign functions from another file to a Class

I am trying to add functions to class from a separate file, I wonder if this could be possible!
$mClass = new MyClass();
$mClass->new_Functions[0](10); // Is there a way to have it in this form?
class myClass
{
private $Pvar = 5;
$new_Fcuntions;
function __construct()
{
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
}
}
[additional.functions.php] file
function operate($y)
{
return $this->Pvar * $y;
}
----- Edited ------- as it wasn't clear!
"additional.functions.php" is a module and there will be multiple modules to be added to the application, and every module could have more than single function and modules could call one another!
additional.functions.php [module file]
function operate($y)
{
return $this->Pvar * $y;
}
function do-more($foo)
{
return $this->operate(20) + $foo;
}
another.functions.php [another module]
function do-another($foo)
{
return $this->do-more(30) - $foo;
}
function add($foo, $bar)
{
return $foo + $bar;
}
appreciate every participation, its been a while since I am trying to maneuver around with it!
Is this possible or should I give up!
It looks to me like you are looking for Traits, which are a new feature as of PHP 5.4.0. Using traits, you can have snippets of code "mixed in" to other classes, a concept known as "horizontal reuse".
If you are not looking for traits, it's possible that you could do what you wanted with Runkit, however I would suggest staying as far away from it as possible, if you are not genuinely interested in PHP internals as well.
In any event, whatever you are trying to do is very interesting
I got it to work with dependency injection. The pvar has to be public or create a __get method to return the private variable. I also used the function name because it seems cleaner to me to use it via name rather than it's position in the list but if you want to keep that then just put $key where you see $value from the line: $this->function_list[$value] = ...
function operate($y, $that)
{
return $that->Pvar * $y;
}
class Example {
public $function_list = array();
private $Pvar = 5;
public function __construct()
{
$list = get_defined_functions();
$that = $this;
foreach ($list['user'] as $key => $value) {
$this->function_list[$value] = function() use ($value, $that) {
print call_user_func_array($value, array_merge(func_get_args(), array($that )));
};
}
}
public function __get($key)
{
if (isSet($this->$key)) {
return $this->$key;
} else {
throw new \Exception('Key "'.$key.'" does not exist');
}
}
}
$Ex = new Example();
$Ex->function_list['operate'](10);
If you want to extend MyClass from your modules (and not to initialize it, like in your example code), than you could do it in a way like this:
<?php
namespace modules\MyModuleA;
class MyClassExtension
{
private $MyObject;
public function __construct(\MyClass $MyObject)
{
$this->MyObject = $MyObject;
}
public function doSomething($anyParameter)
{
return $this->MyObject->doSomethingElse($anyParameter * 5, 42, 'foo');
}
}
And MyClass:
<?php
class MyClass extends \Extensible
{
// some code
}
abstract class Extensible
{
private $extensions = [];
public function extend($extension)
{
$this->extensions[] = $extension;
}
public function __call($methodName, $parameters)
{
foreach ($this->extensions as $Extension) {
if (in_array($methodName, get_class_methods($Extension))
return call_user_func_array([$Extension, $methodName], $parameters);
}
throw new \Exception('Call to undefined method ' . $methodName . '...');
}
public function hasExtension($extensionName)
{
return in_array($this->extensions, $extensionName);
}
}
And put it all together:
<?php
$moduleNames = ['MyModuleA', 'MyModuleB'];
$MyObject = new \MyClass;
foreach ($moduleNames as $moduleName) {
$className = '\\modules\\' . $moduleName . '\\MyClassExtension';
$module = new $className($MyObject);
$MyObject->extend($module);
}
// Now you can call a method, that has been added by MyModuleA:
$MyObject->doSomething(10);
You should add an interface for the extension classes of course...
The problem is: What happens if any code in your application calls a method of $MyObject, that is not there, because the module has not been loaded. You would always have to check if ($MyObject->hasExtension('ModuleA')) { ... }, but, of course, the application shouldn't be aware of any module. So I would not design an application in such a way.
I would suggest to use traits (mix-ins). See PHP reference
If you can have another class in that file instead of file with functions
- the best solution will be Traits
http://php.net/manual/en/language.oop5.traits.php
or using inheritance
If you move that code to class you can avoid a lot of unnecessary code. I mean:
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
It'll be e.g.:
class myClass extends MyBaseClassWithMyAwesomeFunctions
{
private $Pvar = 5;
}
Maybe this approach helps you:
In the files with the additional functions, don't define named functions, but return a closure, that expects (at least) the object (instance of MyClass) as parameter:
<?php
// additional.functions.php
return function ($myObject) {
$Object->multiplyPvar($myObject->getTheNumber());
$Object->doSomethingElse(42, 'foo');
};
The client, that builds MyClass collects those functions from the files into the array:
<?php
$files = [
'/path/to/my/additional.functions1.php',
'/path/to/my/additional.functions2.php'
];
$initFunctions = [];
foreach ($files as $path)
$initFunctions[] = include $path;
$MyObject = new \MyClass($initFunctions);
The constructor then calls those functions:
<?php
class MyClass
{
public function __construct(array $additionalInitFunctions)
{
foreach ($additionalInitFunctions as $additionalInitFunction)
$additionalInitializerFunction($this); // you can also add parameters of course
}
}
This way the class keeps very well testable as well as the function files. Maybe this could help you in any way. You should never ever think about modifying the internal (private) state of an object directly from any code from outside of the class. This is not testable! Think about writing tests before you implement your code (called "test driven development"). You will see, it is not possible to test a class, if you allow any code outside of that class to modify the internal (private) state of the class instance. And you don't want to have this. If you change some internal implementation detail in your class without breaking the unit test of that class, you will anyways probably break some code in any of your additional.functions.php files and no test will tell you: "Hey: you've broken something right now".

Is there a way to set only defined properties when using fetch_object on a mysqli_result?

If I have a class:
class ExampleClass {
private $thing1;
private $thing2;
}
I can use fetch_object('ExampleClass') on a mysqli_result object to create an instance of ExampleClass. Using this method, the private properties of ExampleClass will be set, provided the query has columns with the same names.
In addition to those properties, other public properties will be added for any other columns in the query. Is there any way to avoid this? I couldn't find anything about it in the php documentation for fetch_object.
If I set up ExampleClass with a constructor like this
function __construct($properties = []) {
foreach ($properties as $key => $value)
if (property_exists($this, $key))
$this->$key = $value;
}
I can get the row from the result set using fetch_assoc instead of fetch_object, and then create a new ExampleClass with the resulting array as an argument. This achieves what I am going for, but I was hoping for something more direct.
For one of my projects I have built a system just like that.
All classes are derived of the basic abstract Object class which, among others, offers a cloneInstance() method. Then, in the concrete implementing class, I'm simply going to use it ('I'm assuming that $pdo is somehow accessible here, for brevity):
Note that cloneInstance() uses reflection to check if the target instance actually has a euqlly named property ($drfl->hasProperty()).
abstract class Object {
protected function cloneInstance($obj) {
if (is_object($obj)) {
$srfl = new ReflectionObject($obj);
$drfl = new ReflectionObject($this);
$sprops = $srfl->getProperties();
foreach ($sprops as $sprop) {
$sprop->setAccessible(true);
$name = $sprop->getName();
if ($drfl->hasProperty($name)) {
$value = $sprop->getValue($obj);
$propDest = $drfl->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($this,$value);
}
}
}
return $this;
}
class MyAutomaticClass extends Object {
// static loader
public static function load($id) {
$result = null;
$sql = 'SELECT * FROM mytable WHERE id=:id';
$stmt = $pdo->prepare($sql, array(':id' => $id));
$list = $stmt->fetchAll(PDO::FETCH_OBJ);
if (count($list)) {
$result = new MyAutomaticClass($list[0]);
}
return $result;
}
// constructor makes use of base Objects cloning feature
public function __construct($obj=null) {
if (is_object($obj)) {
$this->cloneInstance($obj);
}
}
}

How to mock an Object Factory

I use Factories (see http://www.php.net/manual/en/language.oop5.patterns.php for the pattern) a lot to increase the testability of our code. A simple factory could look like this:
class Factory
{
public function getInstanceFor($type)
{
switch ($type) {
case 'foo':
return new Foo();
case 'bar':
return new Bar();
}
}
}
Here is a sample class using that factory:
class Sample
{
protected $_factory;
public function __construct(Factory $factory)
{
$this->_factory = $factory;
}
public function doSomething()
{
$foo = $this->_factory->getInstanceFor('foo');
$bar = $this->_factory->getInstanceFor('bar');
/* more stuff done here */
/* ... */
}
}
Now for proper unit testing I need to mock the object that will return stubs for the classes, and that is where I got stuck. I thought it would be possible to do it like this:
class SampleTest extends PHPUnit_Framework_TestCase
{
public function testAClassUsingObjectFactory()
{
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factoryMock = $this->getMock('Factory');
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
}
}
But when I run the test, this is what I get:
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) SampleTest::testDoSomething
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-bar
+foo
FAILURES!
Tests: 1, Assertions: 0, Failures: 1.
So obviously it is not possible to let a mock object return different values depending on the passed method arguments this way.
How can this be done?
The problem is that the PHPUnit Mocking doesn't allow you to do this:
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
You can only have one expects per ->method();. It is not aware of the fact that the parameters to ->with() differ!
So you just overwrite the first ->expects() with the second one. It's how those assertions are implemented and it's not what one would expect. But there are workarounds.
You need to define one expects with both behaviors / return values!
See: Mock in PHPUnit - multiple configuration of the same method with different arguments
When adapting the example to your problem it could look like this:
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factoryMock->expects($this->exactly(2))
->method('getInstanceFor')
->with($this->logicalOr(
$this->equalTo('foo'),
$this->equalTo('bar')
))
->will($this->returnCallback(
function($param) use ($fooStub, $barStub) {
if($param == 'foo') return $fooStub;
return $barStub;
}
));
Create a simple stub factory class whose constructor takes the instances it should return.
class StubFactory extends Factory
{
private $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function getInstanceFor($type)
{
if (!isset($this->items[$type])) {
throw new InvalidArgumentException("Object for $type not found.");
}
return $this->items[$type];
}
}
You can reuse this class in any unit test.
class SampleTest extends PHPUnit_Framework_TestCase
{
public function testAClassUsingObjectFactory()
{
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factory = new StubFactory(array(
'foo' => $fooStub,
'bar' => $barStub,
));
...no need to set expectations on $factory...
}
}
For completeness, if you don't mind writing brittle tests, you can use at($index) instead of any() in your original code. This will break if the system under test changes the order or number of calls to the factory, but it's easy to write.
$factoryMock->expects($this->at(0))
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->at(1))
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
you should change your "business logic" ... i mean you don't have to pass Factory to the Sample constructor, you have to pass the exact parameters you need

PHP: 'Dynamic' callback from inside/outside a class

we have a problem [cit.]
I need to assign a callback dynamically within a class, in base of a variable param: my goal is to have just one class (and not a main class and many extender sub-class), and inside this class if a value is X, then the funcitonX must be used, if is Y, the functionY.
I know i cant explain well, i hope my example will do:
class plzComplicateMyLife{
public $vehicle;
public $kindVehicle;
public $dynamicFunction;
public function __construct($vehicle, $kindVehicle){
$this->kindVehicle = $kindVehicle;
$this->vehicle = $vehicle;
switch($kindVehicle){
case 'cycle':
$this->dynamicFunction = "isACycle";
break;
case 'car':
$this->dynamicFunction = "isACar";
break;
}
//here come the problem, i need to call the callback store in dynamicFunction.
//i tried:
//call_user_func($this->$this->dinamicFunction, $this->vehicle);
//error: Catchable fatal error: Object of class plzComplicateMyLife could not be converted to string in [...]
//call_user_func("plzComplicateMyLife::".$this->dynamicFunction);
//Warning: call_user_func(plzComplicateMyLife::isACar) [function.call-user-func]: First argument is expected to be a valid callback in [...]
//$this->dynamicFunction();
//Fatal error: Call to undefined method plzComplicateMyLife::dynamicFunction() in [...]
//so, how can i do that?
}
public function isACycle($vehicle){
echo 'im a cycle, model: '.$vehicle.'<br />';
}
public function isACar($vehicle){
echo 'im a car, model: '.$vehicle.'<br />';
}
//i know this has no sense, in this example at least.
public function printKind(){
//call_user_func($this->$this->dinamicFunction, $this->vehicle);
//call_user_func("plzComplicateMyLife::".$this->dynamicFunction);
//then?
}
}
$maserati = new plzComplicateMyLife('maserati4', 'car');
//then, maybe, outside the class i'll need to recover the callback:
$maserati->printKind();
EDIT:
As Rob said, polymorphism would be really a good solution.
But the problem is that, in this case, i really must have the same declaration for every class instance, changing only the parameters...e.g:
$maserati = new plzComplicateMyLife('maserati4', 'car');
$ducati = new plzComplicateMyLife('maserati4', 'cycle');
//is good
//becose i cant have:
$maserati = new plzComplicateMyLifeWithACar('maserati4');
$ducati = new plzComplicateMyLifeWithACycle('maserati4');
Polymorphism is the way to go here but for future reference you can also do this:
public function printKind() {
$this->{$this->dynamicFunction}($this->vehicle);
}
In response to your edit, could you not do something like this instead?
abstract class MethodOfTransport {
protected $model;
public function __construct($model) {
$this->model = $model;
}
abstract public function printKind();
public static function create($model, $type) {
$object = new $type($model);
return $object;
}
}
class cycle extends MethodOfTransport {
public function printKind() {
echo 'im a cycle, model: '.$this->model.'<br />';
}
}
class car extends MethodOfTransport {
public function printKind() {
echo 'im a car, model: '.$this->model.'<br />';
}
}
$maserati = MethodOfTransport::create('maserati4', 'car');
$maserati->printKind();
$ducati = MethodOfTransport::create('maserati4', 'cycle');
$ducati->printKind();
In PHP you can use specify a method callback using an array as a callback variable (see here), for example:
array( $object, $methodName );
So you could do this
$callback = array($this, $this->dynamicFunction);
call_user_func($callback, $this->vehicle);
Er, why don't you want to use a simple inheritance structure here? If you want different behaviour depending upon the object modelled, then that's pretty much the canonical description of polymorphism.
If you really do want to plough on with callbacks into the same object, then you'll need to do one of two things:
Drop the $vehicle parameter from your callbacks, make them private or protected, and call into them normally, i.e.
call_user_func( array( $this, 'isACycle' ) );
Mark the callback as static, make them private or protected, and call into them as follows:
call_user_func( array( __CLASS__, 'isACycle' ), $this );
Within the non-static callback, access the object's properties via $this in the normal fashion. Note also that I suggest marking the callback as private or protected, in order to prevent unnecessary outside callers; presumably, you don't want them executing the wrong method for each type.

Categories