Behavior-Driven-Development is failing my expectations with PHPSpec - php

I have the following class generated through PHPSpec:
class Consumer
{
public function __construct($accesskey, $accessToken)
{
// TODO: write logic here
}
}
When I test the constructor I get an error that it is missing Argument 1. Below is how I have written the behavior:
namespace spec\Zizy\Aggregator\Context;
use Zizy\Aggregator\Context\Contract\ContextContractInterface;
use Zizy\Aggregator\Context\Consumer;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ConsumerSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->beConstructedWith( md5('samplekey'), md5('sampletoken') );
$this->shouldHaveType(Consumer::class);
}
/**
* This spec describes how we would access our consumer directry
*/
public function it_gets_access_token()
{
$this->getAccessToken()->shouldReturn(md5('sampletoken'));
}
}
Below is the error I get when running PHPSpec.
Zizy\Aggregator\Context\Consumer 21 - it gets access token
warning: Missing argument 1 for Zizy\Aggregator\Context\Consumer::__construct() in C:\wamp64\www\spikes\src\Context\Consumer.php line 7
I have also tried to test my consumer through an interface but PHPSpec keeps telling me that it cannot find the interface but in a class context thus offer me an opportunity to create the class meanwhile it should actually be an interface.
How can I also write code through interfaces with PHPSpec?

You'll need to specify the constructor arguments for every example case. If you find that a bit too laborious, you can use let to make preparations for before each example is run. For your case, something like this should work:
namespace spec\Zizy\Aggregator\Context;
use Zizy\Aggregator\Context\Contract\ContextContractInterface;
use Zizy\Aggregator\Context\Consumer;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ConsumerSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith( md5('samplekey'), md5('sampletoken') );
}
function it_is_initializable()
{
$this->shouldHaveType(Consumer::class);
}
/**
* This spec describes how we would access our consumer directry
*/
public function it_gets_access_token()
{
$this->getAccessToken()->shouldReturn(md5('sampletoken'));
}
}

Related

Why's my interface not instantiable even though I have what appears to be the correct files/implementation?

I'm not sure why I'm getting this error that says Illuminate\Contracts\Container\BindingResolutionException: Target [App\Dal\Interfaces\IUploadsRepository] is not instantiable while building [App\Http\Controllers\FileUploadController]. in file /var/www/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 1093 despite having (to my knowledge) everything set up correctly. The spelling and everything else is correct but I'm still not sure what the issue is.
I've tried everything under the sun to make this work but I'm not sure what I'm doing wrong.
What am I missing?
Note: I need to declare the UploadsRepository.php class as abstract because if I don't, then I get a red squiggly line underneath the class name with a warning that says:
Class must be declared abstract or implement methods 'resetScope', 'hidden', 'syncWithoutDetaching', 'update', 'paginate', 'delete', 'findWhereBetween', 'whereHas', 'withCount', 'find', 'getFieldsSearchable', 'create', 'findWhereNotIn', 'setPresenter', 'skipPresenter', 'all', '__callStatic', 'findWhere', 'visible', 'simplePaginate', 'firstOrNew', 'orderBy', 'sync', 'scopeQuery', 'findWhereIn', 'findByField', 'with', 'lists', 'firstOrCreate', 'updateOrCreate', '__call', 'pluck'
I'm not sure if this is the root of the issue but just want to provide as much info as I possibly can.
Here's FileUploadController.php:
<?php
namespace App\Http\Controllers;
use App\Dal\Interfaces\IUploadsRepository;
Use App\Dal\Repositories\UploadsRepository;
class FileUploadController extends Controller
{
protected $__uploadsRepository;
public function __construct(IUploadsRepository $uploadsRepository)
{
$this->__uploadsRepository = $uploadsRepository;
}
public function getUploads(): string
{
return $this->__uploadsRepository->getUploads();
}
}
Here's IUploadsRepository.php (interface):
<?php
namespace App\Dal\Interfaces;
use Prettus\Repository\Contracts\RepositoryInterface;
interface IUploadsRepository extends RepositoryInterface
{
public function getUploads();
}
Here's UploadsRepository.php:
<?php
namespace App\Dal\Repositories;
use App\Dal\Interfaces\IUploadsRepository;
abstract class UploadsRepository implements IUploadsRepository
{
/**
* #return string
*/
public function getUploads(): string
{
return "test";
}
}
Here's RepositoryServiceProvider.php:
<?php
namespace App\Providers;
use App\Dal\Interfaces\IUploadsRepository;
use App\Dal\Repositories\UploadsRepository;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
public function register() {
$this->app->bind(IUploadsRepository::class,UploadsRepository::class);
}
}
Here's config/app.php:
'providers' => [
Prettus\Repository\Providers\RepositoryServiceProvider::class,
\App\Providers\RepositoryServiceProvider::class,
]
The RepositoryInterface you are extending defines all those methods you see in the error: "'resetScope', 'hidden', 'syncWithoutDetaching', 'update'...". Your concrete implementation UploadsRepository is only implementing getUploads(). To fulfill the contract defined by the RepositoryInterface your concrete implementation needs to implement also the other methods. My recommendation would be to rather than implementing that interface, have your UploadsRepository extends from the Prettus\Repository\Eloquent\BaseRepository class which offers a default implementation for those methods. You can declare like this.
First IUploadsRepository does not need to extend RepositoryInterface, so the declaration would be:
<?php
namespace App\Dal\Interfaces;
interface IUploadsRepository
{
public function getUploads();
}
And the concrete implementation then goes like this:
<?php
namespace App\Dal\Repositories;
use App\Dal\Interfaces\IUploadsRepository;
use Prettus\Repository\Eloquent\BaseRepository
class UploadsRepository extends BaseRepository implements IUploadsRepository
{
/**
* #return string
*/
public function getUploads(): string
{
return "test";
}
}
You now have a default implementation for the missing methods and can use IUploadsRepository interface to handle the dependency injection via the RepositoryServiceProvider.

Can I implement an interface and override the return type of one of the interface's methods?

I have a Task class that extends an abstract class, TaskBase.
// class Task
class Task extends TaskBase {
/**
* #var Processor
*/
private $processor
public function __construct(Processor $processor)
{
$this->processor = $processor;
}
public function process() : ProcessResult
{
return $this->processor->execute();
}
}
// abstract class TaskBase
abstract class TaskBase implements CommanTask {
protected function getKey(): string
{
}
}
This TaskBase implements an interface CommanTask, which contains the following method.
interface CommanTask {
public function process(): ProcessResult;
}
Now I need a new task class TaskMultiple, and it's process() method needs to return an array of ProcessResult instead of one ProcessResult.
How can I extend abstract class TaskBase for this new TaskMultiple class?
If you implement an interface, you need to actually comply with the contract laid out by the interface.
The interface you propose says that process() returns a ProcessResult. If you could change that in an implementing class to returning an array, then consumers of the class wouldn't be able to trust the contract specified by the interface.
They would try to use the result of TaskMultiple::process(), and since they interface says it would return a ProcessResult, a fatal error would soon happen when it tried to treat it as such (e.g. by accessing a method for that class), and it wasn't that.
Your solutions are:
If you are on PHP 8, you could use union types:
interface Task {
public function process(): ProcessResult|iterable;
}
It would work, but it's ugly. Now consumers of the any Task implementing service would need to check on the result of process() to see if it's a single ProcessResult, or a collection (presumably of ProcessResults).
If you are on PHP < 8, you could make it work simply by removing the type hint:
interface Task {
public function process()
}
Now consumers would only know that there is a process() method, but you'd have no type information at all. Uglier still.
Better, simply create different interfaces like Task and MultipleTask:
interface Task {
public function process(): ProcessResult;
}
interface MultipleTask {
public function process(): iterable;
}
Since your BaseTask includes nothing to help implement the class (only includes an abstract method, making it already very similar to an interface), move the implements to each of the concrete task classes:
class ExampleTask extends BaseTask implements Task
{ /** implementation **/ }
class ExampleMultipleTask extends BaseTask implements MultipleTask
{ /** implementation **/ }

Testing Symfony custom maker (maker bundle)

I'm trying to make a custom maker with the Symfony make bundle.
The maker command looks like this:
<?php
namespace App\Maker;
use Doctrine\Common\Annotations\Annotation;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
final class MakeCustomEntity extends AbstractMaker
{
public static function getCommandName(): string
{
return 'make:custom-entity';
}
public static function getCommandDescription(): string
{
return 'Creates a new entity';
}
public function configureCommand(Command $command, InputConfiguration $inputConf)
{
$command
->addArgument('entity-class', InputArgument::OPTIONAL, sprintf('Choose a name for your entity class (e.g. <fg=yellow>%s</>)', Str::asClassName(Str::getRandomTerm())));
}
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
{
}
public function configureDependencies(DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(
Annotation::class,
'doctrine/annotations'
);
}
}
So far so good, the custom maker shows up when listing all commands.
However I would like to write a test for this maker (inspired from the tests I have found on the bundles github):
<?php
namespace Tests\Maker;
use App\Maker\MakeCustomEntity;
use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
use Symfony\Bundle\MakerBundle\Test\MakerTestDetails;
class MakeCustomEntityTest extends MakerTestCase
{
public function getTestDetails()
{
yield 'entity_full_custom_namespace' => [
MakerTestDetails::createTest(
$this->getMakerInstance(MakeCustomEntity::class),
[
// entity class name
'\App\Domain\Entity\Test\Test',
]
)
->assert(function (string $output, string $directory) {
$this->assertStringContainsString('created: src/Domain/Entity/Test/Test.php', $output);
}),
];
}
}
When I try to run this test I get the following warning and test doesn't fail even though it should:
The data provider specified for Tests\Maker\MakeCustomEntityTest::testExecute is invalid.
You have requested a non-existent service "maker.maker.make_custom_entity". Did you mean one of these: "maker.maker.make_authenticator",...
Is this the correct way to testing custom makers? What should I do to avoid this?

Dependency Injection of Interface

Hi I'm getting an error of should be an instance of interface
App\Repositories\Traits\PhotoService::__construct() must be an instance of AwsServiceInterface, instance
Here's what I have so far
namespace App\Repositories\Interfaces;
interface AwsServiceInterface
{
//...
}
Now I have this class
namespace App\Repositories\Interfaces;
use App\Repositories\Interfaces\AwsServiceInterface;
class CloudFrontService implements AwsServiceInterface
{
public function __construct()
{
}
}
Now I'm using a dependency injection on this class
namespace App\Repositories\Traits;
use App\Repositories\Interfaces\AwsServiceInterface;
class PhotoService
{
protected $service;
public function __construct(AwsServiceInterface $service)
{
$this->service = $service;
}
public function getAuthParams($resource, $search_key = '')
{
// Execute a function from a class that implements AwsServiceInterface
}
And I'm calling the PhotoService class like this
$photo_service = new PhotoService(new CloudFrontService());
echo $photo_service->getAuthParams($resource);
But somehow I'm getting this error
FatalThrowableError: Type error: Argument 1 passed to App\Repositories\Traits\PhotoService::__construct() must be an instance of AwsServiceInterface, instance of App\Repositories\Interfaces\CloudFrontService given
In your App\Providers\AppServiceProvider class add following code in register() method:
$this->app->bind(
'App\Repositories\Interfaces\AwsServiceInterface',
'App\Repositories\Interfaces\CloudFrontService'
);
and then you can use it as:
$photo_service = app(PhotoService::class);
echo $photo_service->getAuthParams($resource);
You are having a problem with namespacing. The typehint that you are using isn't complete for what you are looking for.
Just guessing but I think that you want to change the typehint to be:
public function __construct(App\Repositories\Interfaces\AwsServiceInterface $service)
http://php.net/manual/en/language.namespaces.basics.php
You need to bind implementation to that interface. Here's an example.
Laravel itself does not know what implementation you want to use for this interface, you need to specify that yourself.
I solved my problem. For those of you having the same issue make sure you do this step.
1. as mentioned by #Amit double check on the service provider that the service is bind under register function
2. Make sure to do
php artisan clear-compile
php artisan cache:clear
php artisan config:clear

Unable to use helper in controller of laravel app

I'm building an application, now i'm created a helper
class Students{
public static function return_student_names()
{
$_only_student_first_name = array('a','b','c');
return $_only_student_first_name;
}
}
now i'm unable to do something like this in controller
namespace App\Http\Controllers;
class WelcomeController extends Controller
{
public function index()
{
return view('student/homepage');
}
public function StudentData($first_name = null)
{
/* ********** unable to perform this action *********/
$students = Student::return_student_names();
/* ********** unable to perform this action *********/
}
}
this is my helper service provider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class HelperServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
foreach(glob(app_path().'/Helpers/*.php') as $filename){
require_once($filename);
}
}
}
i event added it as an alias in config/app.php file
'Student' => App\Helpers\Students::class,
Try putting use App\Helpers\Student; at the top of your controller beneath the namespace delcaration:
namespace App\Http\Controllers;
use App\Helpers\Student;
class WelcomeController extends Controller
{
// ...
Look more into PHP namespaces and how they are used, I believe you may have a deficient understanding about them. Their only purpose is to make so you can name and use two classes with the same name (e.g. App\Helpers\Student vs maybe App\Models\Student). If you needed to use both of those classes inside of the same source file, you can alias one of them like this:
use App\Helpers\Student;
use App\Models\Student as StudentModel;
// Will create an instance of App\Helpers\Student
$student = new Student();
// Will create an instance of App\Models\Student
$student2 = new StudentModel();
You do not need to have a service provider for this, just the normal language features. What you would need a service provider for is if you wanted to defer the construction of your Student object to the IoC:
public function register()
{
$app->bind('App\Helpers\Student', function() {
return new \App\Helpers\Student;
});
}
// ...
$student = app()->make('App\Helpers\Student');
You should never have to include or require a class file in laravel because that is one of the functions that composer provides.
You do not need a service provider to make it works. Just lets the Students class as you did:
class Students{
public static function return_student_names()
{
$_only_student_first_name = array('a','b','c');
return $_only_student_first_name;
}
}
all its methods should be static
You added the Facade correctly:
'Student' => App\Helpers\Students::class,
Finally, looks like your problem is caused by forgetting a backslash at facade name. Uses \Students instead of Students:
public function StudentData($first_name = null)
{
$students = \Student::return_student_names();
}
When using a facade, it is not necessary makes nay include, the facades were made to avoid complex includes in everywhere.

Categories