Attributes for Symfony route - symfony

In Symfony controllers, route are defined as follow :
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
public function list(int $page): Response
{
// ...
}
}
Imagine now, you have a request or query parameter in this url. There's no functional need to define it in the route, it is accessible through $request->get('parameter_name').
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
public function list(Request $request, int $page): Response
{
$id = $request->get('id');
}
}
But I'm wondering if it is not more clear to define this parameter as attribute. For exemple like this :
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+', 'id' => '\d+'])]
public function list(Request $request, int $page): Response
{
$id = $request->get('id');
}
}
But in that case, Symfony won't check the type of the attribute (ie, url like /blog/1?id="foo" will work).
So maybe something like this with annotations
class BlogController extends AbstractController
{
/**
* #param int id Id of the post to be highlighted
*/
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+', 'id' => '\d+'])]
public function list(Request $request, int $page): Response
{
$id = $request->get('id');
}
}
}
Or maybe as suggested here for Laravel :
class BlogController extends AbstractController
{
/**
* Returns list of posts.
* Request object may have optional query int parameter 'id'
* used for highlighting a specific post
*
* #param Request $request
* #return Response
*/
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+', 'id' => '\d+'])]
public function list(Request $request, int $page): Response
{
$id = $request->get('id');
}
}
}
So what is the best option for a clear and well documented code ?

Related

Laravel 5.7 get current logged in user with user profile on one-one relationship

I am currently learning Laravel 5.7. I have downloaded the source code from github. In that code i have implemented one to one relationship with user and profile table.
I have been able to successfully login the user, and able to register the user. However, when i call to method getCurrentUser() it only returns the data from only the user table, not from the profile.
User Model
class AuthController extends Controller
{
public function __construct()
{
$this->middleware('auth:api')->only('logout');
}
public function getCurrentUser(): User
{
return request()->user();
}
public function login(Request $request): JsonResponse
{
$credentials = $this->validate($request, [
'email' => 'required|email|exists:users',
'password' => 'required|min:5',
]);
if (auth()->attempt($credentials)) {
$user = auth()->user();
/** #var User $user */
$user['token'] = $this->generateTokenForUser($user);
return response()->json($user);
} else {
return response()->json(['success' => 'false', 'message' => 'Authentication failed'], 401);
}
}
}
User
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'first_name','last_name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* Encrypt the password while savinf it.
*
* #param string $password
*/
public function setPasswordAttribute(string $password)
{
$this->attributes['password'] = Hash::make($password);
}
public function User()
{
return $this->hasOne('App\UserProfile');
}
}
User Profile
class UserProfile extends Model
{
//
/**
* The following fields are mass assignable.
* #var array
*/
protected $fillable = ['first_name', 'last_name',
'middle_name', 'date_of_birth', 'nationality','phone','image','permanent_address_country',
'permanent_address_state','permanent_address_district','temp_address_district','temp_address_state','gender','user_id'];
public function UserProfile()
{
return $this->belongsTo('App\User');
}
}
How can i return the current logged in user and profile details through Auth getCurrentUser api?
I am using Vue.js on my client side.
In your User model, change the User() method to this:
public function userProfile()
{
return $this->hasOne('App\UserProfile');
}
And in your UserProfile model, change the UserProfile() method to this:
public function user()
{
return $this->belongsTo('App\User');
}
Then you could get your user with its profile with this query:
User::with('userProfile')->find(Auth::id());
In your case, you can refactor the getCurrentUser() method to this:
use Illuminate\Http\Request;
public function getCurrentUser(Request $request): User
{
return $request->user()->load('userProfile');
}

Use UniqueEntity outside of entity and without forms

I need to validate an email passed by user:
private function validate($value): bool
{
$violations = $this->validator->validate($value, [
new Assert\NotBlank(),
new Assert\Email(),
new UniqueEntity([
'entityClass' => User::class,
'fields' => 'email',
])
]);
return count($violations) === 0;
}
But UniqueEntity constraint throws an exception:
Warning: get_class() expects parameter 1 to be object, string given
Seems like ValidatorInterface::validate() method's first argument awaiting for Entity object with getEmail() method, but it looks ugly.
Is there any elegant way to validate uniqueness of field passing only scalar value to ValidatorInterface::validate() method?
Seems like there is no built-in Symfony solution to do what I want, so I created custom constraint as Jakub Matczak suggested.
UPD: This solution throws a validation error when you're sending form to edit your entity. To avoid this behavior you'll need to improve this constraint manually.
Constraint:
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class UniqueValueInEntity extends Constraint
{
public $message = 'This value is already used.';
public $entityClass;
public $field;
public function getRequiredOptions()
{
return ['entityClass', 'field'];
}
public function getTargets()
{
return self::PROPERTY_CONSTRAINT;
}
public function validatedBy()
{
return get_class($this).'Validator';
}
}
Validator:
namespace AppBundle\Validator\Constraints;
use Doctrine\ORM\EntityManager;
use InvalidArgumentException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UniqueValueInEntityValidator extends ConstraintValidator
{
/**
* #var EntityManager
*/
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
$entityRepository = $this->em->getRepository($constraint->entityClass);
if (!is_scalar($constraint->field)) {
throw new InvalidArgumentException('"field" parameter should be any scalar type');
}
$searchResults = $entityRepository->findBy([
$constraint->field => $value
]);
if (count($searchResults) > 0) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Service:
services:
app.validator.unique_value_in_entity:
class: AppBundle\Validator\Constraints\UniqueValueInEntityValidator
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: validator.constraint_validator }
Usage example:
private function validate($value): bool
{
$violations = $this->validator->validate($value, [
new Assert\NotBlank(),
new Assert\Email(),
new UniqueValueInEntity([
'entityClass' => User::class,
'field' => 'email',
])
]);
return count($violations) === 0;
}
For this porpose i would use #UniqueEntity(fields={"email"}) in user class annotation. Kind of this way:
/**
* #ORM\Entity()
* #ORM\Table(name="user")
* #UniqueEntity(fields={"email"})
*/

Why Sonata Admin doesn't save my custom doctrine field?

I writed the simpliest Money class ever:
class Money
{
private $amount;
private $currency;
// getters and setters here
public function toArray() {
return ['amount' => $this->amount, 'currency' => $this->currency];
}
}
Then I created Doctrine custom type which extend Sonata's JsonType:
class MoneyType extends JsonType
{
/**
* #param Money $value
* #param AbstractPlatform $platform
* #return mixed|null|string
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return parent::convertToDatabaseValue($value->toArray(), $platform);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return new Money(parent::convertToPHPValue($value, $platform));
}
public function getName()
{
return 'money';
}
}
Then I added it in my config.yml:
doctrine:
dbal:
types:
json: Sonata\Doctrine\Types\JsonType
money: Enl\CurrencyBundle\Doctrine\Type\MoneyType
Then I added form type for this data piece:
class MoneyType extends AbstractType
{
// Some trivial getters and setters for dependencies here
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'enl_money';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'currencies' => $this->getPossibleCurrencies(),
'data_class' => 'Enl\CurrencyBundle\Model\Money'
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('currency', 'choice', ['choices' => $options['currencies'], 'required' => true])
->add('amount', 'number', ['required' => true]);
}
public function getParent()
{
return 'form';
}
}
And then I added the field to my model:
/**
* #var Money
* #ORM\Column(type="money", nullable=true)
*/
protected $price;
And finally my sonata admin form:
$form
->with('editor.dish', ['class' => 'col-md-8'])
->add('price', 'enl_money')
// blah-blah-blah
The problem is that Sonata Admin just doesn't save the form value!
I added var_dump($object); to Admin::update() method - the new value is there in object.
After that I created the simple test:
$dish = new Dish();
$dish->setPrice(new Money(7000, "BYR"));
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$em->persist($dish);
$em->flush();
It works! What should I change in my Admin class to get this working?

How to get Request object inside a Twig Extension in Symfony?

How can one access the Request object inside Twig Extension?
namespace Acme\Bundle\Twig;
use Twig_SimpleFunction;
class MyClass extends \Twig_Extension
{
public function getFunctions()
{
return array(
new Twig_SimpleFunction('xyz', function($param) {
/// here
$request = $this->getRequestObject();
})
);
}
public function getName() {
return "xyz";
}
}
As requested in the comments, here's the prefered way of injecting a request into any service. It works with Symfony >= 2.4.
Injecting the request and putting our service in the request scope is no longer recommended. We should use the request stack instead.
namespace AppBundle\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
class MyClass extends \Twig_Extension
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getFunctions()
{
$requestStack = $this->requestStack;
return array(
new \Twig_SimpleFunction('xyz', function($param) use ($requestStack) {
$request = $requestStack->getCurrentRequest();
})
);
}
public function getName()
{
return "xyz";
}
}
app/config/services.yml
app.twig_extension:
class: AppBundle\Twig\MyExtension
arguments:
- '#request_stack'
tags:
- { name: twig.extension }
Docs:
the request stack API
the request stack announcement
Register your extension as a service and give it the container service:
# services.yml
services:
sybio.twig_extension:
class: %sybio.twig_extension.class%
arguments:
- #service_container
tags:
- { name: twig.extension, priority: 255 }
Then retrieve the container by your (twig extension) class constructor and then the request:
<?php
// Your class file:
// ...
class MyClass extends \Twig_Extension
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var Request
*/
protected $request;
/**
* Constructor
*
* #param ContainerInterface $container
*/
public function __construct($container)
{
$this->container = $container;
if ($this->container->isScopeActive('request')) {
$this->request = $this->container->get('request');
}
}
// ...
Note that testing the scope is usefull because there is no request when running console command, it avoids warnings.
That's it, you are able to use the request !
I would suggest setting 'needs_environment' => true for your Twig_SimpleFunction, which then will add \Twig_Environment as first argument of your function. Then in your function you can find the request like this:
$request = $twig->getGlobals()['app']->getRequest();
So the whole function will look like this:
...
public function getFunctions() {
return [
new \Twig_SimpleFunction('xyz', function(\Twig_Environment $env) {
$request = $twig->getGlobals()['app']->getRequest();
}, [
'needs_environment' => true,
]),
];
}
...

How can I create a symfony twig filter?

For instance my bundle namespace is Facebook\Bundle\FacebookBundle\Extension.
Using this how can I create a twig extension ?
It's all here: How to write a custom Twig Extension.
1. Create the Extension:
// src/Facebook/Bundle/Twig/FacebookExtension.php
namespace Facebook\Bundle\Twig;
use Twig_Extension;
use Twig_Filter_Method;
class FacebookExtension extends Twig_Extension
{
public function getFilters()
{
return array(
'myfilter' => new Twig_Filter_Method($this, 'myFilter'),
);
}
public function myFilter($arg1, $arg2='')
{
return sprintf('something %s %s', $arg1, $arg2);
}
public function getName()
{
return 'facebook_extension';
}
}
2. Register an Extension as a Service
# src/Facebook/Bundle/Resources/config/services.yml
services:
facebook.twig.facebook_extension:
class: Facebook\Bundle\Twig\AcmeExtension
tags:
- { name: twig.extension }
3. Use it
{{ 'blah'|myfilter('somearg') }}
You can also create twig functions by using the getFunctions()
class FacebookExtension extends Twig_Extension
{
public function getFunctions()
{
return array(
'myFunction' => new Twig_Filter_Method($this, 'myFunction'),
);
}
public function myFunction($arg1)
{
return $arg1;
}
Use your function like this:
{{ myFunction('my_param') }}
The Twig_Filter_Method class is DEPRECATED since Symfony 2.1
Please use the Twig_SimpleFilter class instead as showed in the following example:
\src\Acme\Bundle\CoreBundle\Twig\DatetimeExtension.php
<?php
namespace Acme\Bundle\CoreBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DatetimeExtension extends \Twig_Extension
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFilters()
{
return array(
'dateFormat' => new \Twig_SimpleFilter('dateFormat', array($this, 'dateFormat')),
'datetimeFormat' => new \Twig_SimpleFilter('datetimeFormat', array($this, 'datetimeFormat'))
);
}
/**
* #param mixed $date
* #return string
*/
public function dateFormat($date)
{
$format = $this->container->getParameter('acme_core.date_format');
return $this->format($date, $format);
}
/**
* #param mixed $date
* #return string
*/
public function datetimeFormat($date)
{
$format = $this->container->getParameter('acme_core.datetime_format');
return $this->format($date, $format);
}
/**
* #param mixed $date
* #param string $format
* #throws \Twig_Error
* #return string
*/
private function format($date, $format)
{
if (is_int($date) || (is_string($date) && preg_match('/^[0-9]+$/iu', $date))) {
return date($format, intval($date, 10));
} else if (is_string($date) && !preg_match('/^[0-9]+$/', $date)) {
return date($format, strtotime($date));
} else if ($date instanceof \DateTime) {
return $date->format($format);
} else {
throw new \Twig_Error('Date or datetime parameter not valid');
}
}
public function getName()
{
return 'datetime_extension';
}
}
\src\Acme\Bundle\CoreBundle\Resources\config\services.yml
services:
acme_core.twig.datetime_extension:
class: Acme\Bundle\CoreBundle\Twig\DatetimeExtension
arguments: [#service_container]
tags:
- { name: twig.extension }
Usage example:
{{ value|datetimeFormat }}
Symfony documentation: http://symfony.com/doc/master/cookbook/templating/twig_extension.html
Twig documentation: http://twig.sensiolabs.org/doc/advanced.html#id3
None of the given answers worked for Symfony 3.4 and above.
For Symfony 3.4, 4.x
// src/TwigExtension/customFilters.php
namespace App\TwigExtension;
use Twig\TwigFilter;
class customFilters extends \Twig_Extension {
public function getFilters() {
return array(
new TwigFilter('base64_encode', array($this, 'base64_en'))
);
}
public function base64_en($input) {
return base64_encode($input);
}
}
And then in your twig template you can do
{{ 'hello world' | base64_encode }}
Thats it. For detailed explanation, you could check out the reference.
Reference: DigitalFortress

Resources