How can I get the action name in a Symfony2 controller? - php

Is there a way to get the name of the action in a Symfony2 controller?
public function createAction(Request $request, $title) {
// Expected result: create
$name = $this->getActionName();
}

useļ¼š
$request->attributes->get('_controller');
// will get yourBundle\Controller\yourController::CreateAction
$params = explode('::',$request->attributes->get('_controller'));
// $params[1] = 'createAction';
$actionName = substr($params[1],0,-6);
// $actionName = 'create';

I found this snippet (here):
$matches = array();
$controller = $this->getRequest()->attributes->get('_controller');
preg_match('/(.*)\\\(.*)Bundle\\\Controller\\\(.*)Controller::(.*)Action/', $controller, $matches);
which seems to be a promising approach. This regexp actually doesn't work. But it won't be hard to fetch the action name by using strstr(). Works!
And returns (see example)
Array
(
[0] => Acme\MyBundle\Controller\MyController::myAction
[1] => Acme
[2] => My
[3] => My
[4] => my
)
If input was Acme\MyBundle\Controller\MyController::myAction.

Now, I am using this with Symfony 2.8, (and Symfony3):
<?php
namespace Company\Bundle\AppBundle\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Request as BaseRequest;
/**
* Request shortcuts.
*/
class Request extends BaseRequest
{
/**
* Extract the action name.
*
* #return string
*/
public function getActionName()
{
$action = $this->get('_controller');
$action = explode('::', $action);
// use this line if you want to remove the trailing "Action" string
//return isset($action[1]) ? preg_replace('/Action$/', '', $action[1]) : false;
return $action[1];
}
/**
* Extract the controller name (only for the master request).
*
* #return string
*/
public function getControllerName()
{
$controller = $this->get('_controller');
$controller = explode('::', $controller);
$controller = explode('\\', $controller[0]);
// use this line if you want to remove the trailing "Controller" string
//return isset($controller[4]) ? preg_replace('/Controller$/', '', $controller[4]) : false;
return isset($controller[4]) ? $controller[4] : false;
}
}
To use this custom request class, you must "use" it in your web/app*.php controllers:
use Company\Bundle\AppBundle\Component\HttpFoundation\Request;
// ...
$request = Request::createFromGlobals();
// ...
Then in your controller:
class AppController extends Controller
{
/**
* #Route("/", name="home_page")
* #Template("")
*
* #return array
*/
public function homePageAction(Request $request)
{
$controllerName = $request->getControllerName();
$actionName = $request->getActionName();
dump($controllerName, $actionName); die();
// ...
}
Will output:
AppController.php on line 27:
"AppController"
AppController.php on line 27:
"homePageAction"
You can also access these functions through the RequestStack service:
class MyService
{
/**
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function myService()
{
$this->controllerName = $this->requestStack->getMasterRequest()->getControllerName();
$this->actionName = $this->requestStack->getMasterRequest()->getActionName();
// ...
}

If you use Controller as a Service than the schema is different:
$request->attributes->get('_controller'); will return "service_id:createAction"
A possible solution for both schemas:
$controller = $request->attributes->get('_controller');
$controller = str_replace('::', ':', $controller);
list($controller, $action) = explode(':', $controller);

In all version of symfony and without $request or container, service or nothing else... , directly in your method
public function myMethod(){
$methodName = __METHOD__;
return $methodName;
}
// return App\Controller\DefaultController::myMethod
public function mySecondMethod(){
$methodName = explode('::', __METHOD__);
return $methodName[1];
}
// return mySecondMethod

Related

How to use autowired services when using PhpUnit?

Was testing an application and was repeatable getting the infamous new entity not configured to cascade persist error. I was surprised since I wasn't even creating new entities, and after digging into it, it appears to be relate to using different instances of the EntityManager object (I have confirmed that they are working with the same database, however) which I guess makes sense since each test will have a transaction applied. The only way I was able to get rid of the errors was to use the entityManager in the container instead of the autowired ones. While it works, it is a bit of a kludge and I would like to know the right way of doing this. Thank you
namespace App\Tests;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
class MyTest extends ApiTestCase
{
/**
* #dataProvider getData
*/
public function testWhichDoesNotWork(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$user = $service->getUser();
$randomEntity = $service->getRandomEntity($user->getTenant(), $class);
$randomEntity->setSomething('something');
$service->saveEntity($randomEntity);
}
/**
* #dataProvider getData
*/
public function testWhichWorks(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $service->getUser();
$randomId = $service->getRandomEntityId($user->getTenant(), $class);
$randomEntity = $em->getRepository($class)->find($randomId);
$randomEntity->setSomething('something');
$em->persist($randomEntity);
$em->flush();
}
/**
* #dataProvider getData
*/
public function testAnotherWhichWorks(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$service->setNewEntityManager(static::getContainer()->get(EntityManagerInterface::class));
$user = $service->getUser();
$randomEntity = $service->getRandomEntity($user->getTenant(), $class);
$randomEntity->setSomething('something');
$service->saveEntity($randomEntity);
}
public function getData(): array
{
return [
[123, SomeClass::class]
];
}
}
namespace App\Test\Service;
final class MyService
{
public function __construct(private EntityManagerInterface $entityManager)
{}
public function setNewEntityManager(EntityManagerInterface $entityManager):self
{
$this->entityManager = $entityManager;
return $this;
}
public function getDatabase():string
{
return $this->entityManager->getConnection()->getDatabase();
}
public function getUser(int $id):User
{
return $this->entityManager->getRepository(User::class)->find($id);
}
public function getRandomId(Tenant $tenant, string $class):int
{
$meta = $this->entityManager->getClassMetadata($class);
$_sql = 'SELECT %s FROM public.%s WHERE tenant_id=? OFFSET floor(random() * (SELECT COUNT(*) FROM public.%s WHERE tenant_id=?)) LIMIT 1;';
$sql = sprintf($_sql, $meta->getSingleIdentifierFieldName(), $meta->getTableName(), $meta->getTableName());
return $this->entityManager->getConnection()->prepare($sql)->execute([$tenant->getId(), $tenant->getId()])->fetchOne();
}
public function getRandomEntity(Tenant $tenant, string $class):object
{
return $this->entityManager->getRepository($class)->find($this->getRandomId($tenant, $class));
}
public function saveEntity(object $entity):self
{
$this->entityManager->persist($entity);
$this->flush();
return $this;
}
}
services:
app.test.my.service:
alias: App\Test\Service\MyService
public: true

Connecting method/function in laravel

I'm trying to create a class function which resembles how we used to fetch database listing and convert into a dropdown listing.
eg: DB::table()->where()->get()
what i would like to achieve in laravel custom class or through model is this
Dropdown::fetch()->toArray()
Dropdown::fetch()->toDropdown()
I tried to figure out how this can be done through google. But couldn't find any solution to it.
I'm using laravel 5.8
--
Edit - Sample Code added
Code tried:
namespace App\Http\Models;
use DB;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Database\Eloquent\Model;
class Dropdown extends Model
{
private $result = [];
private $default;
public function _cities(){
$tbl_cities = config("tables.TBL_meta_cities");
$result = DB::table($tbl_cities)->select('id', 'cityname')
->orderBy('id')->get()->toArray();
$this->result = $result;
}
public function _select(){
}
public function _list(){
return $this->result;
}
public function _setDefault($def=''){
}
public static function __callStatic($method, $parameters)
{
$action = '_'.$method;
if(method_exists(get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
public function __call($method, $parameters)
{
$action = '_'.$method;
if(method_exists($get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
}
and i tried
Dropdown::cities()->list()
but ended with bugs
Well i figured it out myself.
class Dropdown extends Model
{
private static $result = [];
private function getCities(){
$result = City::select('id', 'cityname')
->orderBy('id')->get()->toArray();
self::$result = $result;
}
public function toArray(){
return self::$result;
}
public function toDropdown(){
// Do the dropdown works
}
/**
* Dynamically handle calls to the class.
*
* #param string $method
* #param array $parameters
* #return mixed
*
* #throws \BadMethodCallException
*/
public function __callMethod($method, $parameters){
// Check with inclusive
$class = get_called_class();
$avail = false;
$action = '';
// Check method availability - direct
if(!$avail){
$action = $method;
$avail = method_exists($class, $action);
}
// Check method 2
if(!$avail){
$action = 'get'.ucwords($method);
$avail = method_exists($class, $action);
}
if($avail){
// Call the method
$return = self::$action(...$parameters);
if(!empty($return)) return $return;
} else {
// Throw error if method not found
throw new BadMethodCallException("No such method exists: $name");
}
return new self;
}
public static function __callStatic($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
public function __call($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
}
All i need to do is return new self which does the trick instead of return $this so that the trailing function can be called easily.
Now i can able to call that function like this
Dropdown::cities()->toArray();
Reference:
https://stackoverflow.com/a/41631711/1156493
Thank you #Joseph for your time & support.

How to mock request method in phpunit mockery?

I started using mockery so I have a problem in doing my unit test . I want to test authenticate middleware , I passed one condition for expectsJson so I need one more pattern to return true from expectesJson like below but not success
Authenticate.php
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
AuthenticatTest.php
class AuthenticateTest extends TestCase
{
/**
* A basic unit test example.
*
* #return void
*/
public function testMiddleware()
{
$request = Request::create(config('app.url') . '500', 'GET',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:8000']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$expectedStatusCode = 401;
$this->assertContains("http://",$method->invokeArgs($middleware,[$request]));
}
public function testMiddlewareElse()
{
$this->mock(Request::class, function($mock) {
$mock->shouldReceive("expectsJson")
->once()->andReturn(true);
});
$request = Request::create(config('app.url') . '200', 'POST',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:00']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$this->assertContains("",$method->invokeArgs($middleware,[$request]));
}
}
testMiddlewareElse is failed , How to return true for $request->expectsJson
Here's how you could test a request for the authentication middleware. Assume that you have a route that requires authentication that is managed by UserController#dashboard (or similar):
public function testMiddleware() {
// You could disable the other middleware of the route if you don't want them to run e.g.
// $this->withoutMiddleware([ list of middleware to disable ]);
$mockController = $this->prophecy(UserController::class);
//This is if the middleware passes and the controller method is called, use shouldNotBeCalled if you expect it to fail
$mockController->dashboard(Prophecy::any())->shouldBeCalled();
$this->app->instance(
UserController::class,
$mockController->reveal()
);
$this->json("GET", url()->action("UserController#dashboard"));
}
I found the solution ! I need to pass mock class in invoke params ...;)
public function testMiddlewareElse()
{
$mock = $this->mock(Request::class, function($mock) {
$mock->shouldReceive("expectsJson")
->once()->andReturn(true);
});
$request = Request::create(config('app.url') . '200', 'POST',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:00']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$this->assertContains("",$method->invokeArgs($middleware,[$mock]));
}

Must be of the type array, null given,

I keep getting the following error and I'm not to sure why:
Catchable fatal error: Argument 3 passed to System\Loader::action() must be of the type array, null given, called in /Application.php on line 31 and defined in /Loader.php on line 18
Loader class:
<?php
namespace System;
class Loader {
//application object
private $app;
//controllers container of array
private $controllers = [];
//models container array
private $models = [];
//constructor takes in system/application $app
public function __construct(Application $app) {
$this->app = $app;
}
//call the given controller with the given method and pass the given arguments to the controller method
//takes in string $controller, string $method and array $arguments returns mixed
public function action ($controller, $method, array $arguments) { ##line 18
$object = $this->controller($controller);
return call_user_func([$object, $method], $arguments);
}
//call the given controller takes in string $controller and returns object
public function controller ($controller) {
$controller = $this->getControllerName($controller);
if(! $this->hasController($controller)) {
$this->addController($controller);
}
return $this->getController($controller);
}
//determine if the given clas/controller exists in the controller container takes in string $controller and returns boolean
private function hasController($controller) {
return array_key_exists($controller, $this->controllers);
}
//create new object for the given controller and store it in the controllers container
//takes in string $controller and returns void
private function addController($controller) {
$object = new $controller($this->app);
$this->controllers[$controller] = $object;
}
//get the controller object takes in string $controller and returns object
private function getController($controller) {
return $this->controllers[$controller];
}
//get the full class name for the given controller takes string $controller returns string
private function getControllerName($controller) {
$controller .= 'Controller';
$controller = 'App\\Controllers\\' . $controller;
return str_replace('/', '\\', $controller);
}
//call the given Model takes in string $Model and returns object
public function model ($model) {
$model = $this->getModelName($model);
if(! $this->hasModel($model)) {
$this->addModel($model);
}
return $this->getModel($model);
}
//determine if the given clas/Model exists in the Model container takes in string $controller and returns boolean
private function hasModel($model) {
return array_key_exists($model, $this->models);
}
//create new object for the given Model and store it in the controllers container
//takes in string $Model and returns void
private function addModel($model) {
$object = new $model($this->app);
$this->models[$model] = $object;
}
//get the Model object takes in string $Model and returns object
private function getModel($model) {
return $this->models[$model];
}
//get the full class name for the given Model takes string $Model returns string
private function getModelName($model) {
$model .= 'Model';
$model = 'App\\Models\\' . $model;
return str_replace('/', '\\', $model);
}
}
Application class:
<?php
namespace System;
class Application {
//Container array var
private $container = [];
//application object
private static $instance;
//constructor $file param
private function __construct(File $file){
$this->share('file', $file);
$this->registerClasses();
// static::$instance = $this;
$this->loadHelpers();
}
//get application instance parameter $file returns system\application
public static function getInstance($file = null){
if(is_null(static::$instance)) {
static::$instance = new static($file);
}
return static::$instance;
}
//run the application returns void
public function run(){
$this->session->start();
$this->request->prepareUrl();
$this->file->call('App/index.php');
list($controller, $method, $arguments) = $this->route->getProperRoute();
$output = (string) $this->load->action($controller, $method, $arguments); ##line 31
$this->response->setOutput($output);
$this->response->send();
}
//register classes in sql auto load register
private function registerClasses(){
spl_autoload_register([$this, 'load']);
}
//load class through autoloading takes string $class and returns void
public function load($class){
if(strpos($class, 'App') === 0){
$file = $class . '.php';
} else {
//get the class from vendor
$file = 'vendor/' . $class . '.php';
}
if($this->file->exists($file)){
$this->file->call($file);
}
}
//load helpers file returns void
private function loadHelpers(){
$this->file->call('vendor/helpers.php');
}
// get shared value takes in string $key returns mixed
public function get($key){
if(! $this->isSharing($key)){
if($this->isCoreAlias($key)){
$this->share($key, $this->createNewCoreObject($key));
} else{
die('<b>' . $key . '</br> not found in application container');
}
}
return $this->container[$key];
// return $this->isSharing() ? $this->container[$key] : null;
}
//determind if the given key is shared through application takes string $key returns boolean
public function isSharing($key){
return isset($this->container[$key]);
}
//share key and value through application
public function share($key, $value){
$this->container[$key] = $value;
}
//determines if the given key is an alias to core class takes string $alias returns boolean
public function isCoreAlias($alias){
$coreClasses = $this->coreClasses();
return isset($coreClasses[$alias]);
}
//create new object for the core class based on the given alias takes in string $alias returns object
public function createNewCoreObject($alias){
$coreClasses = $this->coreClasses();
$object = $coreClasses[$alias];
return new $object($this);
}
//get all core classes with its aliase returns array
private function coreClasses(){
return [
'request' => 'System\\Http\\Request',
'response' => 'System\\Http\\Response',
'session' => 'System\\Session',
'route' => 'System\\Route',
'cookie' => 'System\\Cookie',
'load' => 'System\\Loader',
'html' => 'System\\Html',
'db' => 'System\\Database',
'view' => 'System\\View\\ViewFactory',
'url' => 'System\\Url',
];
}
//get shared value dynamically takes string $key returns mixed
public function __get($key){
return $this->get($key);
}
}
Not too sure where its getting this null from would appreciate if someone could help me figure this out.
edit: route class
<?php
namespace System;
class Route {
//application object
private $app;
//routes container
private $routes = [];
//not found url
private $notFound;
//constructor
public function __construct(Application $app){
$this->app = $app;
}
//add new route takes string $url string $action and string $requestmethod returns void
public function add ($url, $action, $requestMethod = 'GET'){
$route = [
'url' => $url,
'pattern' => $this->generatePattern($url),
'action' => $this->getAction($action),
'method' => strtoupper($requestMethod),
];
$this->routes[] = $route;
}
//set not found url take string $url returns void
public function notFound($url){
$this->notFound = $url;
}
//get proper route reutns array
public function getProperRoute(){
foreach ($this->routes as $route) {
if ($this->isMatching($route['pattern'])){
$arguments = $this->getArgumentsFrom($route['pattern']);
list($controller, $method) = explode('#', $route['action']);
return [$controller, $method, $arguments];
}
}
}
//determine if the given pattern matches the current requested url takes string $pattern returns boolean
private function isMatching($pattern){
return preg_match($pattern, $this->app->request->url());
}
// get argument from the current requested url based on the given pattern takes in string $pattern returns array
private function getArgumentsFrom($pattern) {
preg_match($pattern, $this->app->request->url(), $matches);
array_shift($matches);
return $matches;
}
//generate a regex pattern for the given url takes string url returns string
private function generatePattern($url){
$pattern = '#^';
// :text ([a-zA-Z0-9-]+)
// :id (\d+)
$pattern .= str_replace([':text', ':id'], ['([a-zA-Z0-9-]+)', '(\d+)'], $url);
$pattern .= '$#';
return $pattern;
}
//get proper action takes in string $action returns string
private function getAction($action){
$action = str_replace('/', '\\', $action);
return strpos($action, '#') !== false ? $action : $action . '#index';
}
}
The call getProperRoute() is returning an array of 2 elements and this is why the "$arguments" is NULL.
You can either change the call to:
public function action ($controller, $method, array $arguments = []){
$object = $this->controller($controller);
return call_user_func([$object, $method], $arguments);
}
Or check what is the return of the getProperRoute method

Call a class by string PHP

I'm calling a class by a string variable passed on a function argument.
ApiTester.php
use MyApp\Sites\Site;
abstract class ApiTester extends TestCase() {
/**
* Make a new record in the DB
*
* #param $type
* #param array $fields
* #throws BadMethodCallException
*/
protected function make($type, array $fields = [])
{
while($this->times--)
{
$stub = array_merge($this->getStub(), $fields);
$type::create($stub);
}
}
SitesTester.php
class SitesTester extends ApiTester() {
/** #test */
public function it_fetches_a_single_site()
{
// arrange
$this->make('Site');
// act
$site = $this->getJson('api/v1/sites/1')->data;
// assertion
$this->assertResponseOk();
$this->assertObjectHasAttributes($site, 'name', 'address');
}
Site.php // Eloquent Model
namespace MyApp\Sites;
class Site extends \Eloquent {
}
But if I call the class that the string variable $type contains, for example; string variable $type contains 'Site', it says class 'Site' not found.
I tried to manually type Site::create($stub) and finally accepts it.
I also tried
call_user_func($type::create(), $stub);
and
$model = new $type;
$model->create($stub);
but unfortunately it says class 'Site' not found.
Any ideas?
You're almost there:
class X {
static function foo($arg) {
return 'hi ' . $arg;
}
};
$cls = 'X';
print call_user_func("$cls::foo", 'there');
If your php is very old (<5.3 I believe), you have to use an array instead:
print call_user_func(array($cls, "foo"), 'there');
You may want to replace that static class call with the following :
while( $this->times-- )
{
$stub = array_merge( $this->getStub(), $fields );
call_user_func( "$type::create", $stub );
}
Runnable code here : http://runnable.com/VIqy4CDePeY-AeMV/output

Categories