After reading documentation and looking for it with Google, I've to ask you.
I want to switch between 3 languages: ca_ES, es_ES and en_GB
So I did a controller like this:
/**
* #Route("/canviar-idioma/{locale}", name="change_lang")
* #Template()
*
* #return array
*/
public function canviarIdiomaAction($locale){
$request = $this->getRequest();
if ($locale == 'cat'){
$this->get('translator')->setLocale('ca_ES');
return new Response('ca');
} else if ($locale == 'es'){
$this->get('translator')->setLocale('es_ES');
return new Response('es');
} else if ($locale == 'eng'){
$this->get('session')->set('_locale', 'en_GB');
return new Response('en');
}
return new Response(null);
}
This controller is being called by ajax, when an user clicks a flag with the language. I receive the "ca" or "es" or "en" correctly, so controller is "working" somehow.
As you can see, I've tried using it by session or getting the translator. Both ways same results.
But, I made this controller to check if my locale really changed:
/**
* #Route("/quinlocaletinc", name="quinlocaletinc")
* #Template()
*
* #return array
*/
public function quinlocaletincAction(){
$request = $this->getRequest();
return new Response($request->getLocale());
}
And this locale ALWAYS gives "ca_ES" as it's the one defined in my parameters file:
locale: ca_ES
And my config.yml:
default_locale: %locale%
translator: { fallback: %locale% }
You need to use the "special" _locale variable in the route, Symfony will then properly set the locale for your application.
You can read more about this in the documentation
Your route should look like this:
/**
* #Route("/canviar-idioma/{_locale}", requirements={"_locale" = "ca_ES|es_ES|en_GB"}, name="change_lang")
* #Template()
*
* #return array
*/
public function canviarIdiomaAction() {
$locale = $request->getLocale();
// ...
Your second route will also need the parameter
/**
* #Route("/quinlocaletinc/{_locale}", name="quinlocaletinc")
* #Template()
*
* #return array
*/
public function quinlocaletincAction() {
$request = $this->getRequest();
return new Response($request->getLocale());
}
A good convention is to prefix all routes with the locale rather than postfix
/**
* #Route("/{_locale}/quinlocaletinc", name="quinlocaletinc")
* #Template()
*
* #return array
*/
public function quinlocaletincAction() {
$request = $this->getRequest();
return new Response($request->getLocale());
}
By using the _locale variable in Symfony, everything just "works" (i.e. if you visit /ca_ES/page all links on that page will include the right url).
Also when using the _locale parameter in your route, $this->get('translator')->setLocale('ca_ES'); is un-necessary as it will happen automatically.
Your annotation routing and Controller argument should be {_locale} and $_locale.
/**
* #Route("/canviar-idioma/{_locale}", name="change_lang")
* #Template()
*
* #return array
*/
public function canviarIdiomaAction($_locale)
{
// ...
Related
I'm using api platform in symfony (4) and without using a transformer (or rather: without using the output property) I'm getting the correct result.
However as I need to transform a logo (add a path) I need to integrate a transformer. As a result the response is empty.
ApiResource definition in Entity:
/**
*
* #ApiResource(
* collectionOperations = {
* "get"
* },
* normalizationContext={"groups" = {"frontend:read"}},
* itemOperations={
"get"
* },
* order={"name"="ASC"},
* paginationEnabled=false,
* output=EntityApiOutput::class
* )
*/
EntityApiOutput:
class EntityApiOutput
{
public $id;
}
EntityApiOutputDataTransformer:
class EntityApiOutputDataTransformer implements DataTransformerInterface
{
/**
* {#inheritdoc}
*/
public function transform($object, string $to, array $context = [])
{
$eao = new EntityApiOutput();
$eao->id = 3;
return $eao;
}
public function supportsTransformation($data, string $to, array $context = []): bool
{
return EntityApiOutput::class === $to && $data instanceof Entity;
}
}
entry in services.yaml:
App\DataTransformer\EntityApiOutputDataTransformer:
tags:
- { name: api_platform.data_transformer }
I simplified the transformer for reading purposes.
Putting a
dump($eao)
exit;
into the transform method confirms that the transformer is called and the EntityApiOutput object is filled.
Mhm unfortunately the api platform doc forgets to mention to also put the group into the output class:
class EntityApiOutput
{
/*
*
* #Groups({"frontend:read"})
*/
public $id;
}
That's how it should look like.
I'm trying to do a hard thing: implementing cache invalidation with Symfony 4.4.13 using FOSHttpCacheBundle 2.9.0 and built-in Symfony reverse proxy.
Unfortunately, I can't use other caching solution (like Varnish or Nginx) because my hosting service doesn't offer them. So, the Symfony built-in reverse proxy is the only solution I have.
I've installed and configured FOSHttpCacheBundle (following the documentation). Also created a CacheKernel class and modified Kernel to use it (following Symfony official documentation, FOSHttpCache documentation and FOSHttpCacheBundle documentation).
After few tests (with my browser), the HTTP caching works and GET responses are cached (seen in browser network analyzer). But, when I update a resource with PUT/PATCH/POST, the GET responses still come from the cache and are unchanged until the expiration. My deduction is the invalidation doesn't work.
Have I do something wrong? Can you help me to troubleshoot?
See my code and configuration below.
config/packages/fos_http_cache.yaml
fos_http_cache:
cache_control:
rules:
-
match:
path: ^/
headers:
cache_control:
public: true
max_age: 15
s_maxage: 30
etag: "strong"
cache_manager:
enabled: true
invalidation:
enabled: true
proxy_client:
symfony:
tags_header: My-Cache-Tags
tags_method: TAGPURGE
header_length: 1234
purge_method: PURGE
use_kernel_dispatcher: true
src/CacheKernel.php
<?php
namespace App;
use FOS\HttpCache\SymfonyCache\CacheInvalidation;
use FOS\HttpCache\SymfonyCache\CustomTtlListener;
use FOS\HttpCache\SymfonyCache\DebugListener;
use FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache;
use FOS\HttpCache\SymfonyCache\PurgeListener;
use FOS\HttpCache\SymfonyCache\RefreshListener;
use FOS\HttpCache\SymfonyCache\UserContextListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class CacheKernel extends HttpCache implements CacheInvalidation
{
use EventDispatchingHttpCache;
// Overwrite constructor to register event listeners for FOSHttpCache.
public function __construct(HttpKernelInterface $kernel, SurrogateInterface $surrogate = null, array $options = [])
{
parent::__construct($kernel, new Store($kernel->getCacheDir()), $surrogate, $options);
$this->addSubscriber(new CustomTtlListener());
$this->addSubscriber(new PurgeListener());
$this->addSubscriber(new RefreshListener());
$this->addSubscriber(new UserContextListener());
if (isset($options['debug']) && $options['debug'])
$this->addSubscriber(new DebugListener());
}
// Made public to allow event listeners to do refresh operations.
public function fetch(Request $request, $catch = false)
{
return parent::fetch($request, $catch);
}
}
src/Kernel.php
<?php
namespace App;
use FOS\HttpCache\SymfonyCache\HttpCacheAware;
use FOS\HttpCache\SymfonyCache\HttpCacheProvider;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
class Kernel extends BaseKernel implements HttpCacheProvider
{
use MicroKernelTrait;
use HttpCacheAware;
private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
public function __construct(string $environment, bool $debug)
{
parent::__construct($environment, $debug);
$this->setHttpCache(new CacheKernel($this));
}
...
public/index.php
<?php
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
require dirname(__DIR__).'/config/bootstrap.php';
...
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel = $kernel->getHttpCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
One of mine controller, src/Controller/SectionController.php (NOTE: routes are defined in YAML files)
<?php
namespace App\Controller;
use App\Entity\Section;
use App\Entity\SectionCollection;
use App\Form\SectionType;
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
use FOS\RestBundle\Controller\AbstractFOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class SectionController extends AbstractFOSRestController
{
/**
* List all sections.
*
* #Rest\View
* #param Request $request the request object
* #return array
*
* Route: get_sections
*/
public function getSectionsAction(Request $request)
{
return new SectionCollection($this->getDoctrine()->getRepository(Section::class)->findAll());
}
/**
* Get a single section.
*
* #Rest\View
* #param Request $request the request object
* #param int $id the section id
* #return array
* #throws NotFoundHttpException when section not exist
*
* Route: get_section
*/
public function getSectionAction(Request $request, $id)
{
if (!$section = $this->getDoctrine()->getRepository(Section::class)->find($id))
throw $this->createNotFoundException('Section does not exist.');
return array('section' => $section);
}
/**
* Get friends of the section's user.
*
* #Rest\View
* #return array
*
* Route: get_friendlysections
*/
public function getFriendlysectionsAction()
{
return $this->get('security.token_storage')->getToken()->getUser()->getSection()->getMyFriends();
}
private function processForm(Request $request, Section $section)
{
$em = $this->getDoctrine()->getManager();
$statusCode = $em->contains($section) ? Response::HTTP_NO_CONTENT : Response::HTTP_CREATED;
$form = $this->createForm(SectionType::class, $section, array('method' => $request->getMethod()));
// If PATCH method, don't clear missing data.
$form->submit($request->request->get($form->getName()), $request->getMethod() === 'PATCH' ? false : true);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($section);
$em->flush();
$response = new Response();
$response->setStatusCode($statusCode);
// set the 'Location' header only when creating new resources
if ($statusCode === Response::HTTP_CREATED) {
$response->headers->set('Location',
$this->generateUrl(
'get_section', array('id' => $section->getId()),
true // absolute
)
);
}
return $response;
}
return View::create($form, Response::HTTP_BAD_REQUEST);
}
/**
*
* Creates a new section from the submitted data.
*
* #Rest\View
* #return FormTypeInterface[]
*
* #InvalidateRoute("get_friendlysections")
* #InvalidateRoute("get_sections")
*
* Route: post_section
*/
public function postSectionsAction(Request $request)
{
return $this->processForm($request, new Section());
}
/**
* Update existing section from the submitted data.
*
* #Rest\View
* #param int $id the section id
* #return FormTypeInterface[]
* #throws NotFoundHttpException when section not exist
*
* #InvalidateRoute("get_friendlysections")
* #InvalidateRoute("get_sections")
* #InvalidateRoute("get_section", params={"id" = {"expression"="id"}})")
*
* Route: put_section
*/
public function putSectionsAction(Request $request, $id)
{
if (!$section = $this->getDoctrine()->getRepository(Section::class)->find($id))
throw $this->createNotFoundException('Section does not exist.');
return $this->processForm($request, $section);
}
/**
* Partially update existing section from the submitted data.
*
* #Rest\View
* #param int $id the section id
* #return FormTypeInterface[]
* #throws NotFoundHttpException when section not exist
*
* #InvalidateRoute("get_friendlysections")
* #InvalidateRoute("get_sections")
* #InvalidateRoute("get_section", params={"id" = {"expression"="id"}})")
*
* Route: patch_section
*/
public function patchSectionsAction(Request $request, $id)
{
return $this->putSectionsAction($request, $id);
}
/**
* Remove a section.
*
* #Rest\View(statusCode=204)
* #param int $id the section id
* #return View
*
* #InvalidateRoute("get_friendlysections")
* #InvalidateRoute("get_sections")
* #InvalidateRoute("get_section", params={"id" = {"expression"="id"}})")
*
* Route: delete_section
*/
public function deleteSectionsAction($id)
{
$em = $this->getDoctrine()->getManager();
if ($section = $this->getDoctrine()->getRepository(Section::class)->find($id)) {
$em->remove($section);
$em->flush();
}
}
}
After searching few days, I found the solution by myself.
In CacheKernel, I extend Symfony\Component\HttpKernel\HttpCache\HttpCache as described in FOSHttpCache documentation. But, the class must extend Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache instead as described in Symfony documentation. By consequences, the constructor change too.
To be honest, I don't know the difference between these two classes but you must use the second one if you want to have a built-in functional reverse proxy. It works now for me.
I put here the final code of src/CacheKernel.php:
<?php
namespace App;
use FOS\HttpCache\SymfonyCache\CacheInvalidation;
use FOS\HttpCache\SymfonyCache\CustomTtlListener;
use FOS\HttpCache\SymfonyCache\DebugListener;
use FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache;
use FOS\HttpCache\SymfonyCache\PurgeListener;
use FOS\HttpCache\SymfonyCache\RefreshListener;
use FOS\HttpCache\SymfonyCache\UserContextListener;
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class CacheKernel extends HttpCache implements CacheInvalidation
{
use EventDispatchingHttpCache;
/**
* Overwrite constructor to register event listeners for FOSHttpCache.
*/
public function __construct(HttpKernelInterface $kernel)
{
parent::__construct($kernel, $kernel->getCacheDir());
$this->addSubscriber(new CustomTtlListener());
$this->addSubscriber(new PurgeListener());
$this->addSubscriber(new RefreshListener());
$this->addSubscriber(new UserContextListener());
if (isset($options['debug']) && $options['debug'])
$this->addSubscriber(new DebugListener());
}
/**
* Made public to allow event listeners to do refresh operations.
*
* {#inheritDoc}
*/
public function fetch(Request $request, $catch = false)
{
return parent::fetch($request, $catch);
}
}
The rest of the code don't change.
Hope it helps. See you.
I have a WordPress running application, which I would like to access using a separate interface that uses Laravel 5.8.(don't worry about the hashing)
As such, instead of cloning passwords back and forth, I would like to use the user_email and user_pass columns in the Laravel User model instead.
I have tried what the official docs say :
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Handle an authentication attempt.
*
* #param \Illuminate\Http\Request $request
*
* #return Response
*/
public function authenticate(Request $request)
{
$credentials = $request->only('user_email', 'user_pass');
if (Auth::attempt($credentials)) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
I then edited the blade files, but no avail. Any pointers?
Laravel provides a way to change the default columns for auth (email, password) by overriding some functions.
In your User model add this function that overrides the default column for password:
App/User.php
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->user_pass;
}
And, in your LoginController change from email to user_email
App/Http/Controllers/Auth/LoginController.php
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'user_email';
}
Now you have overridden the default columns used by Laravel's Auth logic. But you are not finished yet.
LoginController has a function that validates the user's input and the password column is hardcoded to password so in order to change that, you also need to add these functions in LoginController:
App/Http/Controllers/Auth/LoginController.php
/**
* Validate the user login request.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function validateLogin(Request $request)
{
$request->validate([
$this->username() => 'required|string',
'user_pass' => 'required|string',
]);
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only($this->username(), 'user_pass');
}
Next step is to create a custom Provider, let's call it CustomUserProvider that will be used instead of the default one EloquentUserProvider and where you will override the password field.
App/Providers/CustomUserProvider.php
<?php
namespace App\Providers;
class CustomUserProvider extends EloquentUserProvider
{
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('user_pass', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'user_pass')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['user_pass'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
}
Now that you extended the default provider you need to tell Laravel to use this one instead of EloquentUserProvider. This is how you can do it.
App/Providers/AuthServiceProvider.php
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
$this->app->auth->provider('custom', function ($app, $config) {
return new CustomUserProvider($app['hash'], $config['model']);
});
}
Finally update the config information config/auth.php and change the driver from eloquent to custom (that's how I named it above; you can change it to whatever you want). So the config/auth.php file should have this bit:
'providers' => [
'users' => [
'driver' => 'custom',
'model' => App\User::class,
],
],
Hope it helps!
Regards
It would be up and working, If you can just use sessions here instead of using Auth::attempt just like working on core PHP.
Have a problem here:
/**
* Deal controller.
*
* #Route("/portfolio/{portfolio_id}/deal")
*/
class DealController extends Controller
{
// … some code here…
/**
* Creates a new Deal entity.
*
* #Route("/", name="mb_deal_create")
* #Method("POST")
* #Template("MBPortfolioBundle:Deal:new.html.twig")
*/
public function createAction(Request $request)
{
}
So this is my question: how to get $portfolio_id route parameter defined in class annotation from within this createAction?
If I'm trying just add this parameter to the parameter list - it's null then:
public function createAction(Request $request, $portfolio_id) // no way
If I'm trying to get it from query parameter bag - it's null then:
public function createAction(Request $request)
{
$portfolio_id = $request->query->get('portfolio_id'); // no way
So what I need to do?
I see you've already found the solution but it doesn't hurt to put here another way to solve it:
$context = new RequestContext();
$context->fromRequest($request);
$portfolio_id = $context->getParameter('portfolio_id');
Edit
Move portfolio_id to actions' annotation
/**
* Deal controller.
*
*/
class DealController extends Controller
{
// … some code here…
/**
* Creates a new Deal entity.
*
* #Route("/portfolio/{portfolio}/deal", name="mb_deal_create")
* #Method("POST")
* #Template("MBPortfolioBundle:Deal:new.html.twig")
*/
public function createAction(Request $request, Portfolio $portfolio)
{
}
Mine solution is right here:
$portfolio_id = $request->attributes->get('_route_params')['portfolio_id'];
I'm attempting to learn how to use the Symfony 2.3 framework. I thought it would be a good first exercise to modify Acme\DemoBundle\DemoController::helloaction() to provide a default name when none was entered.
This is the original:
/**
* #Route("/hello/{name}", name="_demo_hello")
* #Template()
*/
public function helloAction($name)
{
return array('name' => $name);
}
It works with urls like localhost/Symfony/web/demo/hello/SOMENAME and fails with urls like localhost/Symfony/web/demo/hello/SOMENAME/, localhost/Symfony/web/demo/hello and localhost/Symfony/web/demo/hello/
This is what I did:
/**
* #Route("/hello", name="_demo_hello", defaults={"name" = "World"})
* #Template()
*/
public function helloAction($name)
{
return array('name' => $name);
}
It works with localhost/Symfony/web/demo/hello and fails with localhost/Symfony/web/demo/hello/SOMENAME, localhost/Symfony/web/demo/hello/SOMENAME/ and localhost/Symfony/web/demo/hello/
How do I make the routing work with and without a name and with and without a trailing slash?
You can set a default value like this:
/**
* #Route("/hello/", defaults={"name" = "John"})
* #Route("/hello/{name}", name="_demo_hello")
* #Template()
*/
public function helloAction($name) { ... }
It's also important to know that you can have more than one route on the same action, so no need to duplicate actions.
See documentation: http://symfony.com/doc/2.2/book/controller.html And: #Route Documentation
I think your solution should also work if you append a / after your route /hello.