Php - making a class instance that has dependencies in the constructor - php

I have a class, that has a constructor that looks like this:
use App\Libraries\Content\ContentInterface;
use EllipseSynergie\ApiResponse\Laravel\Response;
class ImportController extends Controller
{
private $indexable;
function __construct(Response $response, ContentInterface $contentInterface) {
$this->indexable = \Config::get('middleton.wp.content.indexable_types');
$this->response = $response;
$this->contentInterface = $contentInterface;
}
public function all() {
$include = array_diff($this->indexable, ['folder']);
$importResult = $this->import($include);
$this->deleteOldContent($importResult['publishedPostsIDs']);
return $importResult['imported'];
}
How can I instantiate this class from another class and call the method all() from it?
I have tried with something like this:
use EllipseSynergie\ApiResponse\Laravel\Response;
use App\Libraries\Content\ContentInterface;
class ContentImport extend Command {
public function handle() {
(new ImportController(new Response, new ContentInterface))->all();
}
But, that doesn't work, I get the error that I should pass the arguments to the Response class too:
[Symfony\Component\Debug\Exception\FatalThrowableError]
Type error: Too few arguments to function EllipseSynergie\ApiResponse\AbstractResponse::__construct(), 0 passed in /home/
vagrant/Projects/middleton/app/Console/Commands/ContentImport.php on line 43 and exactly 1 expected
What is the correct way of doing this?

I believe this should work
use EllipseSynergie\ApiResponse\Laravel\Response;
use App\Libraries\Content\ContentInterface;
class ContentImport extend Command {
public function handle(ImportController $importController) {
$importController->all();
}

Related

Cannot mock partial Log facade in Laravel

I'm trying to mock laravel Log. This is my code:
public function test_process_verify_card()
{
Log::shouldReceive('error')->once();
Log::makePartial();
$class = new MyClass();
$class->myFunction();
}
This is MyClass look like:
class MyClass
{
public function __construct()
{
$this->logger = Logg::channel('test');
}
public function myFunction()
{
// ... some logic
$this->loggger->error('Errror!!');
}
}
When I run test this test case, it throw error
Call to a member function runningUnitTests() on null
at vendor/laravel/framework/src/Illuminate/Log/LogManager.php:568
I tried to debug this error by putting dd() in LogManager class
protected function parseDriver($driver)
{
$driver ??= $this->getDefaultDriver();
dd($this->app); // <--- This is my code
if ($this->app->runningUnitTests()) {
$driver ??= 'null';
}
return $driver;
}
But it show that $this->app is not null.
I've tried mock facade Date before and it works fine.
I want to test that myFunction executes logging action. Is this correct way to do it?
Update
I also tried to mock it through partialMock() function:
public function test_process_verify_card()
{
$this->partialMock(Logger::class, function (MockInterface $mock) {
$mock->shouldReceive('error')->once();
});
$class = new MyClass();
$class->myFunction();
}
But it still not works, it shows error:
Method error(<Any Arguments>) from Mockery_0_Illuminate_Log_Logger should be called
exactly 1 times but called 0 times.
at vendor/phpunit/phpunit/phpunit:98
I would believe the problem why this is not working, is as Log::channel returns a channel on the partial mock. Therefor the mocked instance never receive the error call.
In Mockery you can easily do chained calls, by using '->' in the shouldReceive() call.
Log::shouldReceive('channel->error')
->once()
->andReturn(null);

Is it possible to invoke an instance of a class whose name is passed as a variable?

I'm developing in Laravel 9, though I assume this is Php-specific. Example below of what I'm trying to achieve: Imagine I have a controller named HomeController.php with a getData() method that returns something I need...
<?php
namespace App\Http\Controllers;
class HomeController
{
public function getData()
{
return [my data]
}
}
And I want to be able to call that class and method in a dynamic way, and assign my data to $data...
<?php
use App\Http\Controllers\HomeController;
class Example
{
public $className = 'HomeController';
public $method = 'getData';
public function index()
{
$instance = new $this->className;
$method = $this->method;
$data = $instance->$method();
}
}
I have a variation of this setup in my application, and it's not working. I get the following error: Class "HomeController" not found.
If I replace $this->className with HomeController it works. Keep in mind $className will be passed from elsewhere, I want to avoid hard-coding class names into my Example class.
It is true that I will still need to include them all at the top anyway, but I just want to know if it's possible to pass a class name like that. Unless there's a way to dynamically include those too, but I doubt it.
Edit: Tim's answer in the comments worked great. Here is a fixed version:
<?php
use App\Http\Controllers\HomeController;
class Example
{
public $className = 'App\\Http\\Controllers\\HomeController'; // Change 1
public $method = 'getData';
public function index()
{
$instance = app()->make($this->className); // Change 2
$method = $this->method;
$data = $instance->$method();
}
}

makePartial() returns Mockery\Exception\BadMethodCallException : Method does not exist on this mock object

I'm trying to test my Category class. I'm using Mockery::mock() method, with 'overload:' prefix and makePartial() method.
When running test I have this error:
Mockery\Exception\BadMethodCallException : Method App\Models\Category::getDynamicFieldsForDocument() does not exist on this mock object
Here is my code:
namespace App\Models;
class Category extends DictionaryBase{
//some methods
public function getDynamicFieldsForDocument()
{
$data = [];
$parents = [];
$allParents = $this->getParents($this->id, $parents);
foreach ($allParents as $parentId) {
$parent = Category::find($parentId);
$fields = $parent->dynamicFields();
foreach ($fields as $field) {
$data[$field['name']] = $field;
}
}
return $data;
}
}
TestCase:
namespace Tests\Unit;
use App\Models\Category;
use Tests\TestCase;
class CategoryModelTest extends TestCase{
//some methods
/**
* #runInSeparateProcess
* #preserveGlobalState disabled
*/
public function testGetDynamicFieldsForDocument()
{
$mockCategory = \Mockery::mock('overload:'.Category::class)->makePartial();
$preparedDynamicFields = $this->prepareDynamicFields();
$preparedCategories = $this->prepareCategories();
$mockCategory->shouldReceive('find')->andReturn($preparedCategories[0], $preparedCategories[1], $preparedCategories[2]);
$mockCategory->shouldReceive('getParents')->andReturn(['1a2b', '3c4d', '5e6f']);
$mockCategory->shouldReceive('dynamicFields')->andReturn(null, $preparedDynamicFields[0], $preparedDynamicFields[1]);
$response = $mockCategory->getDynamicFieldsForDocument();
dd($response);
}
}
I have no idea why i still have error. I think when ->makePartial() method is called it should mock only methods, which are called by ->shouldReceive()
EDIT:
Now I'm making mock instance without :overload, and mocking 'find' method in this way:
`$mockCategory->shouldReceive('find')->andReturn($preparedCategories[0], $preparedCategories[1], $preparedCategories[2]);`
My find method looks like this:
public static function find($id) {
return $id ? self::list(config(static::IDENT.'.fields'), (new Filter('and'))->add('id', $id, '')->toArray(),[],1,1)[0] ?? null : null;
}
And this is my error:
Error : Wrong parameters for App\Exceptions\ApiException([string
$message [, long $code [, Throwable $previous = NULL]]])
It's because list method call API so it looks like this method is called without mock.
I know that i can't mock static method, but earlier when I used :overload it was possible. What's now?
Delete :overload and just define your mock as:
$mockCategory = \Mockery::mock(Category::class)->makePartial()
Example
Model:
namespace App\Models;
class Foobar extends BaseModel
{
public function foonction()
{
Foobar::find();
return '1';
}
}
Test:
namespace Tests;
use Evalua\Heva\Models\Foobar;
class FoobarTest extends TestCase
{
public function testFoobar()
{
$fooMock = \Mockery::mock('overload:'.Foobar::class)->makePartial();
$fooMock->shouldReceive('find')->once();
$fooMock->foonction();
}
}
Fails with:
Mockery\Exception\BadMethodCallException: Method Evalua\Heva\Models\Foobar::foonction() does not exist on this mock object
Without the :overload the test pass.
The explanation should be based on what's written in the documentation about overload:
Prefixing the valid name of a class (which is NOT currently loaded) with “overload:” will generate an alias mock (as with “alias:”) except that created new instances of that class will import any expectations set on the origin mock ($mock). The origin mock is never verified since it’s used an expectation store for new instances. For this purpose we use the term “instance mock”

Using method from library included by use

I need to use method from Nette library, that I'm including by use command. But it doesn't work as I want to, throws fatal error, that I am calling undefined method.
How should I approach that method to make it work? Stupid question, but I am kinda new to OOP...
Method from class PresenterComponent.php
public function getPresenter($need = TRUE)
{
return $this->lookup('Nette\Application\UI\Presenter', $need);
}
And my code, where I need to use that method:
use Nette\Application\UI\PresenterComponent;
class DatabaseCollectionAdapter extends ArrayDataAdapter
{
// ..... some code......
$this->user = $this->getPresenter()->getUser();
Error:
Fatal Error
Call to undefined method Ctech\Gridator\DataAdapter\DatabaseCollectionAdapter::getPresenter()
change this
$this->user = $this->getPresenter()->getUser();
to this:
$object = new yourObject(); // yourObject extends PresenterComponent
$this->user = $object->getPresenter()->getUser();
use Nette\Application\UI\PresenterComponent; does not include or do any kind of magic to make it's functions available on the fly.
https://secure.php.net/manual/de/language.namespaces.importing.php
It's just a shorthand that helps you to use PresenterComponent directly without specifying the whole namespace.
Your DatabaseCollectionAdapter or ArrayDataAdapter has to have a function that looks like this:
class AdapterClass
public function getPresenter() {
return new Nette\Application\UI\PresenterComponent;
}
}
or something like this
use Nette\Application\UI\PresenterComponent;
class AdapterClass
public function getPresenter() {
return new PresenterComponent;
}
}

codeception acceptance test with fixtures in yii2

Sorry for maybe silly question, but I cannot find answer through googling.
My question is:
I created file TaskCest.php under backend\acceptance, In that file have following declaration
use yii\test\FixtureTrait;
public function fixtures() {
return ['tasks' => TasksFixture::className()];
}
I have that fixture class with data in data directory.
But when I run script I get following error:
[yii\base\ErrorException] ltrim() expects parameter 1 to be string, object given
Error is obvious, but I cant understand, in file yii2\test\FixtureTrait.php:145 I have function which expects name parameter to be string but object passed automatically [I dont call getFixture].
What's problem. Did someone faced the same?
-vvv output
Test tests/acceptance/TaskCest.php:getFixture
[yii\base\ErrorException] ltrim() expects parameter 1 to be string, object given
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/Lib/Di.php:123
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/Lib/Di.php:123
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/Test/Cest.php:136
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/Test/Cest.php:148
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/Test/Cest.php:82
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/Test/Test.php:90
/home/nginx/www/planning-back/vendor/phpunit/phpunit/src/Framework/TestSuite.php:728
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/PHPUnit/Runner.php:98
/home/nginx/www/planning-back/vendor/codeception/codeception/src/Codeception/SuiteManager.php:154
/home/velaro/.config/composer/vendor/codeception/codeception/src/Codeception/Codecept.php:183
/home/velaro/.config/composer/vendor/codeception/codeception/src/Codeception/Codecept.php:152
/home/velaro/.config/composer/vendor/codeception/codeception/src/Codeception/Command/Run.php:282
/home/velaro/.config/composer/vendor/symfony/console/Command/Command.php:255
/home/velaro/.config/composer/vendor/symfony/console/Application.php:829
/home/velaro/.config/composer/vendor/symfony/console/Application.php:191
/home/velaro/.config/composer/vendor/symfony/console/Application.php:122
/home/velaro/.config/composer/vendor/codeception/codeception/src/Codeception/Application.php:103
/home/velaro/.config/composer/vendor/codeception/codeception/codecept:34
Codeception recognize trait's methods like tests (it search all public methods of Cest-class include trait's methods and run it).
You should extract trait to another class FixtureLoader, and include it into your Cest file.
class FixtureLoader
{
use \yii\test\FixtureTrait;
public $fixtures;
public function fixtures()
{
return $this->fixtures;
}
}
abstract class ApiCest
{
/**
* #var FixtureLoader
*/
protected $fixtureLoader;
public function __construct()
{
$this->fixtureLoader = new FixtureLoader();
}
protected function fixtures()
{
return [];
}
public function _before(\FunctionalTester $I)
{
$this->fixtureLoader->fixtures = $this->fixtures();
$this->fixtureLoader->loadFixtures();
}
public function _after(\FunctionalTester $I)
{
$this->fixtureLoader->unloadFixtures();
}
}
class UserCest extends ApiCest
{
protected function fixtures()
{
return [
'users' => UserFixture::className(),
];
}
public function testSomething(\FunctionalTester $I)
{
$I->sendGET('/user');
$I->seeResponseCodeIs(200);
}
}

Categories