question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

url.match Regex returns null

See original GitHub issue

Description

I’m trying to use route() function to get a url from route name but when I use it in my js script it throws an error :

“TypeError: this.config.url.match(…) is null”

Expected behavior

add @routes directive in blade

add routes mixin in vue

use route(‘user.ad.create’)

should return intended url

Environment

Linux Ubuntu (OS) Docker laradock (web server and software) php 7.3 mysql 8.0

  • Laravel version:  8.14.0
  • Ziggy version: 1.0

Related routes: user.ad.create

Route::get('/user/ads/create', 'AdController@create')->name('user.ad.create');

Ziggy.routes:

const Ziggy = {"url":"http:\/\/localhost\/mobile.test","port":null,"defaults":[],"routes":{"debugbar.openhandler":{"uri":"_debugbar\/open","methods":["GET","HEAD"]},"debugbar.clockwork":{"uri":"_debugbar\/clockwork\/{id}","methods":["GET","HEAD"]},"debugbar.telescope":{"uri":"_debugbar\/telescope\/{id}","methods":["GET","HEAD"]},"debugbar.assets.css":{"uri":"_debugbar\/assets\/stylesheets","methods":["GET","HEAD"]},"debugbar.assets.js":{"uri":"_debugbar\/assets\/javascript","methods":["GET","HEAD"]},"debugbar.cache.delete":{"uri":"_debugbar\/cache\/{key}\/{tags?}","methods":["DELETE"]},"horizon.stats.index":{"uri":"horizon\/api\/stats","methods":["GET","HEAD"]},"horizon.workload.index":{"uri":"horizon\/api\/workload","methods":["GET","HEAD"]},"horizon.masters.index":{"uri":"horizon\/api\/masters","methods":["GET","HEAD"]},"horizon.monitoring.index":{"uri":"horizon\/api\/monitoring","methods":["GET","HEAD"]},"horizon.monitoring.store":{"uri":"horizon\/api\/monitoring","methods":["POST"]},"horizon.monitoring-tag.paginate":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["GET","HEAD"]},"horizon.monitoring-tag.destroy":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["DELETE"]},"horizon.jobs-metrics.index":{"uri":"horizon\/api\/metrics\/jobs","methods":["GET","HEAD"]},"horizon.jobs-metrics.show":{"uri":"horizon\/api\/metrics\/jobs\/{id}","methods":["GET","HEAD"]},"horizon.queues-metrics.index":{"uri":"horizon\/api\/metrics\/queues","methods":["GET","HEAD"]},"horizon.queues-metrics.show":{"uri":"horizon\/api\/metrics\/queues\/{id}","methods":["GET","HEAD"]},"horizon.jobs-batches.index":{"uri":"horizon\/api\/batches","methods":["GET","HEAD"]},"horizon.jobs-batches.show":{"uri":"horizon\/api\/batches\/{id}","methods":["GET","HEAD"]},"horizon.jobs-batches.retry":{"uri":"horizon\/api\/batches\/retry\/{id}","methods":["POST"]},"horizon.pending-jobs.index":{"uri":"horizon\/api\/jobs\/pending","methods":["GET","HEAD"]},"horizon.completed-jobs.index":{"uri":"horizon\/api\/jobs\/completed","methods":["GET","HEAD"]},"horizon.failed-jobs.index":{"uri":"horizon\/api\/jobs\/failed","methods":["GET","HEAD"]},"horizon.failed-jobs.show":{"uri":"horizon\/api\/jobs\/failed\/{id}","methods":["GET","HEAD"]},"horizon.retry-jobs.show":{"uri":"horizon\/api\/jobs\/retry\/{id}","methods":["POST"]},"horizon.jobs.show":{"uri":"horizon\/api\/jobs\/{id}","methods":["GET","HEAD"]},"horizon.index":{"uri":"horizon\/{view?}","methods":["GET","HEAD"]},"profile.show":{"uri":"user\/profile","methods":["GET","HEAD"]},"other-browser-sessions.destroy":{"uri":"user\/other-browser-sessions","methods":["DELETE"]},"current-user.destroy":{"uri":"user","methods":["DELETE"]},"current-user-photo.destroy":{"uri":"user\/profile-photo","methods":["DELETE"]},"api-tokens.index":{"uri":"user\/api-tokens","methods":["GET","HEAD"]},"api-tokens.store":{"uri":"user\/api-tokens","methods":["POST"]},"api-tokens.update":{"uri":"user\/api-tokens\/{token}","methods":["PUT"]},"api-tokens.destroy":{"uri":"user\/api-tokens\/{token}","methods":["DELETE"]},"dashboard":{"uri":"\/","methods":["GET","HEAD"],"domain":"team.mobile.test"},"login":{"uri":"login","methods":["GET","HEAD"],"domain":"team.mobile.test"},"logout":{"uri":"logout","methods":["POST"],"domain":"team.mobile.test"},"password.request":{"uri":"forgot-password","methods":["GET","HEAD"],"domain":"team.mobile.test"},"password.email":{"uri":"forgot-password","methods":["POST"],"domain":"team.mobile.test"},"password.reset":{"uri":"reset-password\/{token}","methods":["GET","HEAD"],"domain":"team.mobile.test"},"password.update":{"uri":"reset-password","methods":["POST"],"domain":"team.mobile.test"},"register":{"uri":"register","methods":["GET","HEAD"],"domain":"team.mobile.test"},"user-profile-information.update":{"uri":"user\/profile-information","methods":["PUT"],"domain":"team.mobile.test"},"user-password.update":{"uri":"user\/password","methods":["PUT"],"domain":"team.mobile.test"},"password.confirm":{"uri":"user\/confirm-password","methods":["GET","HEAD"],"domain":"team.mobile.test"},"password.confirmation":{"uri":"user\/confirmed-password-status","methods":["GET","HEAD"],"domain":"team.mobile.test"},"two-factor.login":{"uri":"two-factor-challenge","methods":["GET","HEAD"],"domain":"team.mobile.test"},"user.dashboard":{"uri":"dashboard","methods":["GET","HEAD"],"domain":"my.mobile.test"},"user.ad.create":{"uri":"ads\/sell","methods":["GET","HEAD"],"domain":"my.mobile.test"},"user.ad.step_phone_model_variant":{"uri":"ads\/sell\/{phone_brand}\/{phone_model}","methods":["GET","HEAD"],"domain":"my.mobile.test","bindings":{"brand":"name","model":"name"}},"user.ad.step_phone_model":{"uri":"ads\/sell\/{phone_brand}","methods":["GET","HEAD"],"domain":"my.mobile.test","bindings":{"brand":"name"}},"user.ad.step_store_variant":{"uri":"ads\/sell\/store\/variant","methods":["POST"],"domain":"my.mobile.test"},"user.user.home":{"uri":"\/","methods":["GET","HEAD"],"domain":"my.mobile.test"},"user.login":{"uri":"login","methods":["GET","HEAD"],"domain":"my.mobile.test"},"user.logout":{"uri":"logout","methods":["POST"],"domain":"my.mobile.test"},"user.auth":{"uri":"auth","methods":["POST"],"domain":"my.mobile.test"},"user.verify":{"uri":"auth\/validate","methods":["POST"],"domain":"my.mobile.test"}}};

if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') {
    for (let name in window.Ziggy.routes) {
        Ziggy.routes[name] = window.Ziggy.routes[name];
    }
}

export { Ziggy };

Ziggy call and context:

// Login Page component
inertia.visit(route('ad.user.create');

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
rodrigopedracommented, Nov 14, 2020

Laravel actually doesn’t consider mobile.test, without a protocol, to be a valid URL

Well it is not Laravel that doesn’t consider it to be a valid URL, it is just not a valid URL.

Actually the console appending http://localhost behavior is due to Symfony’s HTTP Foundation using PHP’s parse_url(...) to set the request’s URL.

If you run parse_url('mobile.test') the result will be:

$ php artisan tinker
Psy Shell v0.10.4 (PHP 7.4.12 — cli) by Justin Hileman
>>> parse_url('aaa.test')
=> [
     "path" => "aaa.test",
   ]

As there is no scheme, PHP guess the user only informed a path.

When bootstrapping a console application, Laravel bootstraps a shallow request:

https://github.com/laravel/framework/blob/e9483c441d5f0c8598d438d6024db8b1a7aa55fe/src/Illuminate/Foundation/Bootstrap/SetRequestForConsole.php#L16-L34

You can see in line 32 it calls Request::create($url, ...) with the $url retrieved from config. That factory function is from Symfony’s Request, which Laravel’s Request extends from.

When Symfony tries to create this request object, it uses parse_url return value to hydrate schema, domain, and several other URL parts. As parse_url() only returned a path key, it defaults the request URL scheme to http://, the domain to localhost, and as there is a path key it assumes the path for this request is mobile.test.

https://github.com/symfony/http-foundation/blob/46ff6a7c7a747cf87ca4ca2701048c6adc9c29de/Request.php#L335-L369

Laravel’s URL generator uses the request object to guess the base URL, and not the config:

https://github.com/laravel/framework/blob/e9483c441d5f0c8598d438d6024db8b1a7aa55fe/src/Illuminate/Routing/UrlGenerator.php#L530-L550

Which I think is the most correct thing to do, as when bootstrapping a HTTP application Laravel builds the request object from PHP globals, thus it uses the real request domain instead of a hardcoded one.

That is why it ends prepending http://localhost/mobile.test to every URL generated by the UrlGenerator class.

Maybe making a PR to Laravel to add a check in the SetRequestForConsole class before it creates the shallow request, if the URL is valid, would be a good idea to prevent this kind of issues.

Something like this:

// ./vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/SetRequestForConsole.php

public function bootstrap(Application $app)
{
    $uri = $app->make('config')->get('app.url', 'http://localhost');

    /////////////////////////////////////////////
    // Fail here if invalid URL
    /////////////////////////////////////////////

    $components = parse_url($uri);

    $server = $_SERVER;

    if (isset($components['path'])) {
        $server = array_merge($server, [
            'SCRIPT_FILENAME' => $components['path'],
            'SCRIPT_NAME' => $components['path'],
        ]);
    }

    $app->instance('request', Request::create(
        $uri, 'GET', [], [], [], $server
    ));
}
0reactions
bakerkretzmarcommented, Nov 12, 2020

Going to patch this to make it a bit more forgiving, but just a heads up that Laravel actually doesn’t consider mobile.test, without a protocol, to be a valid URL: https://github.com/laravel/framework/blob/8.x/src/Illuminate/Routing/UrlGenerator.php#L575-L588

Read more comments on GitHub >

github_iconTop Results From Across the Web

match() with a regular expression returns null - Stack Overflow
You want RegExp.test , which tests a value for a match instead of retrieving the match. With your existing code, that would mean:...
Read more >
Regex.exec() returns null - The freeCodeCamp Forum
exec() is called, it starts looking for matches from this updated pointer, not the beginning of the string, and fails to find a...
Read more >
String.match( ): find one or more regular-expression ... - O'Reilly
If no match is found, match( ) returns null . Otherwise, it returns an array containing information about the match that it found....
Read more >
Methods of RegExp and String - The Modern JavaScript Tutorial
If there are no matches, no matter if there's flag g or not, null is returned. That's an important nuance. If there are...
Read more >
Regular expressions - JavaScript - MDN Web Docs
It returns true or false . match(), Returns an array containing all of the matches, including capturing groups, or null if no match...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found