I couldn't get a parameter in an action when using the definitive guide to Yii 3.0

What I did:

  1. Created a project
  2. Created a new “Hello” page in the application

When I open http://localhost/say/Goodbye in a browser, the page displays “The message is: Hello!”, but “The message is: Goodbye” is expected.

1 Like

I am having the same issue.

I wondered if the instructions were out of date as these two links have different instructions.

a) docs/guide/en/start/hello.md at master · yiisoft/docs · GitHub

b) Saying hello | docs

I tried following a) but it doesn’t work, and b) gives an error.

By doesn’t work I mean it prints the default message, not what I pass in the URL.

I just asked Grok and it suggested the following which worked. Not sure if that makes it the correct approach.

The problem is in this line of your routes.php:

Route::get('/say[/{message}]')

The square brackets [] make the entire part /say/{message} optional in Yii Router (which uses yiisoft/router).

So this route matches both:

  • /say → $message gets the default value 'Hello!'
  • /say/anything → $message gets the value anything

But it also matches /say with no trailing slash or parameter at all, and when the parameter is missing, your action uses the default value 'Hello!'.

When you visit:

http://localhost:8080/say/Hello11

The router matches the route correctly and passes Hello11 as $message.

But you say it prints “Hello!” instead of “Hello11”.

That means: the $message parameter inside your action is NOT receiving the value from the URL.

The real issue

You are using a custom attribute:

#[RouteArgument('message')]

But in yiisoft/router v2+ (which is used by Yii 3), the correct attribute is:

use Yiisoft\Router\CurrentRoute;
use Yiisoft\Router\Attribute\Argument; // or Parameter

The attribute class you are using:

use Yiisoft\Router\HydratorAttribute\RouteArgument;

This class was removed / renamed a long time ago (around 2021–2022).
It no longer exists in current versions of yiisoft/router.

So the attribute is completely ignored → the method receives only default parameters → $message stays 'Hello!'.

Correct solutions (choose one)

Option 1 – Recommended (Yii 3 style): Use constructor injection of CurrentRoute

<?php

declare(strict_types=1);

namespace App\Web\Echo;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Html\Html;
use Yiisoft\Router\CurrentRoute;

final readonly class Action
{
    public function __construct(
        private ResponseFactoryInterface $responseFactory,
        private CurrentRoute $currentRoute,
    ) {}

    public function __invoke(): ResponseInterface
    {
        $message = $this->currentRoute->getArgument('message', 'Hello!');

        $response = $this->responseFactory->createResponse();
        $response->getBody()->write('The message is: ' . Html::encode($message));
        return $response;
    }
}

And keep your route exactly as:

Route::get('/say[/{message}]')
    ->action(Web\Echo\Action::class)
    ->name('echo/say'),

This works perfectly.

Option 2 – Use method parameter + proper attribute (if you really want invokable with param)

use Yiisoft\Router\Attribute\Parameter;

public function __invoke(#[Parameter('message')] string $message = 'Hello!'): ResponseInterface
{
    // ...
}

But note: the attribute class name has changed over time. In the latest versions it’s:

use Yiisoft\Router\Attribute\RouteParameter;

So:

use Yiisoft\Router\Attribute\RouteParameter;

public function __invoke(#[RouteParameter('message')] string $message = 'Hello!'): ResponseInterface

Option 3 – Make the parameter required (simpler)

If you never want the default “Hello!”, just make it required:

Route::get('/say/{message}', Web\Echo\Action::class)->name('echo/say');

and remove the default value in PHP.

Summary

Your code doesn’t work because:

use Yiisoft\Router\HydratorAttribute\RouteArgument;

→ This attribute no longer exists in current Yii 3 / yiisoft/router.

Fix: Use CurrentRoute (recommended) or the correct up-to-date attribute (RouteParameter).

After changing to CurrentRoute, visiting /say/Hello11 will correctly show:

The message is: Hello11

Let me know which version of yiisoft/router you have (composer show yiisoft/router) if you want the exact attribute name for your version. But in 99% of Yii 3 projects today, the CurrentRoute solution above is the cleanest and most widely used.

Solution 1 works, but it is definitely not the standard approach.
Solution 2 almost works, but the conclusion of Yiisoft\Router\HydratorAttribute\RouteArgument is wrong.
So, the correct solution is
Replace:

#[RouteArgument('message')]
public function __invoke(string $message = 'Hello!'): ResponseInterface

with:

public function __invoke(#[RouteArgument('message')] string $message = 'Hello!'): ResponseInterface

Solution 3 does not work.

Thanks,

That worked!