I am trying to inject one class (a simple PHP class) into another (an ORM\Entity annotated class), and getting an error, but I cannot discover the source. Looking at the code I feel like I am doing everything correctly, yet I cannot solve this error.
Here is the relevant code:
First, the class ORM\Entity where I want to inject the ErrorConstants class:
use Base\Model\Constants\ErrorConstants;
/**
* #ORM\Entity ...
*/
class CwPackagePeriod extends AbstractRestEntity
public $errors;
public function __construct()
{
parent::__construct();
$this->errors = new ErrorConstants();
}
}
The ErrorConstants class is a simple class that contains a list of error constants:
class ErrorConstants
{
public const ERR_MISSING = 'Record could not be found.';
}
The error occurs when I try to throw an exception in the CwPackagePeriod class if an integer value is out of bounds on a setter:
throw new InvalidOrMissingParameterException(
sprintf($this->errors::ERR_MISSING)
);
The error is the following:
Class name must be a valid object or a string
The AbstractRestEntity class does not contain any reference to ErrorConstants, and when I add the reference there, nothing changes with respect to the error. What am I doing wrong?
As u_mulder noted constants refer to class, not to class instance. In order to properly get the constants from your class you could use something like that in your ErrorConstants class:
public function getConstants()
{
$reflectionClass = new \ReflectionClass($this);
return $reflectionClass->getConstants();
}
then in your CwPackagePeriod class:
public function __construct()
{
parent::__construct();
$errorConstants = new ErrorConstants();
$this->errors = $errorConstants->getConstants();
}
...
throw new InvalidOrMissingParameterException(
sprintf($this->errors['ERR_MISSING']);
);
Of course the simplest solution would be to use just:
throw new InvalidOrMissingParameterException(
sprintf(ErrorConstants::ERR_MISSING);
);
Finally I would like to note, although it is not very intuitive, you CAN indeed use $this->errors::ERR_MISSING to get a constant. The reason because you get this error is probably because $this->errors is not defined in that part of code for some reason.
Related
It started when I was performing null checks everywhere to make sure I have the necessary entities for my interactor. Fortunately, I came across this post which points towards not allowing the entities to be in an invalid state do the check in your constructor. Now my Interactors use protected static $request to state explicitly which entities they require, which are then passed in during instantiation. I chose static so the check could be done prior to creating an instance of the Interactor.
abstract class Interactor {
protected static $request = [];
protected $entities = [];
final public function __construct(Entity ...$entities) {
$this->setEntities(...$entities);
$this->checkEntities();
}
final private function setEntities(Entity ...$entities) {
foreach($entities as $entity) {
$this->setEntity($entity);
}
}
final private function setEntity(Entity $entity){
$className = get_class($entity);
if (!in_array($className, static::$request)){
throw new Exception("Not a requested entity");
}
$this->entities[$className] = $entity;
}
final private function checkEntities(){
if (count(static::$request) != count($this->entities))
throw new Exception("Entity mismatch");
foreach(static::$request as $index=>$name) {
if (!array_key_exists($name, $this->entities))
throw new Exception("Missing requested entity ($name)");
if (!is_a($this->entities[$name], $name))
throw new Exception("Not the specified entity");
}
}
final public static function getRequest(){
return array_values(static::$request);
}
}
Ok great, now I just do the check in a single location and I don't need to worry about performing null checks at the beginning of my functions. The problem with the way I am going about it now is that my Interactor is checking the class name against a static class name request array. Thus, when I DI the mocked entities during testing, my parent Interactor throws an exception saying it isn't in the pre approved list.
To demonstrate is the following simplified Chess example:
class Chess extends Interactor {
protected static $request = ['Piece','Engine','Board'];
}
Then we have our Entities:
abstract class Entity {}
class Piece extends Entity {}
class Engine extends Entity {}
class Board extends Entity {}
And finally our test:
class ChessTest extends TestCase {
function setUp(){
$this->piece = $this->getMockBuilder(Piece::class)->getMock();
$this->engine = $this->getMockBuilder(Engine::class)->getMock();
$this->board = $this->getMockBuilder(Board::class)->getMock();
$this->chess = new Chess($this->piece, $this->engine, $this->board);
}
function testCanSetup(){
$this->assertTrue(
is_a($this->chess, Chess::class)
);
}
}
Which throws Exception: Interactor receiving entity not requested (Mock_Piece_faaf8b14)
Of course Mock_Piece_faaf8b14 is not going to be in our static::$request array, so this is destined to throw an exception.
The workaround I have come up with so far is to include in Entity:
public function getClassName(){
return get_called_class();
}
Then in Interactor->setEntity($entity) instead of using get_class($entity) I would use $entity->getClassName() which then becomes trivial to mock.
I thought the way I had created the Interactor was inline with what the previously mentioned post was getting at, only take the entities in the constructor. However, it all feel apart when I injected mocked entities.
1) Is there a way to avoid getClassName() in my entities?
2) Is there something in the entities I can mock that gets called in get_class() instead?
Thank you for your help!
You are checking to see if the name of your class is one of the keys in your $request array. And it isn't. The keys in your array are numerical 0, 1, 2 so you are throwing the exception. I think that you want to use in_array instead.
Though at the same time, this still wouldn't pass with the mock because you are checking to see if the class name is in $request. So the name won't be there at all either and the exception will still be thrown.
If all that your Interactor class is doing is making sure that the correct objects are passed into the constructor why not just use PHP's native type hinting?
Your Chess class becomes:
class Chess {
public function __construct(Piece $piece, Engine $engine, Board $board) { }
}
PHP will make sure that the passed in objects are of the correct type and will allow you to mock them for testing.
You get the type checking that you are looking for without need to use getClassName() at all.
I struggle with this problem for a while - and the reason is probably trivial.
Background
I've created parser module for my Yii2 application so I can call it from other places (mobile app, etc.) to get data from various websites. There may be many parser classes, all implementing same interface.
Project structure
...
/modules
\_ parser
\_components
\_parsers
\_SampleParser.php
\_controllers
\_DefaultController.php
\_Parser.php
...
I've removed some code for better readability.
DefaultController.php:
namespace app\modules\parser\controllers;
use Yii;
use yii\web\Controller;
use app\modules\parser\components\parsers;
use app\modules\parser\components\parsers\SampleParser;
/**
* Default controller for the `parser` module
*/
class DefaultController extends Controller
{
private function loadParser($parserName){
return new SampleParser(); // if I leave this here, everything works okay
$className = $parserName.'Parser';
$object = new $className();
if ($object instanceof IParseProvider){
return $object;
}
}
...
public function actionIndex()
{
$url = "http://google.com";
$parser = 'Sample';
$loadedParser = $this->loadParser($parser);
$response = $loadedParser->parse($url);
\Yii::$app->response->format = 'json';
return $response->toJson();
}
...
SampleParser.php:
<?php
namespace app\modules\parser\components\parsers;
use app\modules\parser\models\IParseProvider;
class SampleParser implements IParseProvider {
public function canParse($url){
}
public function parse($url){
}
}
Right now everything works more or less ok, so I guess I'm importing correct namespaces. But when I remove return new SampleParser(); and let the object to be created by string name, it fails with error:
PHP Fatal Error – yii\base\ErrorException
Class 'SampleParser' not found
with highlighted line:
$object = new $className();
What am I doing wrong here? Thanks!
Try again with help of Yii:
private function loadParser($parserName)
{
return \yii\di\Instance::ensure(
'app\modules\parser\components\parsers\\' . $parserName . 'Parser',
IParseProvider::class
);
}
Remember that ensure() throws \yii\base\InvalidConfigException when passed reference is not of the type you expect so you need to catch it at some point.
If you are using PHP < 5.5 instead of IParseProvider::class you can use full class name with it's namespace.
P.S. remove use app\modules\parser\components\parsers; unless you have got class named parsers you want to use.
This is a basic PHP question, and somehow I couldn't find any information about it.
So what I'm trying to do is to pass a parent class to a function so all of its children class can be passed too.
Is there anyway to do it?
function transform(Fruit $fruit){}
transform($orange)
transform($apple)
Update :
Looks like I need to show the situation more.
So what I have is a transformer parent
class ResourceTransformer{
public function transform(ResourceModel $model){}
}
And its child
class ColorTransformer extends ResourceTransformer{}
Now what I want to do is passing ColorModel, a child of ResourceModel to ColorTransformer.
class ColorModel extends ResourceModel{}
when I'm doing that it throws an error like this:
Type error: Argument 1 passed to App\\Modules\\Product\\Transformer\\ResourcesTransformer::transform() must be an instance of App\\Modules\\Product\\Models\\ResourceModel, instance of App\\Modules\\Product\\Models\\ColorModel given.
So basically, ColorTransformer can't accept ColorModel and only accept ResourceModel, yet ColorModel is a child of ResourceModel. Might be some of you can give me more enlightement.
I have tried this code to check your problem
class ResourceModel { }
class ResourceTransformer
{
public function transform(ResourceModel $model)
{
var_dump($model);
}
}
class ColorTransformer extends ResourceTransformer { }
class ColorModel extends ResourceModel { }
$transformer = new ColorTransformer();
$model = new ColorModel();
$transformer->transform($model);
and i've got correct result
object(ColorModel)#2 (0) { }
If this is not working in your case then it's look like the class ResourceModel form this part of your code
class ResourceTransformer{
public function transform(ResourceModel $model){}
}
is not the same that in this part
class ColorModel extends ResourceModel{}
You told in comment that the problem is only with the ColorModel but your information about error tell us that namespaces for ResourceModel and ColorModel are the same so I think the problem is with your ResourceTransformer class definition - do you have this code somewhere in top of your file with this definition?
use App\Modules\Product\Models\ResourceModel;
If you don't then do you have an another declaration of the ResourceModel class somewhere in your file with ResourceTransformer class definition?
The other thing I see in your question is the difference between this part of your code
class ResourceTransformer{
public function transform(ResourceModel $model){}
}
and class name in your error
Type error: Argument 1 passed to App\Modules\Product\Transformer\ResourcesTransformer::transform() must be an instance of App\Modules\Product\Models\ResourceModel, instance of App\Modules\Product\Models\ColorModel given.
Look that in error you have a class ResourcesTransformer and you tell us that this class is named ResourceTransformer. If this difference is ok then your problem should be placed in file with declaration of the ResourcesTransformer class.
You can simply pass name of class as a string and dinamically instantiate it:
function transform($name_of_class){
...
new $name_of_class;
}
Check the example please;
<?php
// function transform(Fruit $fruit){ //or
function transform($fruit){
print_r($fruit);
}
class Fruit{
public $name = null;
function __construct($name){
$this->name = $name;
}
}
transform(new Fruit('Apple')); // result: Fruit Object ( [name] => Apple )
echo '<br />';
$orange = new Fruit('Orange');
transform($orange); // result: Fruit Object ( [name] => Orange )
In a silex application I have a KafkaAPiClient class which definitely has the public method postMessages.
<?php
namespace Kopernikus\KafkaWriter;
use Kopernikus\KafkaWriter\Model\AbstractMessage;
/**
* KafkaApiClient.
**/
class KafkaApiClient
{
/**
* #param AbstractMessage[] $msg
*/
public function postMessages(array $messages)
{
foreach ($messages as $message) {
$this->postMessage($message);
}
}
public function postMessage(AbstractMessage $msg)
{
...
}
}
I can call KafkaAPiClient::postMessages just fine, yet when mocking the class in a test:
<?php
namespace unit\Request;
use Kopernikus\KafkaWriter\KafkaApiClient;
/**
* MockeryMethodsNotBeingCallableTest
**/
class MockeryMethodsNotBeingCallableTest extends \PHPUnit_Framework_TestCase
{
public function testMockMethodIsCallable()
{
$leMock = \Mockery::mock(KafkaApiClient::class);
$leMock->postMessages([]);
}
}
I am getting:
1) unit\Request\MockeryMethodsNotBeingCallableTest::testMockMethodIsCallable
BadMethodCallException: Method Mockery_11_Kopernikus_KafkaWriter_KafkaApiClient::postMessages() does not exist on this mock object
~/le-project/tests/unit/Request/MockeryMethodsNotBeingCallableTest.php:14
I am confused, I was expecting for the mock to not do anything yet allow the methods to be called so that I later could add my expectations on it.
Though I have found a solution, I am still wondering if it is possible to mock all the methods by default, and later check if certain ones have been called.
There exists shouldIgnoreMissing method on the mock object. Calling that does exactly what it says on the tin, that is: ignoring calls to not yet defined methods, resulting in a mock that does nothing:
$leMock = \Mockery::mock(KafkaApiClient::class);
$leMock->shouldIgnoreMissing()
$leMock->postMessages([]);
And by nothing, it means nothing. I got into an other error for my queue when I instantiated the mock that way, as methods will return null by default and their return value has to be explicitly stated.
$msg = new Message('dummy-message');
$this->kafkaQueue
->shouldIgnoreMissing()
->shouldReceive('getMessages')->andReturn([$msg]);
Any call to getMessages will now return exactly the array [$msg].
Alternatively, one can be very explicit about what methods are called with Mockery, by adding shouldReceive:
public function testMockMethodIsCallable()
{
$leMock = \Mockery::mock(KafkaApiClient::class);
$leMock->shouldReceive('postMessages');
$leMock->postMessages([]);
}
I've run into a problem and I'm not sure if this is just normal behaviour or if I wrote something wrong. I have a method in my base class that applies a global filter to a given class by way of creating a proxy for all new instances of that particular class. The way I planned to go about it is as follows:
Attach static $global_filter (the proxy) to the class I want to be filtered, which extends the base class object
Via my loading mechanism, return the proxy instead of the actual class upon new instantiations (which will mask method calls and apply filters accordingly)
However, I am getting stuck in step 1 and it seems that when I try to assign static $global_filter to the descendent class I want filtered, my base class object also gets the same assignment, which breaks everything else that extends from it.
Please see below for relevant code:
class object {
public static $global_filter;
public function _filterGlobal($class, $method, $callback) {
if ( !is_object($class::$global_filter) ) {
$class::$global_filter = new filterable(null);
# Replace the object being called with the new proxy.
}
var_dump($class);
var_dump($class::$global_filter); // `filterable`
var_dump(\core\blueprint\object::$global_filter); // Returns same as line above
die();
return $class::$global_filter->_add($method, $callback);
}
}
Both $class::$global_filter and \core\blueprint\object::$global_filter (the base class) are returning same instance. Whereas I expected object::$global_filter to be null.
I'm not using late static binding in order to preserve consistency (both single-object filters and global filters are called much in the same way non-statically).
This question seems relevant
Any help will be much appreciated :)
Edit, full example
This would be a concrete class, which extends model which extends object
<?php
use core\blueprint\model;
class modelMock extends model {
protected $schema = array();
public function method($test) {
return $test;
}
}
This would be another object (e.g a controller), which extends object aswell. It applies a filter to all new instances of model
<?php
use core\blueprint\object;
class objectMock extends object {
public function applyFilters() {
$this->_filterGlobal('core\blueprint\model', 'method', function($self, $chain) {
$chain->params[0] = 'new param'; // adjust the paramters
return $chain->next();
});
}
}
when I try to assign static $global_filter to the descendent class I want filtered, my base class object also gets the same assignment
Yes, indeed this happens. A static property in essence is a global variable, constrained within the class's namespace. Running into problems with global variables is often an indication you're not using the best solution.
To solve your problem, you could make the filter a (non-static) property:
$class->$filter = new Whatever();
But as always, there's more roads that lead to Rome, and I would advise you to look for alterative ways to do it.
I don't know if this is a help for you:
class a {
public static $type;
public static function setType($class, $newType) {
$class::$type = $newType;
var_dump($class::$type);
}
}
class b {
public static $type = 'myType';
}
var_dump(b::$type);
a::setType('b', 'yourType');
var_dump(a::$type);
May be you have not defined the static property to the concrete class.
Thanks everyone for you help, I spent some time on it this morning and managed to solve my problem. It's a bit of a workaround but here's how it goes:
public function _filterGlobal($class, $method, $callback) {
if ( !is_object($class::$global_filter[$class]) ) {
$class::$global_filter[$class] = new filterable(null);
# Replace the object being called with the new proxy.
}
return $class::$global_filter[$class]->_add($method, $callback);
}
So basically in order to get unique static variables working in child classes without having to explicitly define them, you can use an array that stores the child's class name as a key and then access these variables via a getter.