Routing files
This is caused because of the way you have your options set in your command to php: you set the routing file to be
index.php`...
php -S localhost:8888 index.php
^^^^^^^^^
...this means that every request will go via that file first and then decide what to do. For example, suppose our index.php
file contains the following:
if (rand(1,100) % 2) {
echo "Not a multiple of 2\n";
return true;
} else {
echo "Multiple of 2\n";
return false;
}
Note: return true;
technically isn't needed but I've included it for clarity
What happens here is that the request is made and the routing file is run. If the routing file returns false
(i.e. is a Multiple of 2) then the request goes through to the requested file. If the code returns true
then execution stops and the requested file isn't accessed.
In your case the requested file is index.php
(the same file) so, with the above code, you will always end up with output like:
Not a multiple of 2
// OR
Multiple of 2
Multiple of 2
// OR
Multiple of 2
Not a multiple of 2
Break down of the http request with your code/setup
So, looking at your actual code...
session_start();
if (!empty($_POST['token'])) {
var_dump($_POST['token'], $_SESSION['token']);
exit;
}
$_SESSION['token'] = rand();
echo '<form ...>...</form';
What happens here?
- You make a request for
index.php
*
- The server accesses the routing file
index.php
to decide what to do
- The routing file outputs data which effectively indicates
true
- The data (form) from the routing file is returned & the SESSION variable has been set
- The request for a file is ignored
- You submit the form
- The request goes through to the routing file to decide what to do (NOT the location of the attribute
action
**)
- The routing file checks the
POST>token
against SESSION>token
.
- They should match and so the routing file effectively outputs data which indicates
true
***
- The data is returned
- The request for a file (from the
action
attribute) is ignored
* It makes 0 difference what file you attempt to access; try it with infkdsngfdslghfdslgnfdg.php
and it'll still work the same way. Your requested file is never accessed it only appears that it is because the requested and routed file are the same!
** As above, you can set the action attribute to almost anything, try fdsfnldgksdf.php
*** Whether the tokens match or not the routing file still outputs data which equates to true
As per @brombeer's test this does work as expected, so why doesn't it work for you?
The problem
If you check the command prompt/terminal where the server is running you get a stream of what is happening (e.g. when a request is a accepted etc.). You'll notice that if you watch that when making a request you get results like:
[DATE] [::1]:XXX01 Accepted
[DATE] [::1]:XXX02 Accepted
[DATE] [::1]:XXX01 Closing
[DATE] [::1]:XXX02 Closing
@brombeer on the other hand will get results like:
[DATE] [::1]:XXX01 Accepted
[DATE] [::1]:XXX01 Closing
This is the problem. You're making two requests to @brombeer's one request and both requests go through the routing file.
The first request is the one you expect and you get output as you would expect. However, after you receive that output the second request runs (which follows the exact same flow as described above - remember it doesn't matter what file is requested the script will output the same thing(!) - and effectively changes the $_SESSION["token"]
to a new random number.
This can perhaps be seen more easily if you change...
$_SESSION["token"] = rand();
...to...
$_SESSION["token"]++;
What is the second request?
This is nothing to do with PHP; it's all to do with your browser. Browsers make requests for all sorts of things other than the requested file. For example:
- JavaScript files
- CSS files
- Images used on the page
Of course, you aren't using any of that in this example. However, there are some resources that browsers look for whether you tell them to or not: usually based on context.
In this case your browser is smart enough to know that you're trying to access a website (likely because of the port number but maybe because of the request method or URI).
So it tries to locate some additional files that it would expect to find on a website, specifically: favicon.ico
(you should be able to see this request in your bowser's dev tools under Network
).
As already explained, because you've set your server up with a routing file that request goes through the same exact process as the index.php
or fnjksgjfndsglkjnsf.php
requests. In fact the actual icon file is never even looked for.
You can prove this further by adding this code to the top of your file...
if (!strpos($_SERVER["REQUEST_URI"], ".php")) {
return false;
}
This will stop execution in your routing file if the requested file isn't a php
file. Additionally because we return false
the server will look for the icon file. Returning true
would also work but the icon file wouldn't be looked for.
You could also try changing the port to something like :8030
and I expect the code would work as you expect (because the browser won't request a favicon).
The solution
Referencing back to my first comment on the question...
Why would you pass a file to the server in this scenario?
I'm still not sure why you've done it: I assume that it's because you didn't understand what a routing file did? Or perhaps that you didn't understand that you were creating a routing file?
Hopefully we've cleared that up here?
Either way I am fairly certain that setting up a routing file
isn't what you intended and for your purposes it doesn't appear that it's what you need either.
So just don't add index.php
to the end of the command.
Additional worked example
Replace your index.php
with the following code:
session_start();
echo "<pre>";
if (!empty($_POST['token'])) {
var_dump($_POST['token'], $_SESSION['token']);
exit;
}
$_SESSION['token']++;
echo '<form action="indasdasfdex.php" method="post"><input name="token" value="' . $_SESSION['token'] . '"><input type="submit" value="submit"></form>';
var_dump($_POST['token'] ?? null, $_SESSION['token'] ?? null);
Run your server with index.php
as the router file (as per your original question):
php -S localhost:8888 index.php
// Because you require `index.php` in your `server.php`
// this will work the same if you use `server.php` instead
This is what happens
Request: index.php
Router file runs `$_SESSION["token"] == 1`
Router file returns data to browser with a form: `"token" == 1`
Request terminated
Browser shows returned data
Form: `token.value == 1`
`var_dump` output
`$_POST["token"] == null`
`$_SESSION["token"] == 1`
Request: favicon.ico
Router file runs `$_SESSION["token"] == 2`
Router file returns data to browser with a form `"token" == 2`
Request terminated
Browser doesn't show returned data (but you can see it in dev tools)
_Not shown because an image isn't returned!_
_Can be seen in dev tools_
Form: `token.value == 2`
`var_dump` output
`$_POST["token"] == null`
`$_SESSION["token"] == 2`
Request: submit form to index.php
Router file runs
Dumps `$_SERVER["token"]` and `$_POST["token"]` (2,1)
Router file `exit`
Request terminated
Browser shows returned data
`var_dump` output
`$_POST["token"] == 1`
`$_SESSION["token"] == 2`
Request: favicon.ico
Router file runs `$_SESSION["token"] == 3`
Router file returns data to browser with a form `"token" == 3`
Request terminated
Browser doesn't show returned data (but you can see it in dev tools)
_Not shown because an image isn't returned!_
_Can be seen in dev tools_
Form: `token.value == 3`
`var_dump` output
`$_POST["token"] == null`
`$_SESSION["token"] == 3`**