2021-07-27 14:46:32 +02:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\HttpKernel\Controller ;
use Psr\Log\LoggerInterface ;
use Symfony\Component\HttpFoundation\Request ;
/**
* This implementation uses the '_controller' request attribute to determine
* the controller to execute .
*
* @ author Fabien Potencier < fabien @ symfony . com >
* @ author Tobias Schultze < http :// tobion . de >
*/
class ControllerResolver implements ControllerResolverInterface
{
private $logger ;
public function __construct ( LoggerInterface $logger = null )
{
$this -> logger = $logger ;
}
/**
* { @ inheritdoc }
*/
public function getController ( Request $request )
{
if ( ! $controller = $request -> attributes -> get ( '_controller' )) {
if ( null !== $this -> logger ) {
$this -> logger -> warning ( 'Unable to look for the controller as the "_controller" parameter is missing.' );
}
return false ;
}
if ( \is_array ( $controller )) {
if ( isset ( $controller [ 0 ]) && \is_string ( $controller [ 0 ]) && isset ( $controller [ 1 ])) {
try {
$controller [ 0 ] = $this -> instantiateController ( $controller [ 0 ]);
2022-06-16 21:54:19 +02:00
} catch ( \Error | \LogicException $e ) {
2021-07-27 14:46:32 +02:00
try {
// We cannot just check is_callable but have to use reflection because a non-static method
// can still be called statically in PHP but we don't want that. This is deprecated in PHP 7, so we
// could simplify this with PHP 8.
if (( new \ReflectionMethod ( $controller [ 0 ], $controller [ 1 ])) -> isStatic ()) {
return $controller ;
}
} catch ( \ReflectionException $reflectionException ) {
throw $e ;
}
throw $e ;
}
}
if ( ! \is_callable ( $controller )) {
throw new \InvalidArgumentException ( sprintf ( 'The controller for URI "%s" is not callable: ' , $request -> getPathInfo ()) . $this -> getControllerError ( $controller ));
}
return $controller ;
}
if ( \is_object ( $controller )) {
if ( ! \is_callable ( $controller )) {
throw new \InvalidArgumentException ( sprintf ( 'The controller for URI "%s" is not callable: ' . $this -> getControllerError ( $controller ), $request -> getPathInfo ()));
}
return $controller ;
}
if ( \function_exists ( $controller )) {
return $controller ;
}
try {
$callable = $this -> createController ( $controller );
} catch ( \InvalidArgumentException $e ) {
throw new \InvalidArgumentException ( sprintf ( 'The controller for URI "%s" is not callable: ' , $request -> getPathInfo ()) . $e -> getMessage (), 0 , $e );
}
if ( ! \is_callable ( $callable )) {
throw new \InvalidArgumentException ( sprintf ( 'The controller for URI "%s" is not callable: ' . $this -> getControllerError ( $callable ), $request -> getPathInfo ()));
}
return $callable ;
}
/**
* Returns a callable for the given controller .
*
* @ param string $controller A Controller string
*
* @ return callable A PHP callable
*
* @ throws \InvalidArgumentException When the controller cannot be created
*/
protected function createController ( $controller )
{
2022-05-03 15:24:29 +02:00
if ( ! str_contains ( $controller , '::' )) {
2021-07-27 14:46:32 +02:00
$controller = $this -> instantiateController ( $controller );
if ( ! \is_callable ( $controller )) {
throw new \InvalidArgumentException ( $this -> getControllerError ( $controller ));
}
return $controller ;
}
[ $class , $method ] = explode ( '::' , $controller , 2 );
try {
$controller = [ $this -> instantiateController ( $class ), $method ];
2022-06-16 21:54:19 +02:00
} catch ( \Error | \LogicException $e ) {
2021-07-27 14:46:32 +02:00
try {
if (( new \ReflectionMethod ( $class , $method )) -> isStatic ()) {
return $class . '::' . $method ;
}
} catch ( \ReflectionException $reflectionException ) {
throw $e ;
}
throw $e ;
}
if ( ! \is_callable ( $controller )) {
throw new \InvalidArgumentException ( $this -> getControllerError ( $controller ));
}
return $controller ;
}
/**
* Returns an instantiated controller .
*
* @ param string $class A class name
*
* @ return object
*/
protected function instantiateController ( $class )
{
return new $class ();
}
private function getControllerError ( $callable ) : string
{
if ( \is_string ( $callable )) {
2022-05-03 15:24:29 +02:00
if ( str_contains ( $callable , '::' )) {
2021-07-27 14:46:32 +02:00
$callable = explode ( '::' , $callable , 2 );
} else {
return sprintf ( 'Function "%s" does not exist.' , $callable );
}
}
if ( \is_object ( $callable )) {
$availableMethods = $this -> getClassMethodsWithoutMagicMethods ( $callable );
$alternativeMsg = $availableMethods ? sprintf ( ' or use one of the available methods: "%s"' , implode ( '", "' , $availableMethods )) : '' ;
return sprintf ( 'Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.' , \get_class ( $callable ), $alternativeMsg );
}
if ( ! \is_array ( $callable )) {
return sprintf ( 'Invalid type for controller given, expected string, array or object, got "%s".' , \gettype ( $callable ));
}
if ( ! isset ( $callable [ 0 ]) || ! isset ( $callable [ 1 ]) || 2 !== \count ( $callable )) {
return 'Invalid array callable, expected [controller, method].' ;
}
[ $controller , $method ] = $callable ;
if ( \is_string ( $controller ) && ! class_exists ( $controller )) {
return sprintf ( 'Class "%s" does not exist.' , $controller );
}
$className = \is_object ( $controller ) ? \get_class ( $controller ) : $controller ;
if ( method_exists ( $controller , $method )) {
return sprintf ( 'Method "%s" on class "%s" should be public and non-abstract.' , $method , $className );
}
$collection = $this -> getClassMethodsWithoutMagicMethods ( $controller );
$alternatives = [];
foreach ( $collection as $item ) {
$lev = levenshtein ( $method , $item );
2022-05-03 15:24:29 +02:00
if ( $lev <= \strlen ( $method ) / 3 || str_contains ( $item , $method )) {
2021-07-27 14:46:32 +02:00
$alternatives [] = $item ;
}
}
asort ( $alternatives );
$message = sprintf ( 'Expected method "%s" on class "%s"' , $method , $className );
if ( \count ( $alternatives ) > 0 ) {
$message .= sprintf ( ', did you mean "%s"?' , implode ( '", "' , $alternatives ));
} else {
$message .= sprintf ( '. Available methods: "%s".' , implode ( '", "' , $collection ));
}
return $message ;
}
private function getClassMethodsWithoutMagicMethods ( $classOrObject ) : array
{
$methods = get_class_methods ( $classOrObject );
return array_filter ( $methods , function ( string $method ) {
return 0 !== strncmp ( $method , '__' , 2 );
});
}
}