I am new to laravel and would like to fetch list of all controllers and list of action in that controller.
Just want to know if there is a way to get a list of all Controllers as well as all their Methods by code?
Thanks,
DJ
By how you are explaining the need for you to know the controller actions, it seems that the actions are already mapped to routes, which means you can use the routes to get the list of mapped controllers and actions. The following code will generate an array of the registered route controller actions:
$controllers = [];
foreach (Route::getRoutes()->getRoutes() as $route)
{
$action = $route->getAction();
if (array_key_exists('controller', $action))
{
// You can also use explode('#', $action['controller']); here
// to separate the class name from the method
$controllers[] = $action['controller'];
}
}
This will ignore routes that have Closures mapped, which you don't need. Mind you, you might need to filter out any matches from routes registered by third party packages.
That worked for me.
Extracting all controllers (App\Http\Controllers)
$controllers = require_once base_path('vendor/composer/autoload_classmap.php');
$controllers = array_keys($controllers);
$controllers = array_filter($controllers, function ($controller) {
return strpos($controller, 'App\Http\Controllers') !== false;
});
$controllers = array_map(function ($controller) {
return str_replace('App\Http\Controllers\\', '', $controller);
}, $controllers);
dd($controllers);
Extracting all methods in a specific controller using ReflectionClass
$namespace = "App\Http\Controllers";
$controller = "TestController";
$controller_class = new ReflectionClass($namespace.'\\'.$controller);
$controller_methods = $controller_class->getMethods(ReflectionMethod::IS_PUBLIC);
dd($controller_methods);
One catch though. You may need to run composer dump-autoload after creating a controller.
Old code:
public static function Controllers()
{
$controllers = require_once base_path('vendor/composer/autoload_classmap.php');
$controllers = array_keys($controllers);
$controllers = array_filter($controllers, function ($controller) {
return strpos($controller, 'App\Http\Controllers') !== false;
});
$controllers = array_map(function ($controller) {
return str_replace('App\Http\Controllers\\', '', $controller);
}, $controllers);
return $controllers;
}
Edited:
This Code is much better:
public static function Controllers()
{
$classes = array_filter(get_declared_classes(), function ($class) {
$isController = substr($class, -10) == 'Controller';
$isNotPlainController = $isController && substr($class, -11) != '\Controller';
return $isNotPlainController;
});
//Optional: to clear controller name from its namespace
$controllers=array_map(function ($controller){
return last(explode('\\',$controller));
},$classes);
//Optional: to reset keys of array to start from 0,1,2,...etc
$controllers = array_values($controllers);
return $controllers;
}
Try running this:
$classes = get_declared_classes();
foreach ($classes as $class) {
if (is_subclass_of($class, 'App\Http\Controllers\Controller')) {
echo $class . '<br />';
$methods = get_class_methods($class);
foreach ($methods as $method)
echo '--- ' . $method . '<br />';
}
}
It should output all your controllers (anything inherited from the Controller class) and their methods.
I'm not sure how to tell which ones of the methods are actual controller actions though. If you use controller routes that would be the ones starting with "get", "post" or "any", but if you're using direct links that's not the case.
Try this
$controllers = [];
$i=0;
foreach (Route::getRoutes()->getRoutes() as $route)
{
$action = $route->getAction();
if (array_key_exists('controller', $action))
{
$_action = explode('#',$action['controller']);
$_namespaces_chunks = explode('\\',$_action[0]);
$controllers[$i]['controller'] = end($_namespaces_chunks);
$controllers[$i]['action'] = end($_action);
$controllers[$i]['namespace'] = $action['controller'];
$controllers[$i]['route'] = $route;
}
$i++;
}
dump($controllers);
Grep can be used to print a list of all actions per controller on unix systems.
Open a console in the laravel project root and execute the following command:
grep "public function" app/Http/Controllers/* -r
Related
I am trying to load (include file) the GetRiskSummaryCommandHandler.php (GetRiskSummary\CommandHandler) at runtime dynamically, while resolving Route api/risks to HandleCommand (of class CommandHandler) method.
How can I do this? If not can I modify any taken approach including modifying autoloader?
My api class snippet looks like this:
API.php
<?php
abstract class API
{
public function processRequest()
{
$id1 = $this->requestObj->id1;
//$id2 = $this->requestObj->id2;
$endpoint1 = $this->requestObj->endpoint1;
$endpoint2 = $this->requestObj->endpoint2;
$isDestination = in_array($id1, ['first', 'prev', 'next', 'last']);
$numSetEndpoints = (int)isset($endpoint1) + (int)isset($endpoint2);
switch($numSetEndpoints)
{
case 0:
if ($isDestination)
return json_decode($this->_response("No Endpoint: ", $endpoint1));
return json_decode($this->_response("ProjectAIM API"));
case 1:
$className = $endpoint1.'Controller';
break;
case 2:
$className = $endpoint2.'Controller';
break;
}
$class = "GetRiskSummaryCommandHandler";
$method = "HandleCommand";
if (class_exists($class))
{
if (method_exists($class, $method))
{
$response = (new $class($this->requestObj))->{$method}($this->requestObj);
if ($response['Succeeded'] == false)
{
return $response['Result'];
}
else if ($response['Succeeded'] == true)
{
header("Content-Type: application/json");
return $this->_response($response);
}
else if ($response['Result'])
{
header("Content-Type: text/html");
return $this->_response($response);
}
}
}
}
**Command Handler Snippet, uses a Route Attiribute
GetRiskSummaryCommandHandler.php
<?php
namespace GetRiskSummary;
use Infrastructure\CommandHandler;
class GetRiskSummaryCommandHandler extends CommandHandler
{
#[Route("/api/risks", methods: ["GET"])]
public function HandleCommand()
{
$qb = $this->getEntityManager()
->createQueryBuilder();
$qb->select('*')
->from('Risks', 'Risks')
->orderBy('RiskID', 'DESC');
$query = $qb->getQuery();
return $query->getResult();
}
}
Autoloader.php
<?php
namespace Autoloader;
class Autoloader
{
private static function rglob($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files, self::rglob($dir.'/'.basename($pattern), $flags));
}
return $files;
}
public static function ClassLoader($path)
{
$pathParts = explode("\\", $path);
$path = $pathParts[array_key_last($pathParts)];
$matches = self::rglob("*/$path*");
foreach ($matches as $name)
{
$filePath = realpath($name);
if (file_exists($filePath))
include $filePath;
}
}
}
spl_autoload_register("AutoLoader\AutoLoader::ClassLoader");
I think the biggest missing feature of PHP Attributes would be that as far as I'm aware PHP Attributes are static and aren't evaluated at runtime. They are just glorified comments.
If it was true, we could do some truly amazing things like non-intrusively attaching pre/post-processing functions, just like what you wrote.
They work just like in symfony or doctrine. You can't use variables in them and you must write your own code to search your php class tree for attributes in the code and use php reflection to obtain the information and run the attributes.
To be honest it's pretty disappointing, although maybe it's just the first step that will be fixed in later versions of PHP.
But for now, I think what you're asking for is impossible. Because they are not running dynamically like you appear to be asking.
So my issue is, I have no idea how to handle page not found handling, Since routing runs every time a route is added if you do anything other than the first route it'll have 2 outputs.
Routes.php
Route::set('index.php', function() {
Index::CreateView('Index');
});
Route::set('test', function() {
Test::CreateView('Test');
});
?>
Routes.php (class)
<?php
class Route {
public static $validRoutes = array();
public static function set($route, $function) {
self::$validRoutes[] = $route;
$url = $_GET['url'];
if($url == $route) {
$function->__invoke();
die();
}
if(!in_array($url, self::$validRoutes)) {
Controller::CreateView("404");
die();
}
}
}
?>
I'm trying to understand how I'd even handle if its not found.
How about something like:
<?php
class Router
{
public $routes = [];
public function add($route, $function)
{
$this->routes[$route] = $function;
}
public function route($path)
{
$function =
$this->routes[$path] ??
$this->routes['404'] ?? null;
if(is_null($function))
http_response_code(404) && exit('404 Not Found.');
$function->__invoke();
}
}
$router = new Router;
$router->add('foo', function() {
echo 'bar';
});
$router->add('greeting', function() {
echo 'hello earth';
});
$router->route('greeting');
Output:
hello earth
Rather than trying to resolve your routes upon each addition, you can add them all, and then resolve/dispatch/route later.
I've simplified the Router::routes array to use the paths as keys.
When resolving, if the path cannot be found (the index does not exist) it will try and check for a '404' key in the routes array. Failing that it responds with a basic 404.
I have a string, route, and i explode it and then i have a classname. $callback = explode('#', $callback); $callback[0] is the classname. I am making a router and based on the class and methed i return this.
I want to get the full namespace and when i use this then it works but now i want to make it dynamically.
How do i concat BaseController to the ::class?
BasController::class
$fullclass = HERE I NEED THE FULL NAMESPACE$callback[0];
$class = new $fullclass;
$method = $callback[1];
$class->$method();
Exaple index.php
Router::route('/user/{id}/' , 'BaseController#show');
Router::execute($_SERVER['REQUEST_URI']);
Router:
namespace App;
class Router
{
private static $routes = array();
private function __construct()
{
}
private function __clone()
{
}
public static function route($pattern, $callback)
{
$pattern = $pattern;
self::$routes[$pattern] = $callback;
}
public static function execute($url)
{
foreach (self::$routes as $pattern => $callback) {
if ($pattern == $url) {
$callback = explode('#', $callback);
$fullclass = __NAMESPACE__ . '\\Controllers\\' . $callback[0];
$class = new $fullclass;
$method = $callback[1];
$class->$method();
}else{
echo 404;
}
}
}
}
BasController::class returns a full class name in string in that case you can simply concat any string on it. If $callback[0] is a string you can do below code.
$fullclass = BasController::class . $callback[0];
$class = new $fullclass;
$method = $callback[1];
$class->$method();
I hope you are trying to retrieve namespace
echo (new \ReflectionClass($object))->getNamespaceName() ;
If you are in that scope try
__NAMESAPCE__
See this document if you are looking for autoload
What i was looking for is giving the class as param into the route
Router::route('/user/{id}/' , App\Controllers\BaseController::class , 'show');
I'm developing a WebApp which (as usual) must support customer specific functionalities.
To achieve it I plan to set the customer name in the local app configuration (config/autoload/local.php )
configuration file so that I can use it to call the specialized code later on.
The module folder structure is this:
/module/Application
/module/Application/config
/module/Application/src
/module/Application/src/Application
/module/Application/src/Application/Controller
/module/Application/src/Application/Controller/[customer_instance_name]
/module/Application/src/Application/Model
/module/Application/src/Application/Model/[customer_instance_name]
/module/Application/view
/module/Application/view/Application
/module/Application/view/Application/[action]
/module/Application/view/Application/[action]/[customer_instance_name]
Using a custom ViewModel I inject the specific customer name to the template path:
namespace Application\Model;
use Zend\View\Model\ViewModel;
use Zend\View\Resolver\TemplatePathStack;
use Zend\Mvc\Service\ViewTemplatePathStackFactory;
class MyViewModel extends ViewModel
{
private $customInstanceName;
private $pathStack;
/**
* Constructor
*
* #param null|array|Traversable $variables
* #param array|Traversable $options
*/
public function __construct($variables = null, $options = null)
{
parent::__construct ( $variables, $options );
$serviceLocator = MySingleton::instance()->serviceLocator;
$factory = new ViewTemplatePathStackFactory();
$this->pathStack = $factory->createService($serviceLocator);
$config = $serviceLocator->get('config');
if (isset($config['custom_instance_name']) AND ($config['custom_instance_name']!='')) {
$this->customInstanceName = $config['custom_instance_name'];
} else {
$this->customInstanceName = false;
}
}
/**
* Set the template to be used by this model
*
* #param string $template
* #return ViewModel
*/
public function setTemplate($template)
{
$this->template = (string) $template;
if ( $this->customInstanceName === false) {
return $this;
}
$pathComponents = explode('/', (string) $template);
$last = array_pop($pathComponents);
array_push($pathComponents, $this->customInstanceName);
array_push($pathComponents, $last);
$customTemplate = implode('/', $pathComponents);
if ($this->pathStack->resolve($customTemplate) !== false) {
$this->template = $customTemplate;
}
return $this;
}
}
Using the "Decorator Pattern" I can achieve the same customization level on my Models.
I'm having problem to handle specific behavior. In this case I plan to create custom Controllers extending
my base controller class, but I'unable to call those controllers since the routing is defined on the module
config (and I was unable to change it in runtime).
My questions are:
1) Is this approach correct, or there is a better way to do it?
2) If the approach is correct, how can I define a custom router to be used when the ServiceManager reads my routing config?
Just found a solution. Will register it here hoping someone will benefit from it.
All I had to do was to create a specific router class with a match method which returns the correct routing target for each customer controller, and add it to my module.config.php as the type for each action.
namespace TARGETNAMESPACE;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Mvc\Router\Http\RouteInterface;
use Zend\Mvc\Router\Http\RouteMatch;
use Zend\Mvc\Router\Http\Literal;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
class MyRouterLiteral extends Literal {
public function match(Request $request, $pathOffset = null) {
if (! method_exists($request, 'getUri')) {
return null;
}
$uri = $request->getUri();
$path = $uri->getPath();
if ($pathOffset !== null) {
if ($pathOffset >= 0 && strlen($path) >= $pathOffset && ! empty($this->route)) {
if (strpos($path, $this->route, $pathOffset) === $pathOffset) {
return new RouteMatch($this->getDefaults(), strlen($this->route));
}
}
return null;
}
if ($path === $this->route) {
return new RouteMatch($this->getDefaults(), strlen($this->route));
}
return null;
}
private function getDefaults() {
$aux = explode('\\', $this->defaults['controller']);
$last = array_pop($aux);
array_push($aux, '[CUSTOM_INSTANCE_NAME]');
array_push($aux, '[CUSTOM_INSTANCE_NAME]'.$last);
$result = $this->defaults;
$result['controller'] = implode('\\', $aux);
return $result;
}
}
To address all cases I had to create a second custom router (for segment routes) which follows the same rules and can be easily derived from the above code.
I have been trying to figure out a way to load models from different directory other than "application/models" directory.
I want to load my model from "application/modules".
I have tried the instructions by extending CI_Loader by following instructions provided in
http://ellislab.com/forums/viewthread/103276/#
but it is giving me error "Unable to locate the specified class: Model.php" whereas my core "Model.php" is there under "system/core" folder.
Even ,I have also tried to load model using
$this->load->model (APPPATH.'modules/mymodel.php');
but no success.
UPDATE:
I want my model loader to behave like this :
$this->load->module('mymodel')
and
$this->mymodel->mymodelmethod() !!
NOT like: $this->load->models('mymodel')
Is there any way i could load models from outside the "application/modules" folder ?
Please advise .Thanks
The problem is that the code you reference is for an older version of codeigniter. In older versions of codeigniter the CI_Model class was named Model. As the class Model doesn't exist in the latest version of php you are getting the error:
Unable to locate the specified class: Model.php
The part of the code where this is happening is towards the end (line 114):
if ( ! class_exists('Model'))
{
load_class('Model', FALSE);
}
If you check your system/core/Loader.php you will see that in the version of codeigniter you are using this has been replaced with:
if ( ! class_exists('CI_Model'))
{
load_class('Model', 'core');
}
You need to re-write the MY_Loader.php to make it compatible with the version of codeigniter you are using, presumably 2.1.
The best way to do this would be to grab the Codeigniter 2.1 version of the core Loader.php and use the model() method from it as a base for re-writing your MY_Loader.php.
Something to bare in mind when re-writing your MY_Loader.php is that constructors also changed in later versions of codeigniter, so your MY_Loader class should look something like this:
class MY_Loader extends CI_Loader {
function __construct()
{
parent::__construct();
}
public function model($model, $name = '', $db_conn = FALSE)
{
...
}
}
More details on extending core classes can be found in the Codeigniter Documentation
UPDATE
I noticed you changed your functionality, so I updated the answer. You can create a whole new loader and base it off the core CI_Model class, although it seems like a lot of effort when you could just extend the model loader and be done.
class MY_Loader extends CI_Loader {
protected $_ci_module_paths = array();
protected $_ci_modules = array();
function __construct()
{
parent::__construct();
$this->_ci_module_paths = array(APPPATH);
}
public function initialize()
{
$this->_ci_classes = array();
$this->_ci_loaded_files = array();
$this->_ci_models = array();
$this->_ci_modules = array();
$this->_base_classes =& is_loaded();
$this->_ci_autoloader();
return $this;
}
public function module($module, $name = '', $db_conn = FALSE)
{
if (is_array($module))
{
foreach ($module as $babe)
{
$this->$module($babe);
}
return;
}
if ($module == '')
{
return;
}
$path = '';
// Is the $module in a sub-folder? If so, parse out the filename and path.
if (($last_slash = strrpos($module, '/')) !== FALSE)
{
// The path is in front of the last slash
$path = substr($module, 0, $last_slash + 1);
// And the module name behind it
$module = substr($module, $last_slash + 1);
}
if ($name == '')
{
$name = $module;
}
if (in_array($name, $this->_ci_modules, TRUE))
{
return;
}
$CI =& get_instance();
if (isset($CI->$name))
{
show_error('The module name you are loading is the name of a resource that is already being used: '.$name);
}
$module = strtolower($module);
foreach ($this->_ci_module_paths as $mod_path)
{
if ( ! file_exists($mod_path.'modules/'.$path.$module.'.php'))
{
continue;
}
if ($db_conn !== FALSE AND ! class_exists('CI_DB'))
{
if ($db_conn === TRUE)
{
$db_conn = '';
}
$CI->load->database($db_conn, FALSE, TRUE);
}
if ( ! class_exists('CI_Model'))
{
load_class('Model', 'core');
}
require_once($mod_path.'modules/'.$path.$module.'.php');
$module = ucfirst($module);
$CI->$name = new $module();
$this->_ci_modules[] = $name;
return;
}
// couldn't find the model
show_error('Unable to locate the module you have specified: '.$module);
}
public function add_package_path($path, $view_cascade=TRUE)
{
$path = rtrim($path, '/').'/';
array_unshift($this->_ci_library_paths, $path);
array_unshift($this->_ci_model_paths, $path);
array_unshift($this->_ci_module_paths, $path);
array_unshift($this->_ci_helper_paths, $path);
$this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths;
// Add config file path
$config =& $this->_ci_get_component('config');
array_unshift($config->_config_paths, $path);
}
public function remove_package_path($path = '', $remove_config_path = TRUE)
{
$config =& $this->_ci_get_component('config');
if ($path == '')
{
$void = array_shift($this->_ci_library_paths);
$void = array_shift($this->_ci_model_paths);
$void = array_shift($this->_ci_module_paths);
$void = array_shift($this->_ci_helper_paths);
$void = array_shift($this->_ci_view_paths);
$void = array_shift($config->_config_paths);
}
else
{
$path = rtrim($path, '/').'/';
foreach (array('_ci_library_paths', '_ci_model_paths', '_ci_module_paths', '_ci_helper_paths') as $var)
{
if (($key = array_search($path, $this->{$var})) !== FALSE)
{
unset($this->{$var}[$key]);
}
}
if (isset($this->_ci_view_paths[$path.'views/']))
{
unset($this->_ci_view_paths[$path.'views/']);
}
if (($key = array_search($path, $config->_config_paths)) !== FALSE)
{
unset($config->_config_paths[$key]);
}
}
// make sure the application default paths are still in the array
$this->_ci_library_paths = array_unique(array_merge($this->_ci_library_paths, array(APPPATH, BASEPATH)));
$this->_ci_helper_paths = array_unique(array_merge($this->_ci_helper_paths, array(APPPATH, BASEPATH)));
$this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH)));
$this->_ci_module_paths = array_unique(array_merge($this->_ci_module_paths, array(APPPATH)));
$this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE));
$config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH)));
}
private function _ci_autoloader()
{
if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
}
else
{
include(APPPATH.'config/autoload.php');
}
if ( ! isset($autoload))
{
return FALSE;
}
// Autoload packages
if (isset($autoload['packages']))
{
foreach ($autoload['packages'] as $package_path)
{
$this->add_package_path($package_path);
}
}
// Load any custom config file
if (count($autoload['config']) > 0)
{
$CI =& get_instance();
foreach ($autoload['config'] as $key => $val)
{
$CI->config->load($val);
}
}
// Autoload helpers and languages
foreach (array('helper', 'language') as $type)
{
if (isset($autoload[$type]) AND count($autoload[$type]) > 0)
{
$this->$type($autoload[$type]);
}
}
// A little tweak to remain backward compatible
// The $autoload['core'] item was deprecated
if ( ! isset($autoload['libraries']) AND isset($autoload['core']))
{
$autoload['libraries'] = $autoload['core'];
}
// Load libraries
if (isset($autoload['libraries']) AND count($autoload['libraries']) > 0)
{
// Load the database driver.
if (in_array('database', $autoload['libraries']))
{
$this->database();
$autoload['libraries'] = array_diff($autoload['libraries'], array('database'));
}
// Load all other libraries
foreach ($autoload['libraries'] as $item)
{
$this->library($item);
}
}
// Autoload models
if (isset($autoload['model']))
{
$this->model($autoload['model']);
}
// Autoload modules
if (isset($autoload['module']))
{
$this->module($autoload['module']);
}
}
}
You can use the above in your controller via:
$this->load->module('test_module');
$this->test_module->method();
If i am getting your problem correctly , you have to initialize the instance of CI.
Example :-
In your external file do this :-
class modules
{
var $CI;
function __construct()
{
$this->CI =& get_instance();
}
function retrieve_data()
{
// Load Model
$this->CI->load->model('YOUR MODEL NAME'); // Give the name of your model that you want to laod
}
}
Hope it helps you :)
As far as i understand your question the answer lies here.
Moduler Extension
download it and install according to the instructions. then you can simply access any thing with these steps.
In your controller load the module like this.
$this->load->module('pages');
Make sure you have a directory Modules under application. And your Module pages should exist there. Pages module can have controller , models , views , helpers , config and libraries etc.
Now you need to call model. Here is how you can do it.
$this->pages->model->test_model->get_test_data();
Also you should note that loading module loads all the resources.
If nothing works, try this
public function setPackagePaths($path, $view_cascade=TRUE, $model_cascade=TRUE, $library_cascade=TRUE, $helper_cascade=TRUE) {
$path = rtrim($path, '/').'/';
///$this->_ci_view_paths = $this->_ci_library_paths = $this->_ci_model_paths = array();
$this->_ci_library_paths = array($path.'libraries/' => $library_cascade) + $this->_ci_library_paths ;
$this->_ci_helper_paths = array($path.'helpers/' => $helper_cascade) + $this->_ci_helper_paths ;
$this->_ci_model_paths = array($path.'models/' => $model_cascade) + $this->_ci_model_paths ;
$this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths;
// Add config file path
$config =& $this->_ci_get_component('config');
array_unshift($config->_config_paths, $path);
}
Its a hardcore way to update this function to Loader Class. But works fine.