I'm working on a ZF2 project, and using ZfcUser to manage website users.
I just want to know is it possible to have a child route to the zfcuser route?
Something like that, in the configuration of my module:
return [
'router' =>
[
'routes' =>
[
'admin' =>
[
'type' => 'Segment',
'options' =>
[
'route' => '/admin',
'defaults' =>
[
'__NAMESPACE__' => 'admin',
'controller' => 'admin.index',
'action' => 'index',
],
],
'may_terminate' => true,
'child_routes' =>
[
'zfcuser' =>
[
'type' => 'Literal',
'options' =>
[
'route' => '/account',
]
],
],
],
],
],
];
This is a problem I've tried overcoming recently, not using ZfcUser but whilst developing my own modules. There are a few solutions but the most suitable would depend on the type of application you're developing.
The easiest solution would be to override the zfcuser route path and prefixing it with admin.
return [
'router' => [
'routes' => [
'zfcuser' => [
'options' => [
'route' => '/admin/user',
],
],
],
],
];
If you're like me and want all you admin routes contained under a single route then you're better off removing the zfcuser route completely and implementing your own which could utilize the ZfcUser controllers.
namespace Application;
use Zend\ModuleManager\ModuleEvent;
use Zend\ModuleManager\ModuleManager;
class Module
{
public function init(ModuleManager $moduleManager)
{
$events = $moduleManager->getEventManager();
$events->attach(ModuleEvent::EVENT_MERGE_CONFIG, array($this, 'onMergeConfig'));
}
public function onMergeConfig(ModuleEvent $event)
{
$configListener = $event->getConfigListener();
$configuration = $configListener->getMergedConfig(false);
if (isset($configuration['router']['routes']['zfcuser']))
{
unset($configuration['router']['routes']['zfcuser']);
}
$configListener->setMergedConfig($configuration);
}
}
Related
I am using Yii authclient to use social login. I had set everything as it is defined in docs but when I try to login with google it does not call onAuthSuccess method. When I try to login it just redirects me to returnUrl but not authenticated.
Here is my code;
config/main.php
'authClientCollection' => [
'class' => \yii\authclient\Collection::class,
'clients' => [
'google' => [
'class' => \yii\authclient\clients\Google::class,
'clientId' => *********, //changed for issue purpose
'clientSecret' => *********, //changed for issue purpose
'returnUrl' => 'http://localhost/site/landing',
],
],
]
controllers/SiteController
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'signup', 'auth'],
'rules' => [
[
'actions' => ['signup', 'auth'],
'allow' => true,
'roles' => ['?'],
],
[
'actions' => ['logout'],
'allow' => true,
'roles' => ['#'],
],
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post'],
'create-storyboard' => ['post'],
],
],
];
}
/**
* {#inheritdoc}
*/
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'onAuthSuccess'],
],
];
}
public function onAuthSuccess($client)
{
(new AuthHandler($client))->handle();
}
If you set returnUrl the user is from auth provider redirected directly to the url you've set in that property.
In your case the returnUrl says google, that it should redirect user to http://localhost/site/landing. But there is nothing in your site/landing action that would call the onAuthSuccess.
You need to let user come back to site/auth and redirect them after processing response from OAuth provider. To do that remove the returnUrl from config. That will make the authclient to use default return url which is the action that started the auth process.
Then modify your onAuthSuccess to redirect users to site/landing like this:
public function onAuthSuccess($client)
{
(new AuthHandler($client))->handle();
$this->redirect(['site/landing']);
}
I had solved the problem with the help from #Michal Hynčica. The problem was in my returnUrl which means with authentication url it must follow to authenticate rather the redirecting after authentication. So all I need to do was changing it to as below.
'returnUrl' => 'http://localhost/site/auth?authclient=google'
Also don't forget to add same returnUrl to your google console's redirect url.
In a Fieldset I have an Element\Radio foo and Element\Text bar.
public function init()
{
$this->add(
[
'type' => 'radio',
'name' => 'foo',
'options' => [
'label' => _('foo'),
'value_options' => [
[
'value' => 'a',
'label' => 'a',
'selected' => true
],
[
'value' => 'b',
'label' => 'b'
]
]
]
...
]);
$this->add(
[
'name' => 'bar',
'type' => 'text',
'options' => [
'label' => 'bar',
...
],
...
]);
}
The validation of the field bar is depending on the selected foo option. It's easy to implement, if I can get the selected value of foo:
public function getInputFilterSpecification()
{
return [
'bar' => [
'required' => $this->get('foo')->getCheckedValue() === 'a',
...
],
];
}
But there is no method Radio#getCheckedValue(). Well, I can iterate over the $this->get('foo')->getOptions()['value_options'], but is it really the only way?
How to get (in the Fieldset#getInputFilterSpecification()) the selected option of a Zend\Form\Element\Radio?
The selected option gets POSTed to the server along with everything else from the HTML form and is all of this is available in validators through the $context array.
You can create a conditionally required field by using a callback validator and the $context array like this:
public function getInputFilterSpecification() {
return [
'bar' => [
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'required' => true,
'validators' => [
[
'name' => 'Callback',
'options' => [
'callback' => function ($value, $context) {
return $context['foo'] === 'a'
},
'messages' => [
\Zend\Validator\Callback::INVALID_VALUE => 'This value is required when selecting "a".'
]
]
]
]
],
];
}
That would check if 'foo' is equal to 'a', i.e. option 'a' is selected and return true when it is, which marks the input as valid, and false when it's not, marking the input invalid.
I have an abstract service class SAbstract which is inherited by ConcreteServiceA and ConcreteServiceB. Now I am instantiating ConcreteServiceA in the factory class of my controller and inject the service in my controller.
In a specific action in my controller I want to exchange ConcreteServiceA with ConcreteServiceB to change behavior. Because they have same interface (abstract class SAbstract) I could inject it in my controller as well (the services are a Strategy-Pattern).
But I don't want to instantiate ConcreteServiceB directly in my controller to keep my code clean (for easy refactoring and exchanging behavior).
A possible solution is to create a second factory for my controller which injects ConcreteServiceB instead of ConcreteServiceA but then I have duplicated lots of code which is not good...
Another solution would be to inject both services in my controller (but this "smells" like bad code).
Is a delegator factory the right way to do this? Then I have to implement setters in my controller...
Is there a better way?
I tried to schematically visualize my class relationships.
AbstractService <|--<inherit>- ConcreteServiceA
AbstractService <|--<inherit>- ConcreteServiceB
Controller -<use>--> AbstractService
Controller:ActionA -<use>--> ConcreteServiceA:exportAction()
Controller:ActionB -<use>--> ConcreteServiceB:exportAction()
In a specific action in my controller I want to exchange ConcreteServiceA with ConcreteServiceB to change behavior. Because they have same interface.
You can configure the route to use a different controller service name for each action; then configure a controller factory to inject the required service using configuration.
The route config could look like this.
'router' => [
'routes' => [
'foo' => [
'type' => 'literal',
'options' => [
'route' => '/foo',
'defaults' => [
'controller' => 'MyControllerWithFooService',
'action' => 'actionThatNeedsFooService',
],
],
],
'bar' => [
'type' => 'literal',
'options' => [
'route' => '/bar',
'defaults' => [
'controller' => 'MyControllerWithBarService',
'action' => 'actionThatNeedsBarService',
],
],
],
],
]
Then add the config for the services and controllers.
'app_config' => [
'MyControllerWithFooService' => [
'service_name' => 'FooService',
],
'MyControllerWithFooService' => [
'service_name' => 'BarService',
],
],
'service_manager' => [
'factories' => [
'FooService' => 'FooServiceFactory'
'BarService' => 'BarServiceFactory'
],
],
'controllers' => [
'factories' => [
'MyControllerWithFooService' => 'MyControllerServiceFactory'
'MyControllerWithBarService' => 'MyControllerServiceFactory'
],
]
The MyControllerServiceFactory could be very simple.
class MyControllerServiceFactory
{
public function __invoke($controllerManager, $name, $requestedName)
{
$sm = $controllerManager->getServiceLocator();
$config = $sm->get('config');
if (empty($config['app_config'][$requestedName])) {
throw new ServiceNotCreatedException('No config set!');
}
$serviceName = $config['app_config'][$requestedName]['service_name'];
$service = $sm->get($serviceName);
return new MyController($service);
}
}
I'm doing a registration form in ZF2, but I don't get how to validate. Validation seems not working.
I'm adding the validators array, but it doesn't work anyways. I don't know how can I fix that.
This is my code of controller:
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Form\Formularios;
use Zend\Db\Adapter\Adapter;
use Application\Modelo\Entity\Usuarios;
class FormularioController extends AbstractActionController
{
public $dbAdapter;
public function indexAction()
{
return new ViewModel();
}
public function registroAction()
{
if($this->getRequest()->isPost())
{
$this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter');
$u=new Usuarios($this->dbAdapter);
//echo "se recibió el post";exit;
$data = $this->request->getPost();
$u->addUsuario($data);
return $this->redirect()->toUrl($this->getRequest()->getBaseUrl().'/application/formulario/registro/1');
}else
{
//zona del formulario
$form=new Formularios("form");
$id = (int) $this->params()->fromRoute('id', 0);
$valores=array
(
"titulo"=>"Registro de Usuario",
"form"=>$form,
'url'=>$this->getRequest()->getBaseUrl(),
'id'=>$id
);
return new ViewModel($valores);
}
}
}
this is my form code with validator
class Formularios extends Form
{
public function __construct($name = null)
{
parent::__construct($name);
$this->add(array(
'name' => 'name',
'required' => true,
'allow_empty' => false,
'options' => array(
'label' => 'Nombre Completo',
),
'attributes' => array(
'type' => 'text',
'class' => 'input'
),
'filters' => [ ['name' => 'StringTrim'], ],
'validators' => array(
array(
'name' => 'NotEmpty',
'options' => array(
'messages' => array(
\Zend\Validator\NotEmpty::IS_EMPTY => 'Ingrese Nombres.',
))))
));
$this->add(array(
'name' => 'lastname',
'required' => true,
'options' => array(
'label' => 'Apellido',
),
'attributes' => array(
'type' => 'text',
'class' => 'input'
),
'validators' => array(
array(
'name' => 'NotEmpty',
'options' => array(
'messages' => array(
\Zend\Validator\NotEmpty::IS_EMPTY => 'Ingrese Apellidos.',
))))
));
Thanks in advance
First problem.
$data = $this->request->getPost(); should be $data = $this->getRequest()->getPost();
Second problem is that you call your validators direclty when you build your form in the view, which is wrong. The right way to do is via an inputFilter. Now, there are many ways to to this, for example: with or without a factory called from your model or via the for class with a form element manager
I will show you the model way with a factory since it's easier for new comers.
namespace MyModule\Model;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class MyModel implements InputFilterAwareInterface
{
/**
* #var null $_inputFilter inputFilter
*/
private $_inputFilter = null;
// some more code like exhnageArray get/set method
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add(
$factory->createInput([
'name' => 'id',
'required' => false,
'filters' => [
['name' => 'Int'],
],
])
);
$inputFilter->add(
$factory->createInput([
"name"=>"title",
"required" => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'NotEmpty'],
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
'max' => 200,
],
],
],
])
);
$inputFilter->add(
$factory->createInput([
"name"=>"text",
"required" => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'NotEmpty'],
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
],
],
],
])
);
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
Third proble. DO NOT EVER use serviceManager in controller. It's a really really really bad practice. Instead use a factory.
I'm trying to setup a ZF2 project (slightly different from the zf2skeleton) and I want to use composer to deal with all the autoloading, not only from the vendor ones (installed through composer) but also those modules I create.
For some reason, i can't get access to my Application module Index controller. I think its the autoloading that's not working... I just don't know what i might be doing wrong..
Thanks!
Files and data below:
This is my folder structure:
index.php
private
- modules
-- Aplication
--- src
----- Aplication
------- Controller
---------- IndexController.php
- vendor
-- zendframework
-- composer
-- autoload.php
- composer.json
- composer.phar
main.config.php
return array(
'modules' => array(
'Application',
),
'module_listener_options' => array(
'config_glob_paths' => array(
'private/config/autoload/{,*.}{global,local}.php',
),
'cache_dir' => realpath(dirname(__DIR__) . '/../../data/cache'),
'module_paths' => array(
realpath(__DIR__ . '/../module'),
realpath(__DIR__ . '/../vendor'),
),
),
);
index.php
include_once 'private/vendor/autoload.php';
$application = Zend\Mvc\Application::init(include 'private/config/main.config.php');
return $application;
composer.json
{
"repositories": [
{
"type": "composer",
"url": "http://packages.zendframework.com/"
}
],
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": "2.*"
},
"autoload": {
"psr-0": {
"Application\\": "module/Application/src"
}
}
}
Application config
return array(
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'index',
),
),
),
),
),
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController'
),
),
);
IndexController
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController {
public function indexAction() {
die("app ok");
}
}
First, from what I see, you don't run your application :
$application = Zend\Mvc\Application::init(include 'private/config/main.config.php');
$application->run();
Another point is that, you don't have to use composer to load your modules. As your application config already include the module directory, the Module loading process will automatically scan the directory and use the loading strategy defined at the level your Module classes.