Skip to content

New syntax#225

Open
rustamwin wants to merge 20 commits intomasterfrom
new-syntax
Open

New syntax#225
rustamwin wants to merge 20 commits intomasterfrom
new-syntax

Conversation

@rustamwin
Copy link
Copy Markdown
Member

@rustamwin rustamwin commented Nov 9, 2023

Q A
Is bugfix?
New feature? ✔️
Breaks BC? ✔️
Fixed issues #193, #171

Current syntax

- use Yiisoft\Router\Route;
- use Yiisoft\Router\Group;
+ use Yiisoft\Router\Builder\RouteBuilder as Route;
+ use Yiisoft\Router\Builder\GroupBuilder as Group;


Group::create()->routes(
    Route::get('')->name('index'),
-   Route::get('/posts')->name('posts'),
+   Route::get('/posts')->name('posts'), // No getters, only setters
);

@rustamwin rustamwin added the status:code review The pull request needs review. label Nov 9, 2023
@rustamwin rustamwin requested a review from a team November 9, 2023 07:22
@codecov
Copy link
Copy Markdown

codecov bot commented Nov 9, 2023

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (1e9bfd5) to head (c9bacd1).

Additional details and impacted files
@@             Coverage Diff              @@
##              master      #225    +/-   ##
============================================
  Coverage     100.00%   100.00%            
- Complexity       129       192    +63     
============================================
  Files             13        15     +2     
  Lines            400       524   +124     
============================================
+ Hits             400       524   +124     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@what-the-diff
Copy link
Copy Markdown

what-the-diff bot commented Nov 9, 2023

PR Summary

This pull request introduces numerous updates aimed at better organizing and streamlining how routes are handled in the system:

  • New Classes

    • Two new classes, GroupBuilder and RouteBuilder, have been added. These classes represent a group of routes and a single route respectively, and they include methods for defining diverse route characteristics, such as HTTP methods, route pattern, middlewares, hosts, and more.
  • Method and Property Modifications

    • Several existing methods (getName(), getHost(), getPattern(), getMethods()) in the CurrentRoute.php file and Debug/RouterCollector.php were adjusted to use new direct methods instead of a generic getData() method.
    • In the Group.php file, some properties were removed and replaced with new ones, and a variety of new methods designed for handling these properties were added.
  • “Route”

    • The Route.php file underwent a major update where new properties were added, methods were adjusted to utilize these new properties, and unnecessary methods were removed. The changes aim to streamline the usage of the class and make it more efficient.
  • Route Collection Update

    • The RouteCollection.php was refactored to better use new methods in the Route class and to remove outdated code.
  • Route Collector Update

    • The RouteCollector.php underwent several property and method name changes and had type assertions and argument validations added to improve code robustness and reliability.
  • Inclusion of Test Cases

    • A new test file for RouteBuilder class was added and several existing test files were updated to comply with the changes made to the core classes.
  • Miscellaneous

    • Adjustments were made in RouteCollectionTest.php and RouteCollectorTest.php to reflect changes made in the main classes.

These changes all contribute to a more robust and efficient route handling system within the software, aligning the codebase with modern best practices in PHP coding.

Copy link
Copy Markdown
Member

@vjik vjik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. #191 suggest use named arguments, and this must improve performance. In this PR keep routes/groups object and additional add new routes/groups objects with named arguments. Seems, this decrease performance...

  2. This not fix #171.

@rustamwin
Copy link
Copy Markdown
Member Author

In this PR keep routes/groups object and additional add new routes/groups objects with named arguments. Seems, this decrease performance...

We can mention it in docs or drop the current syntax.

This not fix #171.

Yes, doesn't fix directly. Since there is no exception, there is no need for it.

@vjik
Copy link
Copy Markdown
Member

vjik commented Nov 22, 2023

We can mention it in docs or drop the current syntax.

But what goal of syntax change?

# Conflicts:
#	src/Debug/RouterCollector.php
#	src/Route.php
#	tests/RouteTest.php
Copy link
Copy Markdown
Member

@vjik vjik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make Route and Group constructors public and keep BC?

I think, GroupBuilder and RouteBuilder are not needed.

@vjik vjik mentioned this pull request Nov 2, 2025
@rustamwin
Copy link
Copy Markdown
Member Author

Can we make Route and Group constructors public

Did you mean builders? Route and Group constructors are public in this PR.

I think, GroupBuilder and RouteBuilder are not needed.

Will we abandon the current syntax?

@vjik
Copy link
Copy Markdown
Member

vjik commented Nov 7, 2025

Can we make Route and Group constructors public

Did you mean builders? Route and Group constructors are public in this PR.

No, I mean Yiisoft\Router\Router and Yiisoft\Router\Group

I think, GroupBuilder and RouteBuilder are not needed.

Will we abandon the current syntax?

Mark as deprecated, then remove in major version.

@rustamwin
Copy link
Copy Markdown
Member Author

Mark as deprecated, then remove in major version.

Let's abandon right away since the PR has major changes.

@vjik
Copy link
Copy Markdown
Member

vjik commented Nov 13, 2025

Let's abandon right away since the PR has major changes.

What major changes are needed?

# Conflicts:
#	composer.json
#	src/Debug/DebugRoutesCommand.php
#	src/Route.php
rustamwin and others added 3 commits April 15, 2026 22:11
# Conflicts:
#	src/Group.php
#	src/Route.php
#	src/RouteCollection.php
#	src/RouteCollector.php
#	tests/ConfigTest.php
#	tests/Debug/DebugRoutesCommandTest.php
#	tests/Debug/RouterCollectorTest.php
#	tests/Debug/UrlMatcherInterfaceProxyTest.php
#	tests/GroupTest.php
#	tests/RouteTest.php
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new routing syntax by adding immutable builder classes (RouteBuilder, GroupBuilder) while refactoring core Route/Group to mutable objects with constructors + getters/setters. It also updates routing collection/dispatch/debug code and rewrites tests to match the new APIs (breaking BC as noted).

Changes:

  • Add RoutableInterface plus new Builder\RouteBuilder and Builder\GroupBuilder that convert to core Route/Group via toRoute().
  • Refactor Route, Group, RouteCollector, and RouteCollection APIs (getters/setters; middleware collection rename; host→hosts changes).
  • Update tests and debug tooling to use builders/getters, and bump dev dependencies.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/RouteTest.php Reworked tests for new Route constructor + setters/getters and validation.
tests/RouteCollectorTest.php Updated to builder aliases and getMiddlewares() API.
tests/RouteCollectionTest.php Updated for new route/group APIs and enabled middleware retrieval via getters.
tests/Middleware/RouterTest.php Adjusted matcher to return built routes via toRoute().
tests/MatchingResultTest.php Updated to instantiate Route directly with methods/pattern.
tests/HydratorAttribute/RouteArgumentTest.php Updated to convert builder route to core route with toRoute().
tests/GroupTest.php Reworked tests for new Group constructor + setters/getters and validation.
tests/Debug/UrlMatcherInterfaceProxyTest.php Updated to instantiate Route with methods/pattern.
tests/Debug/RouterCollectorTest.php Updated expectations for collected keys (hosts) and new route creation patterns.
tests/Debug/DebugRoutesCommandTest.php Updated to use RouteBuilder in fixtures.
tests/CurrentRouteTest.php Updated CurrentRoute API host→hosts and new Route constructor usage.
tests/ConfigTest.php Updated config test to use RouteBuilder and toRoute().
tests/Builder/RouteBuilderTest.php New test coverage for builder-based fluent syntax and immutability expectations.
tests/Builder/GroupBuilderTest.php New test coverage for group builder syntax, CORS behavior, and immutability expectations.
src/RouteCollectorInterface.php Accept RoutableInterface and rename middleware getter to getMiddlewares().
src/RouteCollector.php Implement RoutableInterface support and rename internal middleware storage/getter.
src/RouteCollection.php Convert RoutableInterface via toRoute(), switch to setter-based middleware/host/pattern/name updates.
src/Route.php Make Route mutable with constructor + getters/setters; action separated from middleware list; add validations/caching.
src/RoutableInterface.php New interface to unify builder→route/group conversion.
src/Middleware/Router.php Dispatch using Route::getEnabledMiddlewares() instead of getData().
src/Group.php Make Group mutable with constructor + getters/setters; add validation and enabled middleware caching.
src/Debug/RouterCollector.php Switch debug output to new getters; host→hosts key changes; middleware/action extraction updated.
src/Debug/DebugRoutesCommand.php Replace custom array list detection with array_is_list().
src/CurrentRoute.php Update to use route getters and host→hosts API.
src/Builder/RouteBuilder.php New immutable fluent builder for routes, converting to core Route.
src/Builder/GroupBuilder.php New immutable fluent builder for groups, converting to core Group.
composer.json Bump dev dependencies (PHPUnit, Rector, Psalm, yiisoft packages).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Route.php
Comment on lines +234 to 246
public function setDefaults(array $defaults): self
{
$route = clone $this;
if ($this->actionAdded) {
/**
* @psalm-suppress PropertyTypeCoercion Keys in the replacement array are not preserved.
* @infection-ignore-all
*/
array_splice(
$route->middlewares,
offset: count($route->middlewares) - 1,
length: 0,
replacement: $definition,
);
} else {
array_push(
$route->middlewares,
...array_values($definition),
);
/** @var mixed $value */
foreach ($defaults as $key => $value) {
if (!is_scalar($value) && !($value instanceof Stringable) && null !== $value) {
throw new InvalidArgumentException(
'Invalid $defaults provided, indexed array of scalar or `Stringable` or null expected.',
);
}
$this->defaults[$key] = (string) $value;
}

$route->enabledMiddlewaresCache = null;

return $route;
return $this;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setDefaults() merges into $this->defaults but never clears existing defaults. If callers expect a typical setter semantics (replace), old defaults will persist when passing a smaller/empty defaults array. Consider either resetting $this->defaults first, or renaming the API to indicate merging behavior.

Copilot uses AI. Check for mistakes.
Comment thread src/Group.php
Comment on lines +116 to 130
public function setHosts(array $hosts): self
{
foreach ($hosts as $host) {
if (!is_string($host)) {
throw new InvalidArgumentException('Invalid $hosts provided, list of string expected.');
}
$host = rtrim($host, '/');

if ($host !== '' && !in_array($host, $new->hosts, true)) {
$new->hosts[] = $host;
if ($host !== '' && !in_array($host, $this->hosts, true)) {
$this->hosts[] = $host;
}
}

return $new;
return $this;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setHosts() appends into $this->hosts without resetting it first. That means calling setHosts() multiple times (or calling it with an empty array) won't replace existing hosts, which is inconsistent with Route::setHosts() and with typical setter semantics. Consider resetting $this->hosts = [] at the start and then applying the same trim/unique logic.

Copilot uses AI. Check for mistakes.
$action = array_pop($middlewares);

return [$middlewares, $action];
return [$route->getMiddlewares(), $route->getAction()];
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMiddlewaresAndAction() now returns getMiddlewares() + getAction(), which ignores disabled middleware filtering and diverges from the previous behavior that reported the effective middleware stack for the matched route. For debugging output it’s usually more accurate to use getEnabledMiddlewares() (then separate the action if non-null) so the panel reflects what will actually execute.

Suggested change
return [$route->getMiddlewares(), $route->getAction()];
$middlewares = $route->getEnabledMiddlewares();
$action = $route->getAction();
return [$middlewares, $action];

Copilot uses AI. Check for mistakes.
Comment thread src/Route.php
public function getName(): string
{
return self::methods([Method::OPTIONS], $pattern);
return $this->name ??= (implode(', ', $this->methods) . ' ' . implode('|', $this->hosts) . $this->pattern);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getName() caches the autogenerated name via ??=. With the new mutable setters (setPattern(), setHosts(), setMethods()), this can leave the route name stale if getName() was called before a subsequent mutation (the cached computed name will no longer reflect the current methods/hosts/pattern). Consider either (1) not persisting the computed name (return $this->name ?? <computed> without assignment), or (2) tracking whether the name was explicitly set and clearing/recomputing it when relevant setters are called.

Suggested change
return $this->name ??= (implode(', ', $this->methods) . ' ' . implode('|', $this->hosts) . $this->pattern);
return $this->name ?? (implode(', ', $this->methods) . ' ' . implode('|', $this->hosts) . $this->pattern);

Copilot uses AI. Check for mistakes.
Comment thread src/Route.php
$route = clone $this;
$route->override = true;
return $route;
$this->action = $action;
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setAction() does not invalidate $enabledMiddlewaresCache. If getEnabledMiddlewares() is called before setAction(), subsequent calls will keep returning the cached list without the action appended. Clearing $enabledMiddlewaresCache (and possibly any derived name cache if applicable) in setAction() would prevent stale middleware stacks.

Suggested change
$this->action = $action;
$this->action = $action;
$this->enabledMiddlewaresCache = null;

Copilot uses AI. Check for mistakes.
Comment thread src/Route.php
Comment on lines +189 to 201
public function setMethods(array $methods): self
{
return $this->hosts($host);
if (empty($methods)) {
throw new InvalidArgumentException('$methods cannot be empty.');
}
foreach ($methods as $method) {
if (!is_string($method)) {
throw new InvalidArgumentException('Invalid $methods provided, list of string expected.');
}
$this->methods[] = $method;
}
return $this;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMethods() appends to $this->methods but never resets it. Calling setMethods() more than once will accumulate/duplicate methods instead of replacing them, which is surprising for a setter and can also break the default name generation. Consider resetting $this->methods at the start and (if you keep name caching) clearing any cached autogenerated name.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:code review The pull request needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants