Service injection in a Symfony controller fails - php

I am adding a feature to a Symfony 2.7 application. I have a controller that has been defined as a service, and it in turn takes in another service. Here are the relevant contents from my services.yml file:
app.service.video_derivative:
class: MyCompany\AppBundle\Service\VideoDerivativeService
arguments:
- "#app.repository.video_derivative"
- "#doctrine.orm.default_entity_manager"
api.controller.video_derivative:
class: MyCompany\AppBundle\Controller\VideoDerivativeController
arguments:
- "#app.service.video_derivative"
... and the relevant code in my controller looks like this:
public function __construct(VideoDerivativeServiceInterface $videoDerivativeService)
{
$this->videoDerivativeService = $videoDerivativeService;
}
/**
* #param $id
* #return JsonResponse
*
* #Route("/admin/video-derivative/create-by-clip-id/{id}", name="create_clip_by_id")
*/
public function byClipIdAction($id)
{
$responseArray = [
'foo' => 'bar',
'baz' => 'qux',
];
return new JsonResponse($responseArray);
}
... but when I pull up my controller in a browser, I get the following message:
Catchable Fatal Error: Argument 1 passed to
MyCompany\AppBundle\Controller\VideoDerivativeController::__construct()
must be an instance of
MyCompany\AppBundle\Service\VideoDerivativeServiceInterface, none given,
called in /usr/src/app/app/cache/dev/classes.php on line 2200 and
defined
... so it looks like I'm doing something wrong. How would you go about debugging this?

It turned out that what I needed was this annotation over the class:
/**
* #\Sensio\Bundle\FrameworkExtraBundle\Configuration\Route(service="api.controller.video_derivative")
*/
Once that was in place, I got a much more helpful error and was able to move on with development.

Related

__construct function not constructing correctly in the container symfony 3.4

Hello all i got a a report-bundle and a service AdminUsersStatsListBlockService that need a UserRepository.php within the report bundle to access function, i tried to add the BookingBundle.php within the report bundle to the construct function but i keep constructing without it here's my code and my errors:
HERE AdminUsersStatsListBlockService.php (so i tried to add the BookingRepository here):
<?php
/*
* This file is part of the Cocorico package.
*
* (c) Cocolabs SAS <contact#cocolabs.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Cocorico\ReportBundle\Block\Service;
use Cocorico\BookingBundle\Entity\Booking;
use Cocorico\ReportBundle\Repository\UserRepository;
use Cocorico\ReportBundle\Repository\BookingRepository; /*(added)*/
use Sonata\AdminBundle\Admin\Pool;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\Service\AbstractBlockService;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdminUsersStatsListBlockService extends AbstractBlockService
{
protected $userRepository;
protected $bookingRepository;/*(added)*/
protected $adminPool;
/**
* #param string $name
* #param EngineInterface $templating
* #param UserRepository $userRepository
* #param Pool $adminPool
* #param BookingRepository $bookingRepository/*(added)*/
*/
public function __construct(
$name,
EngineInterface $templating,
UserRepository $userRepository,
Pool $adminPool = null,
BookingRepository $bookingRepository/*(added)*/
) {
parent::__construct($name, $templating);
$this->userRepository = $userRepository;
$this->bookingRepository = $bookingRepository;/*(added)*/
$this->adminPool = $adminPool;
}
/**
* {#inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$stat = $blockContext->getSetting('stat');
switch ($stat) {
case 'offerers-expiring':
$results = $this->userRepository->getTopOfferersWithBookingsStatusCount(
Booking::STATUS_EXPIRED,
null,
null,
$blockContext->getSetting('limit')
);
break;
case 'offerers-refusing':
$results = $this->userRepository->getTopOfferersWithBookingsStatusCount(
Booking::STATUS_REFUSED,
null,
null,
$blockContext->getSetting('limit')
);
break;
case 'offerers-accepting':
$results = $this->userRepository->getTopOfferersWithBookingsStatusCount(
Booking::STATUS_PAYED,
null,
null,
$blockContext->getSetting('limit')
);
break;
case 'bookings-expired-list':
$results = $this->bookingRepository->getBookingsExpired(); /*there i want to use it*/
break;
default:
$results = array();
}
return $this->renderResponse(
$blockContext->getTemplate(),
array(
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
'results' => $results,
'admin_pool' => $this->adminPool,
),
$response
);
}
}
Here's the error i get:
request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalThrowableError: "Type error: Too few arguments to function Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService::__construct(), 4 passed in /var/www/Symfony/var/cache/prod/Container7aqlalh/getCocoricoReport_Admin_Block_Users_StatsListService.php on line 13 and exactly 5 expected" at /var/www/Symfony/vendor/cocorico/report-bundle/Block/Service/AdminUsersStatsListBlockService.php line 40 {"exception":"[object] (Symfony\Component\Debug\Exception\FatalThrowableError(code: 0): Type error: Too few arguments to function Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService::__construct(), 4 passed in /var/www/Symfony/var/cache/prod/Container7aqlalh/getCocoricoReport_Admin_Block_Users_StatsListService.php on line 13 and exactly 5 expected at /var/www/Symfony/vendor/cocorico/report-bundle/Block/Service/AdminUsersStatsListBlockService.php:40)"} []
and the container still not constructing with the BookingRepository:
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the public 'cocorico_report.admin.block.users.stats_list' shared service.
include_once $this->targetDirs[3].'/vendor/sonata-project/block-bundle/src/Block/BlockServiceInterface.php';
include_once $this->targetDirs[3].'/vendor/sonata-project/block-bundle/src/Block/Service/BlockServiceInterface.php';
include_once $this->targetDirs[3].'/vendor/sonata-project/block-bundle/src/Block/Service/AbstractBlockService.php';
include_once $this->targetDirs[3].'/vendor/cocorico/report-bundle/Block/Service/AdminUsersStatsListBlockService.php';
return $this->services['cocorico_report.admin.block.users.stats_list'] = new \Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService('cocorico_report.admin.block.users.stats_list', ${($_ = isset($this->services['templating']) ? $this->services['templating'] : $this->load('getTemplatingService.php')) && false ?: '_'}, ${($_ = isset($this->services['cocorico_report.user.repository']) ? $this->services['cocorico_report.user.repository'] : $this->load('getCocoricoReport_User_RepositoryService.php')) && false ?: '_'}, ${($_ = isset($this->services['sonata.admin.pool']) ? $this->services['sonata.admin.pool'] : $this->getSonata_Admin_PoolService()) && false ?: '_'});
Edit: found this, is this the loader ? :
services:
cocorico_report.admin.block.stats:
class: Cocorico\ReportBundle\Block\Service\AdminStatsBlockService
arguments:
- "cocorico_report.admin.block.stats"
- "#templating"
- "#cocorico_report.report.manager"
tags:
- { name: sonata.block }
cocorico_report.admin.block.users.stats_list:
class: Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService
arguments:
- "cocorico_report.admin.block.users.stats_list"
- "#templating"
- "#cocorico_report.user.repository"
- "#sonata.admin.pool"
tags:
- { name: sonata.block }
Thanks in advance for help!:)
So all you need to do is to add the booking repository to the service definition.
# ReportBundle/Resources/services.yml
services:
cocorico_report.admin.block.users.stats_list:
class: Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService
arguments:
- "cocorico_report.admin.block.users.stats_list"
- "#templating"
- "#cocorico_report.user.repository"
- "#sonata.admin.pool"
- "#cocorico_report.booking.repository" # ADD THIS #
tags:
- { name: sonata.block }
The assumption here is that you also have the cocorico_report.booking.repository service already defined. If the repository is something you added then you will have to find the user repository service definition and basically clone it.
There is a lot more information available on how to explicitly configure services . Just try to avoid confusing it with the autowire approach.

Laravel Trait not found PHP Fatal Error

I am working on a project which has Laravel 5.3 version and using Laravel Infyom Generator, somehow it generated all these traits and other test files such as (ApiTest, RepositoryTest, ...etc). when I try to run PHPUNIT I am getting this error Can someone help me to find out why I am getting this error?
PHP Fatal error: Trait 'MakeCustomerTrait' not found in C:\Users\ahmed\dev\gamla\tests\CustomerApiTest.php on line 8
Fatal error: Trait 'MakeCustomerTrait' not found in C:\Users\ahmed\dev\gamla\tests\CustomerApiTest.php on line 8
I want to start making new tests for my project do I need to delete these files? because it keeps giving me that error?
screenshot of CustomerApiTest code:
MakeCustomerTrait
<?php
use Faker\Factory as Faker;
use App\Models\Customer;
use App\Repositories\CustomerRepository;
trait MakeCustomerTrait
{
/**
* Create fake instance of Customer and save it in database
*
* #param array $customerFields
* #return Customer
*/
public function makeCustomer($customerFields = [])
{
/** #var CustomerRepository $customerRepo */
$customerRepo = App::make(CustomerRepository::class);
$theme = $this->fakeCustomerData($customerFields);
return $customerRepo->create($theme);
}
/**
* Get fake instance of Customer
*
* #param array $customerFields
* #return Customer
*/
public function fakeCustomer($customerFields = [])
{
return new Customer($this->fakeCustomerData($customerFields));
}
/**
* Get fake data of Customer
*
* #param array $postFields
* #return array
*/
public function fakeCustomerData($customerFields = [])
{
$fake = Faker::create();
return array_merge([
'name' => $fake->word,
'address_street' => $fake->word,
'address_zip' => $fake->word,
'address_city' => $fake->word,
'address_country' => $fake->word,
'shipping_address_street' => $fake->word,
'shipping_address_zip' => $fake->word,
'shipping_address_city' => $fake->word,
'shipping_address_country' => $fake->word,
'contact_person_id' => $fake->randomDigitNotNull,
'created_at' => $fake->word,
'updated_at' => $fake->word
], $customerFields);
}
}
Actually what is happening is here is autoloader is unable to resolve the class in your code. Laravel uses PSR-4 standards to autoload classes which needs fully qualified name spaces for the class which also represents the path of the file which holds the class.
See it this way that if you want to load a class inside your Laravel app directory at the following address :
app/repositories/users/UserRoleRepository.php
You will specify the namespace of your class like App\repositories\users with class name UserRoleRepository so the autoloader can load your class. Otherwise you have to include the file of your class manually.
You can register custom autoloads in your composer.json and by running the following command composer dump-autoload.
You can find more about it over internet like this
Hope it would help.

Override FOS Rest Bundle response

I'm using FOS Rest Bundle for my RESTful API, I want to override the responses returned from my ApiController, example:
php
/**
* #QueryParam(name="sourceAddr", strict=true, requirements="[0-9]{8}", description="Do things")
* #param $paramFetcher ParamFetcher
* #return array
* #throws MtBoxApiException
*/
public function getAuthAction(ParamFetcher $paramFetcher)
{
return [
'rfid' => '445545454',
'fName' => 'adazda',
'lName' => '8888888',
'prod' => 75
];
}
What I want is adding additional data to the returned responses, so I want to intercept these responses and override them based on some conditions.
The final result that I want the api returns:
{
someAdditionalData1: value1,
someAdditionalData2: value2,
data: {
//the data returned by the controller action
}
}
For an idea how this is done you can look at the FOS\RestBundle\EventListener\ViewResponseListener class. They register this class as an event subscriber. You can register your class in the same way in your services.yml
test_response_listener:
class: MyBundle\EventListener\MyViewResponseListener
tags:
- { name: kernel.event_subscriber }
You need to ensure your class implements Symfony\Component\EventDispatcher\EventSubscriberInterface and contains the method getSubscribedEvents like this:
public static function getSubscribedEvents()
{
return array(
KernelEvents::VIEW => array('onKernelView', 50),
);
}
The event is 'onKernelView', i.e. when a view is returned. This will only be called if the response from the controller is not actually a Response object. In my test of this I returned a User object so it was called. The "50" represents the priority. If you don't put it higher than 30 then the FOSRestBundle listener will be called first and will set the response. If any of these listeners call $event->setResponse then the other ones are ignored so make sure you don't do it in your method or the FOSRest one won't be called.
The onKernelView is the name of the method to be called. It will receive a certain type of event so make your method signature like this:
public function onKernelView(GetResponseForControllerResultEvent $event)
Now, finally, what you want to do is unify the response format. You can do this by changing the controller result of the event inside your listener method to match the format you want:
$event->setControllerResult([
'foo' => 'bar',
'data' => $event->getControllerResult()
]);
If you have the serializer set up it should still serialize your controller result as normal, but you'll get the added data in the response.

Disable Log::info in production using Laravel

I assumed that by default the Log::info calls wouldn't log in production, but they are still coming in.
Im setting production using my .env file
APP_ENV=production
APP_DEBUG=false
Ive tried these commands as well, but no luck
composer dump-autoload
php artisan cache:clear
php artisan optimize
Am i missing something?
For anyone still finding this thread (8 years later):
Configure your log channels in config/logging.php file
Set "level" parameter for your log channel to a .env variable
Example:
'channels' => [
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Lumen Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'error'),
]
]
Now you can set the LOG_LEVEL variable in your .env file for each environment
Well, I think that it's too late to search for all the Log::info() and do the proposed answer by #jon__o
if (App::environment('local', 'staging')) {
Log::info($error);
}
But you can still do something. You can override the default Laravel logger instance with your own implementation.
Go to your ApplicationServiceProvider and override the log instance with a custom one:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerLogger();
}
/**
* Register the logger instance in the container.
*
* #return MyCustomWriter
*/
protected function registerLogger()
{
$this->app->instance('log', $log = new MyCustomWriter(
new Monolog($this->app->environment()), $app['events'])
);
$log->dontLogInfoOnEnvironmnets(['production', 'staging', 'other']);
return $log;
}
Now you can create your custom writer by just extending the Laravel's Writer and overriding the info() method.
class MyCustomWriter extends \Illuminate\Log\Writer
{
protected $dontInfoOn = [];
/**
* Log an informational message to the logs.
*
* #param string $message
* #param array $context
* #return void
*/
public function info($message, array $context = [])
{
// Since we are providing the app environment to the Monolog instance in out ApplicationServiceProvider
// we can get the environment from the Monolog getName() method
if(!in_array($this->monolog->getName(), $this->dontInfoOn)) {
return parent::info($message, $context);
}
}
/**
* Don't log info() on the supplied environments .
*
* #param array $environments
* #return void
*/
public function dontLogInfoOnEnvironmnets(array $environments)
{
$this->dontInfoOn = $environments;
}
}
This way, you can still keep you Log::info on testing environments without checking every time.
Only the displaying of errors will be suppressed when your application is not in debug mode. The Log::info() function will always log when called.
The simple solution is for you to wrap that Log::info() function in something like this:
if (App::environment('local', 'staging')) {
Log::info($error);
}
Be sure to include the App facade use App; at the top of your file. Alternatively you can use the app() helper to get the environment: $environment = app()->environment();.

Why PHPUnit does not assert correctly a request with an unvalid method?

what I am trying to do is asserting a request with an unvalid method.
What I testing is a Guest attempting to logging out as GET (unvalid).
This is the code:
/**
* Enables router filters.
*
* #return void
*/
public function enableRouterFilters()
{
$this->app['router']->enableFilters();
}
[...]
/**
* Tests users/logout route as a Guest with GET.
*
* #return void
*/
public function testRouteToUsersLogoutAsGuestWithGetMethod()
{
$this->enableRouterFilters();
$response = $this->call('GET', '/users/logout');
$this->assertResponseStatus(405);
}
[...]
Instead of asserting true, PHPUnit complains about it:
[...]
There was 1 error:
1) RoutesTest::testRouteToUsersLogoutAsGuestWithGetMethod
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException:
[...]
FAILURES!
Tests: 7, Assertions: 11, Errors: 1.
Why it gives me an error if the assertion should be indeed true?
The exception being thrown is "MethodNotAllowedHttpException". This means that you do not have a route set up to catch requests to this particular URL & request type (GET).
What you are actually looking to do is tell PHPUnit that you are expecting a certain exception be thrown, in this case: Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
Try annotating your method with:
/**
* #expectsException Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
**/
For full docs: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.exceptions.examples.ExceptionTest.php

Categories