Laravel 5: How to custom error function in validator request? - laravel-5.1

My custom validation request:
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
class AccountPostRequest extends Request
{
public function authorize() {
return true;
}
public function rules() {
return [
'username' => 'required|alpha_dash'
];
}
public function message() {
return [
'username.required' => 'input your email',
'username.alpha_dash' => 'email format error'
];
}
protected function formatErrors(Validator $validator) {
return $validator->errors()->all();
}
//here is my question, how to invoke this callback when the validation fails,
//or has any other function like the "before" or "after" filters
protected function callback(Validator $validator) {
if ($validator->fails()) {
//do some thing
} else {
//do some thins
}
}
}
?>
My controller:
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\AccountPostRequest;
class AccountController extends Controller
{
public function login(AccountPostRequest $request) {
//...
}
}
As you know, if the request validate failed, it will not continue to execute the login function of AccountController.
My question is how to invoke the callback when the validation fails, or has any other function like the "before" or "after" filters?

All form request that are validated are going through a pipeline in Illuminate\Foundation\Http\FormRequest which uses a trait called Illuminate\Validation\ValidatesWhenResolvedTrait. This class has contains all the classes you may want to modify and overwrite.
For example, you can simply copy and paste the failedValidation method and customize it.
/**
* Handle a failed validation attempt.
*
* #param \Illuminate\Contracts\Validation\Validator $validator
* #return mixed
*/
protected function failedValidation(Validator $validator)
{
// Do something awesome...
}

Related

Symfony 5.4 with OAuth Keycloak connect Error must implement AuthenticatorInterface

I'm trying to implement an OAuth connection with Keycloak on Symfony 5.4, when I display a page of my application, all works fine, I have the keycloak login page, but after validate, I have this error :
Argument 1 passed to Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator::__construct() must implement interface Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface, instance of App\Security\KeycloakAuthenticator given, called in /var/www/oauth-symfony/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php on line 60
Obviously I tried to add implements AuthenticatorInterface I hadd to add authenticate() and createToken() methods, but even with that the implementations still not works.
KeycloakAuthenticator.php
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Class KeycloakAuthenticator
*/
class KeycloakAuthenticator extends SocialAuthenticator
{
/**
* ClientRegistry: the OAuth client manager
* EntityManagerInterface: to read/write in database
* RouterInterface: read a route/URL
*/
private $clientRegistry;
private $em;
private $router;
public function __construct(
ClientRegistry $clientRegistry,
EntityManagerInterface $em,
RouterInterface $router
)
{
$this->clientRegistry = $clientRegistry;
$this->em = $em;
$this->router = $router;
}
public function start(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $authenticationException = null): RedirectResponse
{
return new RedirectResponse(
'/oauth/login', // might be the site, where users choose their oauth provider
Response::HTTP_TEMPORARY_REDIRECT
);
}
public function supports(Request $request): ?bool
{
return $request->attributes->get('_route') === 'oauth_check';
}
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getKeycloakClient());
}
public function getUser($credentials, \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider)
{
$keycloakUser = $this->getKeycloakClient()->fetchUserFromToken($credentials);
//existing user ?
$existingUser = $this
->em
->getRepository(User::class)
->findOneBy(['keycloakId' => $keycloakUser->getId()]);
if ($existingUser) {
return $existingUser;
}
// if user exist but never connected with keycloak
$email = $keycloakUser->getEmail();
/** #var User $userInDatabase */
$userInDatabase = $this->em->getRepository(User::class)
->findOneBy(['email' => $email]);
if($userInDatabase) {
$userInDatabase->setKeycloakId($keycloakUser->getId());
$this->em->persist($userInDatabase);
$this->em->flush();
return $userInDatabase;
}
//user not exist in database
$user = new User();
$user->setKeycloakId($keycloakUser->getId());
$user->setEmail($keycloakUser->getEmail());
$user->setRoles(['ROLE_USER']);
$this->em->persist($user);
$this->em->flush();
return $user;
}
public function onAuthenticationFailure(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
public function onAuthenticationSuccess(Request $request, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token, string $providerKey)
{
// change "app_homepage" to some route in your app
$targetUrl = $this->router->generate('dashboard');
return new RedirectResponse($targetUrl);
}
/**
* #return \KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient
*/
private function getKeycloakClient()
{
return $this->clientRegistry->getClient('keycloak');
}
}
security.yml
security:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
entry_point: form_login
form_login:
login_path: oauth_login
custom_authenticator: App\Security\KeycloakAuthenticator
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
- { path: ^/dashboard, roles: ROLE_USER }
OAuthController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient;
class OAuthController extends AbstractController
{
/**
* #Route("/oauth/login", name="oauth_login")
*/
public function index(ClientRegistry $registry): RedirectResponse
{
/**#var KeycloakClient $client */
$client = $registry->getClient('keycloak');
return $client->redirect();
}
/**
* #Route("/oauth/callback", name="oauth_check")
*/
public function check(){}
}
You need replace the class to extend by OAuth2Authenticator 1.
Add the typing ": ?Response" on onAuthenticationSuccess and onAuthenticationFailure
And create the function "authenticate" to return PassportInterface

ZF2: inject variables in constructor of service

Is there a way to create a new instance of a service and adding constructor parameters? I am a bit new to depency injection, and I find I can only add services as constructor parameters and not runtime variables through a factory.
The code I have looks similar to this:
Class MyService
{
private $name;
private $active;
public function __construct($name,$active)
{
$this->name = $name;
$this->active = $active;
}
}
$myService = $this->getServiceLocator()->get('MyService')
Yes there is a way by using the MutableCreationOptionsTrait trait in your factory.
class YourServiceFactory implements FactoryInterface, MutableCreationOptionsInterface
{
use MutableCreationOptionsTrait;
public function createService(ServiceLocatorInterface $serviceLocator)
{
if (isset($this->creationOptions['name'])) {
// do something with the name option
}
if (isset($this->creationOptions['active'])) {
// do something with the active option
}
$yourService = new YourService(
$this->creationOptions['active'],
$this->creationOptions['name']
);
return $yourService;
}
}
The above shown code implements the trait for creation options. With this trait you can handle an array of options in your factory. Call your service like in the following code.
$yourService = $this->getServiceLocator()->get(YourService::class, [
'active' => true,
'name' => 'Marcel',
]);
Easy as pie. ;)
Let's say your service exists:
Class MyService
{
private $name;
private $active;
public function __construct($name,$active)
{
$this->name = $name;
$this->active = $active;
}
}
If instead of ->get()'ing it, you could ->build() it :)
class SomeFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return $container->build(MyService::class, ['name' => "Foo", 'active' => true]);
// Line below works as well, using a variable requested name, handy for an AbstractFactory of some kind (e.g. one that creates different adapters in the same way with same params)
// return $container->build($requestedName, ['name' => "Foo", 'active' => true]);
}
}
Check out the ServiceManager build() function
Note: Not sure since when it's present, this works in higher versions of ZF2 and all of ZF3.
Note 2: Both get() and build() call function doCreate(). Function declaration:
private function doCreate($resolvedName, array $options = null)
get() does: $object = $this->doCreate($name);
build() does: return $this->doCreate($name, $options);

Zend\Form\Form not displayin error message from custom validator

I'v creatated a custom validator:
class MyValidator extends AbstractValidator
{
const ERROR_CONST = 'error';
protected $dbAdapter;
protected $messageTemplates = array(
self::ERROR_CONST => "Error msg for '%value%'."
);
public function __construct($dbAdapter)
{
$this->dbAdapter = $dbAdapter;
}
public function isValid($value, $context = null)
{
$this->setValue($value);
/**
* Do validation against db
*/
if(/* Not valid */){
$this->error(self::ERROR_CONST);
return false;
}
return true;
}
}
The validation work, I've been able to test it. What doesn't work is the output of the error message using
echo $this->formElementErrors($form->get('action'));
All that is outputted is an empty UL. Is this a translator issue? When I do a get_class on $this->getTranslator() in the validator I get the validator class name. When I var_dump $this->getTranslator() it outputs null. Do I need to set a translator for this to work and where would be the best place to set that translator so that it's system wide for my own validators?
Because you define a __construct method for your validator class, the parent __construct is not implicitly called:
http://php.net/manual/en/language.oop5.decon.php (see the note)
You should modify your __construct method:
public function __construct($dbAdapter)
{
$this->dbAdapter = $dbAdapter;
//parent::__construct($options);
parent::__construct(null); // or (void)
}
As you can see, $messageTemplates and $messageVariables are "loaded" from AbstractValidator::__construct, for being used in some methods ( error included):
https://github.com/zendframework/zf2/blob/master/library/Zend/Validator/AbstractValidator.php#L73-L79
Maybe you forgot to add messageVariables ?
/**
* Message variables
* #var array
*/
protected $messageVariables = array(
'value' => 'value',
);

Silex passing app and request to controller classes

I want a simple way to access $app and $request in my controller classes. The document says to do this,
public function action(Application $app, Request $request) {
// Do something.
}
but it doesn't look right to have to inject $app and $request to every method. Is there a way to include $app and $request to every controller by default, maybe using the constructor? I'd like to be able to use it as $this->app.
Thanks.
In the Controllers as Services part of the documentation you can see how to inject dependencies to controller classes via the constructor - in that case a repository.
It's possible :
Create a ControllerResolver.php somewhere in your project and put this inside :
namespace MyProject;
use Silex\ControllerResolver as BaseControllerResolver;
class ControllerResolver extends BaseControllerResolver
{
protected function instantiateController($class)
{
return new $class($this->app);
}
}
Then register it in your app (before $app->run();):
$app['resolver'] = function ($app) {
return new \MyProject\ControllerResolver($app, $app['logger']);
};
Now you can create a base controller for your app, for example :
namespace MyProject;
use Silex\Application;
use Symfony\Component\HttpFoundation\Response;
abstract class BaseController
{
public $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getParam($key)
{
$postParams = $this->app['request_stack']->getCurrentRequest()->request->all();
$getParams = $this->app['request_stack']->getCurrentRequest()->query->all();
if (isset($postParams[$key])) {
return $postParams[$key];
} elseif (isset($getParams[$key])) {
return $getParams[$key];
} else {
return null;
}
}
public function render($view, array $parameters = array())
{
$response = new Response();
return $response->setContent($this->app['twig']->render($view, $parameters));
}
}
And extend it :
class HomeController extends BaseController
{
public function indexAction()
{
// now you can use $this->app
return $this->render('home.html.twig');
}
}

Custom validator, not being executed when used in a form

i have created a custom validator but when I want to use it, it seems that it is never executed!
the validator :
class sfTestUrlValidator extends sfValidatorUrl {
public function initialize($context, $parameters = null) {
// Initialize parent
parent::initialize($context);
}
public function execute(&$value, &$error) {
if($value == "http://www.librosweb.es/")
{
//$error = "noooooooooooooo";
return true;
}
else return false;
}
}
in the configure method of a form, i do like that :
public function configure() {
.....
....
'url' => new sfTestUrlValidator(),
You need to override sfValidatorBase::doClean method and not some not-existent execute method and throw exception intead of returning true/false. Have a look at any validator class, e.g. sfValidatorString. However in your case, I would simply use sfValidatorChoice with one choice
public function configure()
{
'url' => new sfValidatorChoice(array('choices' => array(
'your.website.url',
)));
}

Resources