In normal way i can able to define an object through out the application by defining a service factory in configuration file global.php
i can get the object in controller by just calling $this->getServiceLocator()->get('mycollection')
My code as follows:
In global.php
service_manager' => array(
'factories' => array(
'mycollection'=> function($sm){
$collectionAdapter = new Collection();
$collectionAdapter->addItem("testvalue",'test');
return $collectionAdapter;
}
By adding in global file i can able to retrieve
//`var_dump($this->getServiceLocator()->get('mycollection')->getItem("test"));// will return testvalue`
through out the application
But my issue is that i dont know how to accomplish set values to the service from a controller
My requirement is that i need to set the service in one controller and retrieve in another module
i tried the following code in my IndexController album module
$this->getServiceLocator()->get('mycollection')->addItem('testvalue28','test8');
and in another module student IndexController called
//var_dump($this->getServiceLocator()->get('mycollection')->getItem("test8"));//
How can i accomplish the same which i set in global.php in a controller . or more clearly i need to store the collection values to the entire application at one instance in all modules
Edited
1)The function addItem will be set only based on controller action
2)Is there any thing similar to ZEND_REGISTERY where i can set a value form a particular request and retrieve in another action
//An application controller is define where i need to set different key value pair
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
public function authenticateAction()
{
//----other code---------
var_dump($this->getServiceLocator()->get('mycollection')->addItem('userauthenticationobj','userkey'));
//$redirect=module=user controller action =index
return $this->redirect()->toRoute($redirect);
}
}
In user module index action i need to get the key value pair which is set inside application module in index action. i know this can be done using a session or db or cookies but i want to implement this using a singleton instance through out the application. i don't know to define the correct term in oops so defining the situation
//User controller
namespace User\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
public function authenticateAction()
{
//----other code---------
var_dump($this->getServiceLocator()->get('mycollection')->addItem('userkey'));
//$redirect=module=user controller action =index
return $this->redirect()->toRoute($redirect);
}
}
//IN GLOBAL.PHP i defined
return array(
'service_manager' => array(
'factories' => array(
'mycollection'=> function($sm){
$collectionAdapter = new Collection();
$collectionAdapter->addItem("testvalue",'test');
return $collectionAdapter;
}
),
),
);
//user defined collection reference: http://www.sitepoint.com/collection-classes-in-php/
namespace Application\Adapter;
class Collection
{
private $items = array();
public function addItem($obj, $key = null)
{
if ($key == null)
{
$this->items[] = $obj;
}
else {
if (isset($this->items[$key]))
{
throw new \Exception("Key $key already in use.");
}
else
{
$this->items[$key] = $obj;
}
}
}
public function getItem($key)
{
if (isset($this->items[$key]))
{
return $this->items[$key];
}
else
{
throw new \Exception("Invalid key $key.");
}
}
}
If you are in the same request (this is, the user is not following links, or the page is not being refreshed) the service manager will keep the Collection alive and what you are trying should work.
But if you are redirecting the user to another controller/action, or the user has followed a link, submited a form, or whatever that causes a new page to be loaded, all the values created in the previous page wont exists anymore. If you need to persist them, you should use sessions, cookies, database, etc.
If the values are not set during the action, i.e you dont need a controller to be loaded, but you need all the controllers to be able to add values to the collection on the application bootstrap, no matter what controller is actually loaded, you can add some code to every module, in Module.php onbootstrap function. for instance, in every module's Module.pho, you do:
public function onBootstrap(MvcEvent $e) {
$sm = $e->getApplication ()->getServiceManager ();
$collection = $sm->get('mycollection');
$collection->addItem('testvalue_N','test_N');
}
and then, in every controller/action that is executed, you will have the collection with all the items added by all the modules
Related
I'm working on small PHP framework (for learning). It has a file with LoginController class containing a method with Route attribute (code below). Is there any way to get the name of the method in the Route attribute class using Reflection?
Class with method using attribute:
class LoginController {
#[Route('GET', '/login')]
public function index() {
// some code
}
}
"Route" attribute class:
use Attribute;
use ReflectionMethod;
#[Attribute]
class Route {
public function __construct($method, $routeUri) {
// Can I get the method name ("index") from attribute instead of writing it?
// And the class name?
$reflection = new ReflectionMethod(\App\Controllers\LoginController::class, 'index');
$closure = $reflection->getClosure();
// Register a route...
Router::add($method, $routeUri, $closure);
}
}
Reflection is an option, but please be aware that you will be looping over all the attributes of all the methods in a class (at least until a matching one is found). Of course, if all routes need to be registered, this isn't that bad.
$classRef = new ReflectionClass(LoginController::class);
foreach ($classRef->getMethods() as $method) {
$methodRef = new ReflectionMethod($method->class, $method->name);
foreach ($methodRef->getAttributes() as $attribute) {
if (
$attribute->getName() === 'Route'
&& $attribute->getArguments() === [$method, $routeUri]
) {
// you can register your route here
}
}
}
As far as classes go, the easiest way to go is just make an array with all your controller class names. There are packages out there that can detect all classes in a given namespace, which could be used for autodetecting your controllers.
In my app I've got a group of routes which need some bootstraping before dispatching.
To illustrate the situation:
There is a special routes group with prefix 'app'. All of this routes have also some params:
site.dev/app/index?age=11&else=af3fs4ta21
Without these params user shouldn't be allowed to access route. I've got it done by creating a simple route middleware.
if (!$request->exists('age') || !$request->exists('else')) {
return redirect('/');
}
Next step is to initialize a class which takes route parameters as a construct arguments. Then param "else" is being used as a argument to db calls. I need to access this class in every route from /app route group.
In order to achive that I tried setting up a serviceprovider:
public function register()
{
$this->app->singleton(Dual::class, function ($app) {
return new Dual($this->app->request->all());
});
}
Then I created a special controller extending BaseController and passing Dual class to its constructor.
class DualController extends Controller
{
public function __construct(Request $request, Dual $dual)
{
$this->middleware(\App\Http\Middleware\DualMiddleware::class);
$this->dual = $dual;
}
}
And then every single controller is extending DualController and accessing Dual class by $this->dual->method().
It is working if route params are in their place and there is already a row in a database.
The problem
This middleware is executed AFTER ServiceProvider & DualController are initializing class Dual. So, middleware is not really working. If route params are not present it is going to fail.
Moreover, in case that there is no required row in database for some reason, Dual class will not be initialized (as it depends on calls to db) and whole app will crash saying that I am trying to perform operations on null.
Desired behaviour
First check route for params presence.
Second, check if there is row in db with key from route.
Third - try to initialize Dual class and pass it to all controllers used by route group /app.
If any of the steps fail -> display proper message.
Part of dual class:
class Dual
{
protected $client = null;
public $config = [];
public function __construct($config)
{
$this->config = $config;
$this->bootstrap();
}
public function getEli()
{
$eli = Eli::where(['else' => $this->config['else']])->first();
return $eli;
}
public function instantiateClient()
{
$client = Client::factory(Client::ADAPTER_OAUTH, [
'entrypoint' => $this->getEli()->eli_url,
'client_id' => '111',
'client_secret' => '111',
]);
$client->setAccessToken($this->getEli()->accessToken()->first()->access_token);
return $client;
}
public function getClient()
{
if ($this->client === null)
{
throw new \Exception('Client is NOT instantiated');
}
return $this->client;
}
public function bootstrap()
{
$this->client = $this->instantiateClient();
}
You can do this in middleware:
$isElseExists = Model::where('else', request('else'))->first();
if (request('age') && request('else') && $isElseExists) {
return $next($request);
} else {
return back()->with('error', 'You are not allowed');
}
If everything is fine, controller method will be executed. Then you'll be able to inject Dual class without any additional logic.
If something is wrong, a user will be redirected to previous URI with error message flashed into session.
For each request I have to load or, at least, create instance of a MyUser, which contains username, some internal permissions info, link to avatar and so on.
The thing is that I need this info for each and every controller and, for most of the views (to render or not to render some controls depending on user status and permissions).
It sounds like the need for a global variable, created at the time request being handled. What is the best way to solve this problem?
Override CWebUser (which is what you call when you issue Yii::app()->user) with your custom class WebUser (placed in the components or other folder that has it's classes autoincluded), and define some getters like it is done with getRole() example below:
<?php
class WebUser extends CWebUser {
private $_model = null;
function getRole() {
if($user = $this->getModel()){
return $user->userRole->name;
}
}
private function getModel(){
if (!$this->isGuest && $this->_model === null){
$this->_model = User::model()->findByPk($this->id);
}
return $this->_model;
}
}
If you user the custom class instead of CWebUser, you have to explicitly tell which class to use in application's config:
'user'=>array(
'class' => 'WebUser',
// …
),
You can create (or inject) an instance of MyUser in the constructor of your base controller, and set it to a public property:
//i am not familiar with Yii naming conventions, so ignore class name etc
class BaseController
{
public $user;
//presuming you can inject, if not $user = new MyUser();
function __construct(MyUser $user){
$this->user = $user;
}
}
Then all controllers that inherit BaseController can access if they need to:
class HomeController extends BaseController
{
function someAction(){
$name = $this->user->name;
}
}
And regardless of whether an action accesses the instance, its available in all views, without passing as a parameter to render:
//someview
echo $this->user->name;
I want to use this code in my application:
class ControllerExtension extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
//etc.
}
}
But where do I put it and how do i activate it? I'm guessing it's something to do with the services.yml file. I've used Event Listeners, but this is obviously different.
From your code snippet (http://justpaste.it/2caz), it seems that you missed the "return" keyword in your call to parent.
class ControllerExtension extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
if($this->getRequest()->getRequestFormat() == 'json') {
return new Response(json_encode($parameters));
} else {
// Missing 'return' in your snippet
return parent::render($view, $parameters, $response);
}
}
}
class MyController extends ControllerExtension
{
public function indexAction()
{
// This should now work
return $this->render(...);
}
}
You can put it in your bundle's Controller directory i.e src/YourNamespace/YourBundleName/Controller/ControllerExtension.php.
Make sure you provide the appropriate namespace in that file:
namespace YourNamespace\YourBundleName\Controller;
class ControllerExtension extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
...
To use it, either create a route for it in src/YourNamespace/YourBundlename/Resources/config/routing.yml
or
extend it:
namespace YourNamespace\YourBundleName\Controller;
class OtherController extends ControllerExtension
{
...
If what you are actually looking to do is override another bundle's controller, see the cookbook which describes overriding controllers.
Edit:
As far as I know, there's no way to automatically make this controller somehow take effect. You can have each of your controllers extend it as I've indicated above.
You might be able to create an event listener and use the response event to somehow change the response if the format is json. But, I'm not sure how you would access the view data from the event listener.
I have this thing that I need in multiple places:
public function init()
{
$fbLogin = new Zend_Session_Namespace('fbLogin'); #Get Facebook Session
if(!$fbLogin->user) $this->_redirect('/'); #Logout the user
}
These two lines:
$fbLogin = new Zend_Session_Namespace('fbLogin'); #Get Facebook Session
if(!$fbLogin->user) $this->_redirect('/'); #Logout the user
Whats the best way to do it in ZendFramework?To create a plugin or? I mean I want to execute it in multiple places but If I need to edit it I want to edit it in one place.
Here is an example of an Action Helper that you can call from your controllers easily.
<?php
class My_Helper_CheckFbLogin extends Zend_Controller_Action_Helper_Abstract
{
public function direct(array $params = array())
{
// you could pass in $params as an array and use any of its values if needed
$request = $this->getRequest();
$view = $this->getActionController()->view;
$fbLogin = new Zend_Session_Namespace('fbLogin'); #Get Facebook Session
if(!$fbLogin->user) {
$this->getActionController()
->getHelper('redirector')
->gotoUrl('/'); #Logout the user
}
return true;
}
}
In order to use it, you have to tell the helper broker where it will live. Here is an example code you can put in the bootstrap to do so:
// Make sure the path to My_ is in your path, i.e. in the library folder
Zend_Loader_Autoloader::getInstance()->registerNamespace('My_');
Zend_Controller_Action_HelperBroker::addPrefix('My_Helper');
Then to use it in your controller:
public function preDispatch()
{
$this->_helper->CheckFbLogin(); // redirects if not logged in
}
It doesn't go into much detail, but Writing Your Own Helpers is helpful as well.
If you need this check in every Controller you could even set up a baseController from which you extend instead of the default one:
class My_Base_Controller extends Zend_Controller_Action
{
public function init()
{ ...
class IndexController extends My_Base_Controller
{ ...
Shift your init() into the base controller and you don't need to repeat yourself in every specific controller.
Need a varying init() in a specific controller?
class FooController extends My_Base_Controller
{
public function init()
{
parent::init();
...