FOS Rest Bundle - Controller must return Response object, but it returns View - fosuserbundle

I'm trying to setup FOS Rest Bundle on symfony 4.2.3. and I'm following this tutorial:
https://www.thinktocode.com/2018/03/26/symfony-4-rest-api-part-1-fosrestbundle/
Installed and configured the bundle:
//annotations.yaml
rest_controller:
resource: ../../src/Controller/Rest/
type: annotation
prefix: /api
//fos_rest.yaml
fos_rest:
view:
view_response_listener: true
format_listener:
rules:
- { path: ^/api, prefer_extension: true, fallback_format: json, priorities: [ json ] }
and when I call postAction() of my ArticleController object, which looks like:
class ArticleController extends FOSRestController
{
/**
* Creates an Article resource
* #Rest\Post("/articles")
* #param Request $request
* #return View
*/
public function postArticle(Request $request): View
{
$entityManager = $this->getDoctrine()->getManager();
$article = new Article();
$article->setTitle($request->get('title'));
$article->setContent($request->get('content'));
$articleCategory = $entityManager->getReference(ArticleCategory::class, $request->get('article_category'));
$article->setArticleCategory($articleCategory);
$article->setPublished($request->get('published'));
$entityManager->persist($article);
$entityManager->flush();
// In case our POST was a success we need to return a 201 HTTP CREATED response
return View::create($article, Response::HTTP_CREATED);
}
}
I get the error message (testing with Postman):
The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned an object of type FOS\RestBundle\View\View.
Why is that?! It's defined that postArticle() method should return a View object and I'm doing that.
One notice: my class is extending FOSRestController class, which is deprecated. I also tried extending AbstractFOSRestController, but with the same result.

So, for some reason my fos_rest.yaml file was not in packages dir, where it would be global, but in test sub-dir of packages. When I moved it where it belongs this started working.

Related

Target class [blade.compiler] does not exist. in Lumen can we use balde in lumen provider

In Lumen can we use Blade in the Lumen provider?
Target class [blade.compiler] does not exist.
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class RolesServiceProvider extends ServiceProvider
{
/**
* #return void
*/
public function register()
{
}
/**
* #return void
*/
public function boot()
{
Blade::directive('role', function ($role) {
return "<?php if(auth()->check() &&
auth()->user()->hasRole({$role})) :";
});
Blade::directive('endrole', function ($role) {
return "<?php endif; ?>";
});
}
}
In your scenario, it's happening because I believe that you forgot to register the Illuminate\View\ViewServiceProvider class.
Also, when registering the Provider, make sure to use $app->configure('view') in your bootstrap/app.php or $this->app->configure('view') from your Service Provider to configure your view configuration. Because the view service provider doesn't load the configuration itself.
You can check how the view component is loaded in a Lumen application.
I did like the following
if (!$this->app->bound('view')) {
// Lumen doesn't load the view config by default
$this->app->configure('view');
$this->app->register(ViewServiceProvider::class);
}
Or you can do the loadComponent thing as lumen does. And it will solve the issue you stated.

Symfony: How to load all paths from the same Index route (to use dynamic routing in a React SPA)

I'm creating a SPA backed by Symfony and ApiPlatform so I want to always load my main route despite the real path of the URL.
I want something like this:
/**
* {#inheritdoc}
*/
class DefaultController extends Controller
{
/**
* #Route("/*", name="homepage")
*
* #return Response
*/
public function indexAction(): Response
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig');
}
}
In my intentions, also if the URL is something like /path/to/the/spa/page I want to anyway load the DefaultController::indexAction()route.
How to do this? (obviously the provided example doesn't work).
Ok, I've found the solution after an "illumination".
I remembered that there is the possibility to rewrite all URL adding or removing the trailing slash
Reading that article I saw this:
class RedirectingController extends Controller
{
/**
* #Route("/{url}", name="remove_trailing_slash",
* requirements={"url" = ".*\/$"})
*/
public function removeTrailingSlash(Request $request)
{
// ...
}
}
So, to intercept all URL despite the path, my DefaultController::indexAction() becomes this:
class DefaultController extends Controller
{
/**
* #Route("/{url}",requirements={"url"=".*"}, name="homepage")
*
* #return Response
*/
public function indexAction(): Response
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig');
}
}
Now all URL are all handled by DefaultController::indexAction() despite the URL path.
I would recommend you to use symfony's event system instead.
Subscribe to either kernel.request or kernel.router events.
In case of kernel.request you have to overtake the Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()
which priotiry is 32 (use 33 at least).
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SpaSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 33],
];
}
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
if (!$request->isXmlHttpRequest()) {
$html = $this->twig->render('spa.html.twig', [
'uri' => $request->getUri(),
]);
$response = new Response($html, Response::HTTP_OK);
$event->setResponse($response);
}
}
}
In case of kernel.router use priority 1 at least.
You can use the php bin/console debug:event-dispatcher command to find out which listeners are registered for events and their priorities.

How to access the user Token in an injected service to reencode passwords?

I have the below code where I am trying to re-encode passwords as users log in (the database has bee migrated form a legacy website). However, I'm not sure what I'm doing wrong as I keep getting errors:
Attempted to call an undefined method named "forward" of class "AppBundle\Service\HubAuthenticator".
I have set things up as follows:
security.yml
security:
encoders:
AppBundle\Entity\Member:
id: club.hub_authenticator
services.yml
services:
//This should be central service than then calls the second
club.hub_authenticator:
class: AppBundle\Service\HubAuthenticator
club.password_rehash:
class: AppBundle\Service\PasswordRehash
Hubauthenticator.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
function __construct($cost=13)
{
parent::__construct($cost);
}
function isPasswordValid($encoded, $raw, $salt)
{
// Test for legacy authentication (and conditionally rehash the password stored in the database if true)
if ($this->comparePasswords($encoded, sha1("saltA".$raw."saltB"))) {
$this->forward('club.password_rehash:rehash');
}
// Test for Symfony's Bcrypt authentication (any passwords just rehashed in previous step should work here)
if (parent::isPasswordValid($cost=13, $encoded,$raw,$salt)) return true ;
}
}
PasswordRehash.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class PasswordRehash extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder
{
// Customises BCryptPasswordEncoder class to use legacy SHA method
function rehash($member, $raw, $salt)
{
//Salt is null as Symfony documentation says it is better to generate a new one
parent::encodePassword($member->getPlainPassword, $salt=null ) ;
}
}
Some other previous attempts for completeness:
My guess is that the problem is that I am misunderstanding what objects are available to me. My understanding is that the user hasn't been authenticated at this point so have tried and removed the below attempts:
Trying to inject the $member into the HubAuthenticator service:
function __construct($cost=13)
{
parent::__construct($cost, \Member $member);
}
When trying to get the plainpassword to rehash:
$this->get('security.context')->getToken()->getUser()->getPlainPassword();
In your services, you can only access what dependencies you've injected.
So, to access the current user object, you need to pass it as argument:
service:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage" ]
Constructor:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
private $storage;
function __construct($cost = 13, TokenStorageInterface $storage)
{
parent::__construct($cost);
$this->storage = $storage;
// Now you can use:
// $user = $this->storage->getToken()->getUser();
}
}
Then, to access the second service, same way, inject it.
Add it to the service arguments:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage", "#club.password_rehash" ]
Add it to your constructor:
private $storage;
private $passwordRehash
function __construct($cost = 13, TokenStorageInterface $storage, PasswordRehash $passwordRehash)
{
parent::__construct($cost);
$this->storage = $storage;
$this->passwordRehash = $passwordRehash;
// Now you can use:
// $this->passwordRehash->rehash(...);
}
Hope this helps you.

Instantiating Request in Controller in Kohana 3.3

While upgrading my own module to work with latest Kohana (3.3) I found malfunction in my scenario. I use template driven schema in my app (My Controllers extend Controller_Theme). But for AJAX calls I used in version 3.2 separate Controller which extends just Controller. I had to instantiate Request object in this controller for access passed variables via POST or GET in Rquest object. I did it in __construct() method:
class Controller_Ajax extends Controller {
public function __construct()
{
$this->request = Request::current();
}
public function action_myaction()
{
if($this->is_ajax())
{
$url = $this->request->post('url');
$text = $this->request->post('text');
}
}
}
In myaction() method I can access posted variables like this.
But this does not work anymore in Kohana 3.3. I always get this error:
ErrorException [ Fatal Error ]: Call to a member function action() on a non-object
SYSPATH/classes/Kohana/Controller.php [ 73 ]
68 {
69 // Execute the "before action" method
70 $this->before();
71
72 // Determine the action to use
73 $action = 'action_'.$this->request->action();
74
75 // If the action doesn't exist, it's a 404
76 if ( ! method_exists($this, $action))
77 {
78 throw HTTP_Exception::factory(404,
I am sure I have routes set up correctly. I didn't found any changes in migration document from 3.2 to 3.3 about Request object. Or did I missed something?
Both request and response are initialised by default in the Controller class (see below code), so there shouldn't be need to override it's constructor. Try to remove your constructor and if that doesn't help, then your routing is messed up.
abstract class Kohana_Controller {
/**
* #var Request Request that created the controller
*/
public $request;
/**
* #var Response The response that will be returned from controller
*/
public $response;
/**
* Creates a new controller instance. Each controller must be constructed
* with the request object that created it.
*
* #param Request $request Request that created the controller
* #param Response $response The request's response
* #return void
*/
public function __construct(Request $request, Response $response)
{
// Assign the request to the controller
$this->request = $request;
// Assign a response to the controller
$this->response = $response;
}

Working with namespaced payloads in Backbone.js

I'm working on a project where the request and response payloads are all namespaced. For example:
{
'SourceMachine':{
'Host':'some value',
'Description':'some value',
'UserName':'some value',
'Password':'some value'
}
}
To be able to do get() and set() on the individual fields, I overrode the parse method in my "source_model" like so:
parse : function(response, xhr) {
return response.SourceMachine;
}
That's all well and good. But the issue is this: When I want to do a POST or PUT operation, I have to get the Host, Description, UserName and Password attributes into the SourceMachine namespace. Basically what I've been doing is copying the model attributes to a temporary object, clearing out the model, then saving as follows:
var tempAttributes = this.model.attributes;
this.model.clear();
this.model.save({SourceMachine: tempAttributes});
This works, but it screams of KLUGE! There's got to be a better way of working with namespaced data. Thanks!
Update
I've now created a require.js module for a base model that I'll be using for all the models in my app, since they all rely on namespaced data:
define(function() {
return Backbone.Model.extend({
/**
* Adding namespace checking at the constructor level as opposed to
* initialize so that subclasses needn't invoke the upstream initialize.
* #param attributes
* #param options
*/
constructor : function(attributes, options) {
//Need to account for when a model is instantiated with
//no attributes. In this case, we have to take namespace from
//attributes.
this._namespace = options.namespace || attributes.namespace;
//invoke the default constructor so the model goes through
//its normal Backbone setup and initialize() is invoked as normal.
Backbone.Model.apply(this, arguments);
},
/**
* This parse override checks to see if a namespace was provided, and if so,
* it will strip it out and return response[this._namespace
* #param response
* #param xhr
* #return {*}
*/
parse : function(response, xhr) {
//If a namespace is defined you have to make sure that
//it exists in the response; otherwise, an error will be
//thrown.
return (this._namespace && response[this._namespace]) ? response[this._namespace]
: response;
},
/**
* In overriding toJSON, we check to see if a namespace was defined. If so,
* then create a namespace node and assign the attributes to it. Otherwise,
* just call the "super" toJSON.
* #return {Object}
*/
toJSON : function() {
var respObj = {};
var attr = Backbone.Model.prototype.toJSON.apply(this);
if (this._namespace) {
respObj[this._namespace] = attr;
} else {
respObj = attr;
}
return respObj;
}
})
});
Create and update operations on models call toJSON to produce the data for the server:
toJSON model.toJSON()
Return a copy of the model's attributes for JSON stringification. This can be used for persistence, serialization, or for augmentation before being handed off to a view.
You could provide your own toJSON:
toJSON: function() {
return { SourceMachine: _(this.attributes).clone() };
}
That should make your server happy. Unfortunately, toJSON is also commonly used to provide data for your templates and you probably don't want the SourceMachine noise in there; if so, then add another method to prepare data for your templates:
// Or whatever you want to call it...
valuesForTemplate: function() {
return _(this.attributes).clone();
}
That's what the standard toJSON method does internally.

Resources