Aleksej_Shherbak
Aleksej_Shherbak

Reputation: 3078

yii2 "Unable to resolve the request" with ActiveController

I have a simple Yii2 REST application. See:

enter image description here

As you can see, there is only one model Category, and there is only one controller CategoryController

Category:

<?php

namespace api\models;

/**
 * This is the model class for table "{{%category}}".
 *
 * @property int $id
 * @property string $slug
 * @property string $title
 * @property int $enabled
 *
 */
class Category extends \yii\db\ActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return '{{%category}}';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            ['enabled', 'default', 'value' => 0],
            [['title'], 'required'],
            [['enabled'], 'integer'],
            [['slug', 'title'], 'string', 'max' => 255],
            [['slug'], 'unique'],
        ];
    }
}

CategoryController:

<?php

namespace api\controllers;

use yii\rest\ActiveController;

/**
 * Class CategoryController
 *
 * @package api\controllers
 */
class CategoryController extends ActiveController
{
    public $modelClass = 'api\models\Category';

}

Then I will pin here my configuration for the application:

config/main.php

<?php
$params = array_merge(
    require __DIR__ . '/../../common/config/params.php',
    require __DIR__ . '/../../common/config/params-local.php',
    require __DIR__ . '/params.php',
    require __DIR__ . '/params-local.php'
);

return [
    'id' => 'app-api',
    'basePath' => dirname(__DIR__),
    'language' => 'ru-RU',
    'bootstrap' => ['log'],
    'controllerNamespace' => 'api\controllers',
    'components' => [
        'request' => [
            'parsers' => [
                'application/json' => 'yii\web\JsonParser',
            ]
        ],
        'response' => [
            'class' => 'yii\web\Response',
            'format' => 'json'
        ],
        'user' => [
            'identityClass' => 'common\models\User',
            'enableAutoLogin' => true,
            'identityCookie' => ['name' => '_identity-frontend', 'httpOnly' => true],
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'urlManager' => [
            'enablePrettyUrl' => true,
            'enableStrictParsing' => true,
            'showScriptName' => false,
            'rules' => [
                [
                    'class' => 'yii\rest\UrlRule',
                    'controller' => 'category',
                ],
            ],
        ],
    ],
    'params' => $params,
];

How I run it? Simple. Just with the help of $ php -S localhost:8900 inside the web directory.

But when I visit the URL: localhost:8900/categories I see the following:

{"name":"Not Found","message":"Page not found.","code":0,"status":404,"type":"yii\web\NotFoundHttpException","previous":{"name":"Invalid Route","message":"Unable to resolve the request \"category/index\".","code":0,"type":"yii\base\InvalidRouteException"}}

What does it mean? I suppose that Yii makes the following things.

But the reason of {"name":"Invalid Route","message":"Unable to resolve the request \"category/index\".","code":0,"type":"yii\\base\\InvalidRouteException"} is unknown for me.

All of this is strange behavior. I'm just trying to follow along this official guide What is wrong for my configuration?

Update

I have dived deeper(directly inside the entrails of the framework).

I found, that the crash is happening here :

   /**
     * Creates a controller based on the given controller ID.
     *
     * The controller ID is relative to this module. The controller class
     * should be namespaced under [[controllerNamespace]].
     *
     * Note that this method does not check [[modules]] or [[controllerMap]].
     *
     * @param string $id the controller ID.
     * @return Controller|null the newly created controller instance, or `null` if the controller ID is invalid.
     * @throws InvalidConfigException if the controller class and its file name do not match.
     * This exception is only thrown when in debug mode.
     */
    public function createControllerByID($id)
    {
        $pos = strrpos($id, '/');
        if ($pos === false) {
            $prefix = '';
            $className = $id;
        } else {
            $prefix = substr($id, 0, $pos + 1);
            $className = substr($id, $pos + 1);
        }

        if ($this->isIncorrectClassNameOrPrefix($className, $prefix)) {
            return null;
        }

        $className = preg_replace_callback('%-([a-z0-9_])%i', function ($matches) {
                return ucfirst($matches[1]);
            }, ucfirst($className)) . 'Controller';
        $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
        // THE PROBLEM IS HERE !!! WITH THE  !class_exists($className)
        if (strpos($className, '-') !== false || !class_exists($className)) {
            return null;
        }

        if (is_subclass_of($className, 'yii\base\Controller')) {
            $controller = Yii::createObject($className, [$id, $this]);
            return get_class($controller) === $className ? $controller : null;
        } elseif (YII_DEBUG) {
            throw new InvalidConfigException('Controller class must extend from \\yii\\base\\Controller.');
        }

        return null;
    }

the !class_exists($className) takes this string as argument "api\controllers\CategoryController" and returns true. The condition works, and I have null as result.

What is happening then? The following:

    /**
     * Runs a controller action specified by a route.
     * This method parses the specified route and creates the corresponding child module(s), controller and action
     * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
     * If the route is empty, the method will use [[defaultRoute]].
     * @param string $route the route that specifies the action.
     * @param array $params the parameters to be passed to the action
     * @return mixed the result of the action.
     * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully.
     */
    public function runAction($route, $params = [])
    {
        $parts = $this->createController($route);
        // $parts IS NULL !!!!!!!!!!
        if (is_array($parts)) {
            /* @var $controller Controller */
            list($controller, $actionID) = $parts;
            $oldController = Yii::$app->controller;
            Yii::$app->controller = $controller;
            $result = $controller->runAction($actionID, $params);
            if ($oldController !== null) {
                Yii::$app->controller = $oldController;
            }

            return $result;
        }

        $id = $this->getUniqueId();
        throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
    }

And I see the familiar exception InvalidRouteException. Any ideas?

Upvotes: 1

Views: 2215

Answers (2)

Aleksej_Shherbak
Aleksej_Shherbak

Reputation: 3078

Ok. The answer is simple. If you are developing new one application with the help of copy and paste from another one (like me), don't forget to update common/config/bootstrap.php and add new one alias. My new application's name is api. It means my bootstrap.php is:

<?php
Yii::setAlias('@common', dirname(__DIR__));
Yii::setAlias('@frontend', dirname(dirname(__DIR__)) . '/frontend');
Yii::setAlias('@backend', dirname(dirname(__DIR__)) . '/backend');
Yii::setAlias('@console', dirname(dirname(__DIR__)) . '/console');
Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api'); // <- new application!!!

P.S. I use advanced template. Thank you for reading)

Upvotes: 2

user9880886
user9880886

Reputation:

You need to specify an index method on the CategoryController.

IE:

public function actionIndex()    
{
     return $this->render('index');
}

And offcourse you need to add the template for this method or

Upvotes: 0

Related Questions