I don't understand Why I can't create custom services. I get errors with the both technic. I don't find anything about that in your doc.
# app/config/services.yml
services:
jdf.utils.phphelper:
class: JDF\Utils\PhpHelper
// src/JDF/Utils/PhpHelper.php
namespace JDF\Utils;
class PhpHelper
{
/**
* [pdebug description]
* #param string $var The string to beautiful show
* #param string $msg Description of the $var
* #param integer $displayNone
* #return echo pre print_r $var string
*/
public function pdebug ($var, $msg = '', $displayNone = 0) {
}
}
Case 1 : (Pass PhpHelper in the __construct function)
// src/JDF/CsvTreatmentBundle\Controller/ImportController
namespace JDF\CsvTreatmentBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use JDF\Utils\PhpHelper;
use Psr\Log\LoggerInterface;
/**
*
*/
class ImportController extends Controller {
function __construct(
PhpHelper $PhpHelper
) {
}
public function indexAction() {
//$test = $this->container->get('jdf.utils.phphelper');
return new Response('<hr>');
}
} /*End of class*/
Error 1 :
Catchable Fatal Error: Argument 1 passed to JDF\CsvTreatmentBundle\Controller\ImportController::__construct() must be an instance of JDF\Utils\PhpHelper, none given, called in C:\kitutilitaire\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Controller\ControllerResolver.php on line 202 and defined
500 Internal Server Error - ContextErrorException
Case 2 (just use get() controller method) :
// src/JDF/CsvTreatmentBundle\Controller/ImportController
namespace JDF\CsvTreatmentBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use JDF\Utils\PhpHelper;
use Psr\Log\LoggerInterface;
/**
*
*/
class ImportController extends Controller {
function __construct(
//PhpHelper $PhpHelper
// LoggerInterface $logger
) {
}
public function indexAction() {
$test = $this->container->get('jdf.utils.phphelper');
// $logger = $this->container->get('logger');
return new Response('<hr>');
}
} /*End of class*/
Error 2 :
Attempted to load class "PhpHelper" from namespace "JDF\Utils".
Did you forget a "use" statement for another namespace?
Stack Trace
in var\cache\dev\appDevDebugProjectContainer.php at line 3555 -
*/
protected function getJdf_Utils_PhphelperService()
{
return $this->services['jdf.utils.phphelper'] = new \JDF\Utils\PhpHelper();
}
/**
EDIT : composer.json autoload
"autoload": {
"psr-4": {
"AppBundle\\": "src/AppBundle/",
"JDF\\CsvTreatmentBundle\\": "src/JDF/CsvTreatmentBundle/",
"JDF\\Utils\\": "src/JDF/Utils/PhpHelper"
},
"classmap": ["app/AppKernel.php", "app/AppCache.php"]
},
Thank in advence for your help.
Controllers do not get any injection by default. They have $this->container always available to get to all your services.
So nothing more to do than:
class ImportController extends Controller {
public function indexAction() {
$test = $this->container->get('jdf.utils.phphelper');
// $logger = $this->container->get('logger');
return new Response('<hr>');
}
}
FYI: The cache file appDevDebugProjectContainer is auto generated and of no significance to your problem.
I've solved the problem with change my composer.json.
For can use $this->container->get('jdf.utils.phphelper'); the all good code is :
# app/config/services.yml
services:
jdf.utils.phphelper:
class: JDF\Utils\PhpHelper
// src/JDF/Utils/PhpHelper.php
namespace JDF\Utils;
class PhpHelper {}
// src/JDF/CsvTreatmentBundle\Controller/ImportController
namespace JDF\CsvTreatmentBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use JDF\Utils\PhpHelper;
/**
*
*/
class ImportController extends Controller {
public function indexAction() {
$test = $this->container->get('jdf.utils.phphelper');
return new Response('<hr>');
}
} /*End of class*/
AND IMPORTANT : The composer.json :
"autoload": {
"psr-4": {
"JDF\\CsvTreatmentBundle\\": "src/JDF/CsvTreatmentBundle/",
"JDF\\Utils\\": "src/JDF/Utils/"
},
"classmap": ["app/AppKernel.php", "app/AppCache.php"]
},
And the CLI command : php composer.phar dump-autoload
Thank to colburton for this time and interest at my issue.
Related
I am writing a Twig function in Symfony 4 but I cannot get it to work...
The extension class
<?php
namespace App\Twig;
use App\Utils\XXX;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class XXXExtension extends AbstractExtension
{
/**
* #return array|TwigFunction|TwigFunction[]
*/
public function getFunctions()
{
return new TwigFunction('showControllerName', [$this, 'showControllerName']);
}
public function showControllerName($sControllerPath)
{
return XXX::getControllerName($sControllerPath);
}
}
I have autowire set to true in services.yaml but just in case i tried with this also:
App\Twig\XXXExtension:
public: true
tags:
- { name: twig.extension }
usage in html.twig
{% set controllerName = showControllerName(app.request.get('_controller')) %}
and the response i get after this is:
HTTP 500 Internal Server Error
Unknown "showControllerName" function.
You need to return an array of functions, you are only returning one.
...
public function getFunctions()
{
return [
new TwigFunction('showControllerName', [$this, 'showControllerName']),
];
}
...
I would like to know if there is a magic method to use this scenario :
If I call a page via an AJAX request the controller returns a JSON object, otherwise it returns a view, i'm trying to do this on all my controllers without changin each method.
for example i know that i can do this :
if (Request::ajax()) return compact($object1, $object2);
else return view('template', compact($object, $object2));
but I have a lot of controllers/methods, and I prefer to change the basic behavior instead of spending my time to change all of them. any Idea ?
The easiest way would be to make a method that is shared between all of your controllers.
Example:
This is your controller class that all other controllers extend:
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
protected function makeResponse($template, $objects = [])
{
if (\Request::ajax()) {
return json_encode($objects);
}
return view($template, $objects);
}
}
And this is one of the controllers extending it:
<?php namespace App\Http\Controllers;
class MyController extends Controller
{
public function index()
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($template, compact($object, $object2));
}
}
Update for Laravel 5+
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
protected function makeResponse($request, $template, $data = [])
{
if ($request->ajax()) {
return response()->json($data);
}
return view($template, $data);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MyController extends Controller
{
public function index(Request $request)
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($request, $template, compact($object, $object2));
}
}
There is no magic but you can easily override ViewService in 3 steps:
1.create your view factory (your_project_path/app/MyViewFactory.php)
<?php
/**
* Created by PhpStorm.
* User: panos
* Date: 5/2/15
* Time: 1:35 AM
*/
namespace App;
use Illuminate\View\Factory;
class MyViewFactory extends Factory {
public function make($view, $data = array(), $mergeData = array())
{
if (\Request::ajax()) {
return $data;
}
return parent::make($view, $data, $mergeData);
}
}
2.create your view service provider (your_project_path/app/providers/MyViewProvider.php)
<?php namespace App\Providers;
use App\MyViewFactory;
use Illuminate\View\ViewServiceProvider;
class MyViewProvider extends ViewServiceProvider {
/**
* Register the application services.
*
* #return void
*/
public function register()
{
parent::register();
}
/**
* Overwrite original so we can register MyViewFactory
*
* #return void
*/
public function registerFactory()
{
$this->app->singleton('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
// IMPORTANT in next line you should use your ViewFactory
$env = new MyViewFactory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
}
3.in your_project_path/config/app.php:
change 'Illuminate\View\ViewServiceProvider',
to 'App\Providers\MyViewProvider',
What this do:
it tells your application to use another view provider which will register your view factory
$env = new MyViewFactory($resolver, $finder, $app['events']);
in line 33 of MyViewProvider.php which will check if request is AJAX and return if true or continue with original behavior
return parent::make($view, $data, $mergeData);
in MyViewFactory.php line 19
Hope this help you,
In laravel 5.1, this is the best way:
if (\Illuminate\Support\Facades\Request::ajax())
return response()->json(compact($object1, $object2));
else
return view('template', compact($object, $object2));
The solution suggested by #ryanwinchester is really good. I, however, wanted to use it for the responses from update() and delete(), and there naturally return view() at the end doesn't make a lot of sense as you mostly want to use return redirect()->route('whatever.your.route.is'). I thus came up with that idea:
// App\Controller.php
/**
* Checks whether request is ajax or not and returns accordingly
*
* #param array $data
* #return mixed
*/
protected function forAjax($data = [])
{
if (request()->ajax()) {
return response()->json($data);
}
return false;
}
// any other controller, e.g. PostController.php
public function destroy(Post $post)
{
// all stuff that you need until delete, e.g. permission check
$comment->delete();
$r = ['success' => 'Wohoo! You deleted that post!']; // if necessary
// checks whether AJAX response is required and if not returns a redirect
return $this->forAjax($r) ?: redirect()->route('...')->with($r);
}
Warning: This question is Laravel 4 specific.
I've been using Facades in my controllers before. Therefore I know the code is working. Now I need to introduce dependency injection for various reasons.
After refactoring the controller I get following error:
Illuminate \ Container \ BindingResolutionException
Unresolvable dependency resolving [Parameter #0 [ $name ]].
I can't figure out where the problem is. The Error message seems cryptic to me and I don't understand it. (I don't see any problem with my __constructor parameters since I've registered the binding for the HelpersInterface)
Here are the important parts of my code:
File: app/start/global.php
<?php
// ...
App::bind('Acme\Interfaces\HelpersInterface', 'Acme\Services\Helpers');
File: composer.json
// ...
"autoload": {
// ...
"psr-0": {
"Acme": "app/"
}
},
// ...
File: app/Acme/Controllers/BaseController.php
<?php namespace Acme\Controllers;
use Carbon\Carbon;
use Controller;
use Illuminate\Foundation\Application as App;
use Illuminate\View\Factory as View;
use Acme\Interfaces\HelpersInterface as Helpers;
use Illuminate\Http\Response;
class BaseController extends Controller {
/**
* #var \Illuminate\Foundation\Application
*/
private $app;
/**
* #var \Carbon\Carbon
*/
private $carbon;
/**
* #var \Illuminate\View\Factory
*/
private $view;
/**
* #var \Acme\Interfaces\HelpersInterface
*/
private $helpers;
function __construct(App $app, Carbon $carbon, View $view, Helpers $helpers)
{
$this->app = $app;
$this->carbon = $carbon;
$this->view = $view;
$this->helpers = $helpers;
$lang = $this->app->getLocale();
$now = $this->carbon->now();
$this->view->share('lang', $lang);
$this->view->share('now', $now);
}
/**
* Missing Method
*
* Abort the app and return a 404 response
*
* #param array $parameters
* #return Response
*/
public function missingMethod($parameters = array())
{
return $this->helpers->force404();
}
}
File: app/Acme/Services/Helpers.php
<?php namespace Acme\Services;
use Illuminate\Config\Repository as Config;
use Illuminate\Database\Connection as DB;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector as Redirect;
use Illuminate\Session\Store as Session;
use Illuminate\Support\Facades\Response;
use Illuminate\Translation\Translator as Lang;
use Illuminate\View\Factory as View;
use Acme\Interfaces\MockablyInterface;
use Monolog\Logger as Log;
class Helpers implements HelpersInterface {
// ...
public function __construct(
Config $config,
Lang $lang,
View $view,
MockablyInterface $mockably,
Log $log,
Request $request,
Session $session,
DB $db,
Redirect $redirect,
Response $response
) {
// ...
}
// ...
}
File: app/Acme/Providers/HelpersServiceProvider.php
<?php namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
use Acme\Services\Helpers;
class HelpersServiceProvider extends ServiceProvider {
private $db;
private $defaultDbConnection;
protected function init()
{
$this->db = $this->app['db'];
$this->defaultDbConnection = $this->db->getDefaultConnection();
}
public function register()
{
$this->init();
$this->app->bind('helpers', function ()
{
return new Helpers(
$this->app['config'],
$this->app['translator'],
$this->app['view'],
$this->app['mockably'],
$this->app->make('log')->getMonolog(),
$this->app['request'],
$this->app['session.store'],
$this->db->connection($this->defaultDbConnection),
$this->app['redirect'],
$this->app['Illuminate\Support\Facades\Response']
);
});
}
For me it was just a matter of running
php artisan optimize:clear
It seems your Acme\Services\Helpers constructor takes a $name parameter, but is not type hinted.
Laravel's IoC is not magic. If your don't provide a type hint for every parameter, the IoC container has no way of knowing what to pass in.
Make sure you use Illuminate\Http\Request; on top of the file instead of any other http import like this
use Illuminate\Http\Request;
THANK ME LATER!
Got it fixed. All the tutorials about dependency injection were referring to concrete implementations of interfaces so that I thought that's the way to go about it. Joseph Silber's answer got me on the right track.
The trick is to bind the Interface to the binding of the ServiceProvider like shown below. That way Laravel will know how to instantiate the Helpers service.
File: app/start/global.php
<?php
// ...
App::bind('Acme\Interfaces\HelpersInterface', 'helpers');
I have installed this package https://github.com/Intervention/image with composer. I have added
'IntImage' => 'Intervention\Image\Facades\Image'
to config/app under aliases
I get the following error and cant figure out what I am doing incorrectly I am sure it has something to do with namespacing /autoloading but app/acme is in the psr-o section of composer.json
'Argument 1 passed to
Acme\Services\Images\InterventionImageEditor::__construct() must be an
instance of IntImage, none given, called in
/var/www/app/ACme/Providers/ImageEditorServiceProvider.php on line 14
and defined' in
/var/www/app/Acme/Services/Images/InterventionImageEditor.php:11
I have the following directory structure
app
acme
Providers
ImageEditorServiceProvider.php
Services
Images
ImageEditorInterface.php
InterventionImageEditor.php
and the content of the files
ImageEditorServiceProvider.php
<?php namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
use Acme\Services\Images\InterventionImageEditor;
/**
*
*/
class ImageEditorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
return new InterventionImageEditor();
});
}
}
ImageEditorInterface.php
<?php namespace Acme\Services\Images;
interface ImageEditorInterface
{
public function hello();
}
InterventionImageEditor.php
<?php namespace Acme\Services\Images;
use IntImage;
/**
*
*/
class InterventionImageEditor implements ImageEditorInterface
{
protected $imageeditor;
public function __construct(IntImage $imageeditor)
{
$this->imageeditor = $imageeditor;
}
public function hello()
{
$hello = 'hello';
return $hello;
}
}
Can I
Use IntImage;
in this way because it is a facade or am I missing something?
edit to include solution;
changing the service provider to the following resolved the problem
<?php namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
use Acme\Services\Images\InterventionImageEditor;
use IntImage;
/**
*
*/
class ImageEditorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
$intimage = new IntImage;
return new InterventionImageEditor($intimage);
});
}
}
The error is coming from ImageEditorServiceProder.php:
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
return new InterventionImageEditor();
});
Here you are instantiating the InterventionImageEditor without any parameters. You InterventionImageEditor requires one parameter of type IntImage.
If there are places where you won't have IntImage when instantiating InterventionImageEditor then you need to update your InterventionImageEditor::__construct so that it accepts null(possibly).
function __construct(IntImage $imageeditor = null)
{
if (is_null($imageeditor)) {
// Construct a default imageeditor
// $imageeditor = new ...;
}
$this->imageeditor = $imageeditor;
}
i am not sure you can using IntImage because this file is Facades.
if you want to extending the intervention class. you should add Intervention\Image\Image to your ImageEditorServiceProvider.
use Intervention\Image\Image;
class ImageEditorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
return new InterventionImageEditor(new Image);
});
}
}
I have writted an annotation who throw an AccesDeniedException when the action is not called by an AJAX request (XMLHttpRequest).
It work but when I want to use the #Secure(roles="A") annotation from JMS/SecurityExtraBundle it don't work like I omitted my custom exception.
Controller
namespace Mendrock\Bundle\SagaBundle\Controller;
use JMS\SecurityExtraBundle\Annotation\Secure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Mendrock\Bundle\SagaBundle\Entity\Saison;
use Mendrock\Bundle\SagaBundle\Form\SaisonType;
use Mendrock\Bundle\ExtraBundle\Annotation\XmlHttpRequest;
/**
* #Route("/episodesAjax")
*/
class EpisodeController extends Controller {
/**
* #XmlHttpRequest()
* #Secure(roles="ROLE_SUPER_ADMIN")
*
* #Route("/saisonAdd", options={"expose"=true})
* #Template()
*/
public function saisonAddAction() {
$entity = new Saison();
$form = $this->createForm(new SaisonType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
Annotation
namespace Mendrock\Bundle\ExtraBundle\Annotation;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* #Annotation
*/
class XmlHttpRequest
{
public $message = 'The action could be an XMLHttpRequest call.';
public function checkRequest($event){
if (!$event->getRequest()->isXmlHttpRequest()) {
throw new AccessDeniedHttpException($this->message);
}
}
public function execute($event){
$this->checkRequest($event);
}
}
Listener
namespace Mendrock\Bundle\ExtraBundle\Listener;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Mendrock\Bundle\ExtraBundle\Annotation\XmlHttpRequest;
class EventListener {
private $reader;
public function __construct(Reader $reader) {
$this->reader = $reader;
}
/**
* This event will fire during any controller call
*/
public function onKernelController(FilterControllerEvent $event) {
if (!is_array($controller = $event->getController())) {
return;
}
$method = new \ReflectionMethod($controller[0], $controller[1]);
foreach ($this->reader->getMethodAnnotations($method) as $configuration) {
if ($configuration instanceof XmlHttpRequest) {
$configuration->execute($event);
}
}
}
}
Any idea why I can't use at the same time #Secure(...) and #XMLHttpRequest?
Edit:
services.yml
services:
annotations.xmlhttprequest:
class: Mendrock\Bundle\ExtraBundle\Listener\EventListener
tags: [{name: kernel.event_listener, event: kernel.controller, method: onKernelController}]
arguments: [#annotation_reader]
I ran into the same problem when I wanted to add my own annotations. The solution using ClassUtils::getUserClass would not work (using Symfony 2.3, if that makes a difference).
Since we only use the #Secure annotation from JMS\SecurityExtraBundle, I made our codebase use LswSecureControllerBundle instead.
This bundle only provides #Secure, and does not do voodoo tricks with your controllers.
I am running into the same issue after upgrading to Symfony 2.1.
The issue, from my investigation, is that the JMS SecurityExtraBundle generates proxy classes whenever you use one of their annotations. The problem with the generated proxy classes is that custom annotations do not get proxied over, which is why the annotations appear to be missing.
The solution according to the author is to rewrite using AOP (facilities provided by JMSAopBundle) or to use ClassUtils::getUserClass.
Thanks to suihock for pointing this out:
$class = \CG\Core\ClassUtils::getUserClass($controller[0]);
$method = new \ReflectionMethod($class, $controller[1]);