I'm developing a project using Symfony 2.1.
I have created a service that is being called from a controller, and it's working ok.
Now I need that the service generates log, and I'm trying to pass logger this way:
soap.client:
class: MyFirm\MyAppBundle\Service\SOAPClient
arguments:
logger: "#logger"
My service is defined this way:
namespace MyFirm\MyAppBundle\Service;
use \SoapClient as SoapClient;
use Monolog\Logger;
class SOAPClient
{
private $logger;
function __construct (Logger $logger)
{
$this->logger = $logger;
}
function sendMessage ($message, $wsdl_url)
{
$webServResult = "ERROR";
try{
$client = new SoapClient($wsdl_url, array("trace"=>true,
"exceptions"=>true));
$webServResult=$client->sendMessage($data);
}
catch(\Exception $ex){
$webServResult="ERROR";
$message=$ex->getMessage();
$log_text = print_r($ex, true)."\n".
$client->__getLastRequest()."\n".
$client->__getLastResponse();
$this->logger->err("ERROR: ".$log_text);
}
return $webServResult;
}
}
However, when I use the logger (if the wsdl doesn't exist, for example), the application hangs.
Am I doing anything wrong? Thanks a lot.
This is not a problzm from logger but from SoapClient that doesn't throw any Exception in case of unreachable WSDL... It waits until a Fatal error is thrown...
You have to check if WSDl exists before calling SoapClient ;)
Sorry, the problem wasn't at logger, but in the lines:
$log_text = print_r($ex, true)."\n".
$client->__getLastRequest()."\n".
$client->__getLastResponse();
If I remove these lines and log only the Exception message all goes right.
SoapClient does generate an Exception if the WSDL is not found:
SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost/unknown.wsdl' :
failed to load external entity "http://localhost/unknown.wsdl"
Solved and fixed. Sorry for the spam.
Related
I've recently started learning Symfony, and I've been trying to make an app that will redirect user to the homepage after encountering an error (For the sake of the question, it can be error 404) However, I had problems with finding a way to do so.
Before, I used TwigErrorRenderer as described in Symfony documentation to handle my errors, but it only explains how to redirect to new error pages created by myself. Could somebody help me with this issue?
It is generally not a good idea to do this, because you want to tell the user that their request was not processed due to an error, or that they accessed non-existing page.
But if you really want to, you can achieve it with this Event Listener.
// src/EventListener/ExceptionListener.php
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\Routing\RouterInterface;
final class ExceptionListener
{
private RouterInterface $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function onKernelException(ExceptionEvent $event): void
{
// You should log the exception via Logger
// You can access exception object via $event->getThrowable();
$homepageRoute = $this->router->generate('homepage', [], RouterInterface::ABSOLUTE_URL);
$response = new RedirectResponse($homepageRoute);
$event->setResponse($response);
}
}
You also need to register the Event Listener in your services.yaml.
services:
App\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
Please note the following:
The Event Listener assumes that your Homepage route is called homepage;
you really should log the exception or you will lose logs about all of them;
as stated at the top of this answer, this is not a good approach to deal with exceptions.
I my contoller I try to clear the cache when updating a page
protected function mapDataToEntity(array $data, Project $entity): void{
$entity->setName($data['name']);
$cacheManager = $this->get('sulu_http_cache.cache_manager');
$cacheManager->invalidatePath($path, $headers);
}
I get the error message:
Attempted to call an undefined method named "get" of class
"App\Controller\Admin\ProjectController". Did you mean to call e.g.
"cgetAction", "getAction", "getLocale" or "getSecurityContext"?
You should autowire your cache manager instead of trying to access it from the container.
private CacheManager $cacheManager;
public function __construct(CacheManager $cacheManager)
{
$this->cacheManager = $cacheManager;
}
And use it in your method:
$this->cacheManager->invalidatePath($path, $headers);
I'm new to Symfony, and I'm working on a first project, using Symfony 4.1. I have a home page configured at '/', and that's working. The point of this project is to create a REST API, and I have a route defined at /api/argument/, and that's working fine, too.
However, when I navigate to /api, or any other route with something in the path (e.g. /car, /apple, /something/else), I get a 404 in the HTTP response, but the page displays a backtrace of the exception.
Sorry, the page you are looking for could not be found.
NotFoundHttpException
No route found for "GET /oijoij"
in RouterListener.php line 139
at RouterListener->onKernelRequest(object(GetResponseEvent), 'kernel.request', object(EventDispatcher))in EventDispatcher.php line 212
...
So then in .env I set
APP_ENV=test
APP_DEBUG=0
And now, instead of a pretty-printed HTML backtrace, I get just a plain-text error message
Fatal error: Uncaught Symfony\Component\Routing\Exception\ResourceNotFoundException in www\project\var\cache\test\srcTestProjectContainerUrlMatcher.php:50 Stack trace: ...
How do I configure this so that undefined routes return a 404 with the 404 template page, without a backtrace?
I'm also throwing a BadRequestHttpException in my API controllers, and that exception just gets dumped on the page too. Does this get solved by the same method?
As many people have pointed out, the error pages are generated for the dev and test environment only. In the prod environment, which you should use on your live system, will display a generic 404 page, which you can customize.
The system responsible for showing either the detailed error page in development or the regular 404 page in production is Symfony's event cycle, more specifically the kernel.exception event, which is listened to and then any uncaught errors and exceptions will be converted into an error page-response. Since you are writing an API you might want to register your own listener and return a JSON response instead of regular HTML.
An event subscriber for this could look something like this:
<?php declare(strict_types = 1);
namespace App\Api\Response;
use Exception;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
final class ExceptionToJsonResponseSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
public function onKernelException(GetResponseForExceptionEvent $event): void
{
// Skip if request is not an API-request
$request = $event->getRequest();
if (strpos($request->getPathInfo(), '/api') !== 0) {
return;
}
$exception = $event->getException();
$error = [
'type' => $this->getErrorTypeFromException($exception),
// Warning! Passing the exception message without checks is insecure.
// This will potentially leak sensitive information.
// Do not use this in production!
'message' => $exception->getMessage(),
];
$response = new JsonResponse($error, $this->getStatusCodeFromException($exception));
$event->setResponse($response);
}
private function getStatusCodeFromException(Exception $exception): int
{
if ($exception instanceof HttpException) {
return $exception->getStatusCode();
}
return 500;
}
private function getErrorTypeFromException(Exception $exception): string
{
$parts = explode('\\', get_class($exception));
return end($parts);
}
}
This will convert any exception into a JSON-response with a custom format similar to this:
{
"type": "NotFoundException",
"message": "Could not find argument with id x"
}
This listener will only do this for routes that start with /api so if you have both an API and a "regular" site it should not interfere with the default error handling.
I am starting to work with services in Symfony and therefore created the example service from the symfony documentation:
namespace AppBundle\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger){
}
public function getMessage()
{
$this->logger->info('Success!');
}
}
I call that service in my controller (I also have the use Statement:
: use AppBundle\Service\MessageGenerator;
$messageGenerator = $this->get(MessageGenerator::class);
$message = $messageGenerator->getMessage();
$this->addFlash('success', $message);
My service is defined in the services.yml file:
app.message_generator:
class: AppBundle\Service\MessageGenerator
public: true
so in my eyes I did everything exactly as described in the documentation and when calling:
php app/console debug:container app.message_generator
in my commandline I get my service:
Option Value
------------------ ------------------------------------
Service ID app.message_generator
Class AppBundle\Service\MessageGenerator
Tags -
Scope container
Public yes
Synthetic no
Lazy no
Synchronized no
Abstract no
Autowired no
Autowiring Types -
Now when I execute the controller function where I call my service I still get the error:
You have requested a non-existent service "appbundle\service\messagegenerator".
Any ideas?
Symfony is a bit confusing at naming: you retrieve the service by requesting it by its defined name: app.message_generator.
$messageGenerator = $this->get('app.message_generator');
Symfony has recently suggested switching from a give-name (app.message_generator) that you are defining the service as, to the class name (AppBundle\Service\MessageGenerator). They are both just 'a name' to call the service.
You are trying to use both, when only the given name is defined.
In the long term, it's suggested to use the ::class based name, and quite possibly allow the framework to find the classes itself, and configure them itself too. This means that, by default, all services are private, and are handled by the framework & it's service container.
In the meantime, while you are learning, you can either:
$messageGenerator = $this->get('app.message_generator');
or define explicitly define the service, and make it public, so it can be fetched with ->get(...) from the container.
# services.yml
AppBundle\Service\MessageGenerator:
class: AppBundle\Service\MessageGenerator
public: true
# php controller
$messageGenerator = $this->get(MessageGenerator::class);
or just injected automatically into the controller, when that is requested
public function __construct(LoggerInterface $logger, MessageGenerator $msgGen)
{
$this->messageGenerator = $msgGen;
}
public function getMessage()
{
$result = $this->messageGenerator->do_things(....);
$this->logger->info('Success!');
}
I am trying to setup a Symfony implementation of this PHP library for Chargify https://github.com/johannez/chargify
I'm getting a bit lost working out the best / proper way to set it all up.
I think I need to setup Guzzle as a service, then create a Chargify factory and have that added as a service.
My problem is that in the factory class, when I try and use the Guzzle service I get a fatal error
Fatal error: Using $this when not in object context in /symfony/src/Acme/ChargifyBundle/Factory/ChargifyFactory.php on line 8
This is my Factory class
<?php
namespace Acme\ChargifyBundle\Factory;
class ChargifyFactory implements ChargifyFactoryInterface
{
public static function build($type)
{
$client = $this->get('chargify.guzzle.client');
$className = 'Chargify\\Controller\\' . ucfirst($type);
if (class_exists($className)) {
return new $className($client);
}
else {
throw new Exception("Invalid controller type given.");
}
}
}
If it's useful to see some config, this is my services.yml for the bundle
services:
chargify.guzzle.client.curl_auth:
class: %guzzle.plugin.curl_auth.class%
arguments:
api_key: %chargify_api_key%
chargify.guzzle.client:
class: %guzzle.client.class%
tags:
- { name: guzzle.client }
calls:
- [setBaseUrl, [%chargify_domain%]]
- [addSubscriber, [#chargify.guzzle.client.curl_auth]]
argument: %chargify_domain%
chargify.factory:
class: Acme\ChargifyBundle\Factory\ChargifyFactory
arguments:
- ["type"]
chargify.customer:
class: Acme\ChargifyBundle\Controller\CustomerController
factory_class: Acme\ChargifyBundle\Factory\ChargifyFactory
factory_method: build
arguments:
type: "customer"
How can I use the guzzle client in the Factory with out using
$client = $this->get('chargify.guzzle.client');
EDIT:
I have changed the code as per #alex's answer, but I'm still getting an error. I think this is because the function is static. I've looked though the documents, but I can't see where I can setup a factory without a static function, and when I get rid of static I get a different error.
Runtime Notice: Non-static method Acme\ChargifyBundle\Factory\ChargifyFactory::build() should not be called statically, assuming $this from incompatible context
That is being thrown from some generated code
protected function getChargify_CustomerService()
{
return $this->services['chargify.customer'] = \Acme\ChargifyBundle\Factory\ChargifyFactory::build('customer');
}