It's been a while since I've touched PHP, and I've been working in C# for a while. I need to do some file reading/writing, but I'm not sure where to start. I've been spoiled by Visual Studio's code-completion and real-time error checking, and it's a bit difficult going over to such a weakly-typed language.
In PHP, what's returned when reading a file, and what needs to be written when writing?
I need to work with the file in hex, but decimal would be fine too. Is there any way to read it in any way but a string?
There is a several ways to read and write files:
You can create a handler by fopen() function.
The other way is just file_get_contents(), this function just returns content. And file_put_contents() just put any data to file.
As example of the handler, here is a logging stuff:
if (!is_writable($this->file) && $name !== self::CORE_LOG)
{
self::getInstance(self::CORE_LOG)->log(sprintf('Couldn\'t write to file %s. Please, check file credentials.', $name));
}
else
{
$this->handler = fopen($this->file, 'a+');
self::$instances[$name] = &$this;
}
...
if ($this->handler)
fwrite($this->handler, '[' . date('r') . '] : ' . $l . "\n");
...
if ($this->handler)
fclose($this->handler);
Here you can read about and Filesystem managment functions
Related
I'm a student, new to PHP (and web development in general), and am trying to write a simple interface with my college's WebDAV server.
The following code (with appropriate credentials and address), which uses an HTTP WebDAV Client plugin I found (https://github.com/pear/HTTP_WebDAV_Client), successfully returns the first 8k of data from the .txt/.html/.js files I've tried it with, but no more.
From what I can tell, the likely culprit is that the server is using chunked transfer encoding (which makes sense), which leads me to believe I will have to read in a stream of data, rather than a single file/chunk (again, I'm new to this). If so, I'm not certain of how to accomplish this.
My understanding is that cURL would likely be the fastest way to do this, but I don't think Dreamhost has cURL enabled for php.
//this loads the HTTP_WebDAV_Client plugin:
// https://github.com/pear/HTTP_WebDAV_Client
require_once "HTTP/WebDAV/Client.php";
//this is for testing purposes only (for obvious reasons)
$user = $_GET['user'];
$pass = $_GET['pass'];
$fileName = $_GET['fileName'];
//actual address obscured
$dir = "webdavs://" . $user . ":" . $pass . "#webfs.xxx.com/main/hcwebdav/";
$file = fopen($dir.$fileName, "rb");
//$content;
if (!$file) {
die("Error opening file :( $user $pass");
} else {
//This returns only the first chunk:
echo file_get_contents($dir.$fileName);
//this has the same behavior
/*
while ($line = fread($file, 8192)) {
$content .= $line;
}
echo $content;
fclose($file);
*/
}
I hope this question isn't too stupid :/ I'm trying to write web applications to help intro-level students learn to code, and this plug-in would make it very easy for them to publish their own websites from a browser-based code editor/mini-IDE!
Cheers!
My suggestions leads away from the package you are using and your issue with it, but SabreDAV is the most popular WebDAV in the PHP community, so why not using it instead.
http://sabre.io/
https://github.com/fruux/sabre-dav
Docu: http://sabre.io/dav/davclient/
I'm using xampp in Windows for local developing environment. I tried to create a simple logger which appends to a text file.
function Logger ($logmessage)
{
$filename = '/errorlog-' . date('Ymd') . '.txt';
file_put_contents($filename, $logmessage, FILE_APPEND | LOCK_EX);
}
I've tried to echo the $filename, and it says '/errorlog-20140427.txt' which means it already has valid filename (I think).
But when I call this logger function, there's no error raised, but I can't find the file everywhere. I tried to search for the whole htdocs for *.txt but no files found. Do you know why I can't write file using php? Do I need to use fopen first? As I refer to another help, I can just use file_put_contents directly without fopen. Thanks for the help.
You're trying to write to the root of your filesystem, e.g.
$file = '/error-log-' etc...
is essentially the same as
$file = 'c:/error-log' etc...
The webserver account is highly unlikely to have the rights to do anything in C:\.
If you'd bothered checking the return value of file_put_contents, you'd have seen it was returning a boolean false to indicate failure.
I have a directory with a number of subdirectories that users add files to via FTP. I'm trying to develop a php script (which I will run as a cron job) that will check the directory and its subdirectories for any changes in the files, file sizes or dates modified. I've searched long and hard and have so far only found one script that works, which I've tried to modify - original located here - however it only seems to send the first email notification showing me what is listed in the directories. It also creates a text file of the directory and subdirectory contents, but when the script runs a second time it seems to fall over, and I get an email with no contents.
Anyone out there know a simple way of doing this in php? The script I found is pretty complex and I've tried for hours to debug it with no success.
Thanks in advance!
Here you go:
$log = '/path/to/your/log.js';
$path = '/path/to/your/dir/with/files/';
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
$result = array();
foreach ($files as $file)
{
if (is_file($file = strval($file)) === true)
{
$result[$file] = sprintf('%u|%u', filesize($file), filemtime($file));
}
}
if (is_file($log) !== true)
{
file_put_contents($log, json_encode($result), LOCK_EX);
}
// are there any differences?
if (count($diff = array_diff($result, json_decode(file_get_contents($log), true))) > 0)
{
// send email with mail(), SwiftMailer, PHPMailer, ...
$email = 'The following files have changed:' . "\n" . implode("\n", array_keys($diff));
// update the log file with the new file info
file_put_contents($log, json_encode($result), LOCK_EX);
}
I am assuming you know how to send an e-mail. Also, please keep in mind that the $log file should be kept outside the $path you want to monitor, for obvious reasons of course.
After reading your question a second time, I noticed that you mentioned you want to check if the files change, I'm only doing this check with the size and date of modification, if you really want to check if the file contents are different I suggest you use a hash of the file, so this:
$result[$file] = sprintf('%u|%u', filesize($file), filemtime($file));
Becomes this:
$result[$file] = sprintf('%u|%u|%s', filesize($file), filemtime($file), md5_file($file));
// or
$result[$file] = sprintf('%u|%u|%s', filesize($file), filemtime($file), sha1_file($file));
But bare in mind that this will be much more expensive since the hash functions have to open and read all the contents of your 1-5 MB CSV files.
I like sfFinder so much that I wrote my own adaption:
http://www.symfony-project.org/cookbook/1_0/en/finder
https://github.com/homer6/altumo/blob/master/source/php/Utils/Finder.php
Simple to use, works well.
However, for your use, depending on the size of the files, I'd put everything in a git repository. It's easy to track then.
HTH
If the code is the same, there appears to be a difference between:
include 'external.php';
and
eval('?>' . file_get_contents('external.php') . '<?php');
What is the difference? Does anybody know?
I know the two are different because the include works fine and the eval gives an error. When I originally asked the question, I wasn't sure whether it gave an error on all code or just on mine (and because the code was evaled, it was very hard to find out what the error meant). However, after having researched the answer, it turns out that whether or not you get the error does not depend on the code in the external.php, but does depend on your php settings (short_open_tag to be precise).
After some more research I found out what was wrong myself. The problem is in the fact that <?php is a "short opening tag" and so will only work if short_open_tag is set to 1 (in php.ini or something to the same effect). The correct full tag is <?php, which has a space after the second p.
As such the proper equivalent of the include is:
eval('?>' . file_get_contents('external.php') . '<?php ');
Alternatively, you can leave the opening tag out all together (as noted in the comments below):
eval('?>' . file_get_contents('external.php'));
My original solution was to add a semicolon, which also works, but looks a lot less clean if you ask me:
eval('?>' . file_get_contents('external.php') . '<?php;');
AFAIK you can't take advantage of php accelerators if you use eval().
If you are using a webserver on which you have installed an opcode cache, like APC, eval will not be the "best solution" : eval'd code is not store in the opcode cache, if I remember correctly (and another answer said the same thing, btw).
A solution you could use, at least if the code is not often changed, is get a mix of code stored in database and included code :
when necessary, fetch the code from DB, and store it in a file on disk
include that file
as the code is now in a file, on disk, opcode cache will be able to cache it -- which is better for performances
and you will not need to make a request to the DB each time you have to execute the code.
I've worked with software that uses this solution (the on-disk file being no more than a cache of the code stored in DB), and I worked not too bad -- way better that doing loads of DB requests of each page, anyway...
Some not so good things, as a consequence :
you have to fetch the code from the DB to put it in the file "when necessary"
this could mean re-generating the temporary file once every hour, or deleting it when the entry in DB is modified ? Do you have a way to identify when this happens ?
you also have to change your code, to use the temporary file, or re-generate it if necessary
if you have several places to modifiy, this could mean some work
BTW : would I dare saying something like "eval is evil" ?
This lets you include a file assuming file wrappers for includes is on in PHP:
function stringToTempFileName($str)
{
if (version_compare(PHP_VERSION, '5.1.0', '>=') && strlen($str < (1024 * 512))) {
$file = 'data://text/plain;base64,' . base64_encode($str);
} else {
$file = Utils::tempFileName();
file_put_contents($file, $str);
}
return $file;
}
... Then include that 'file.' Yes, this will also disable opcode caches, but it makes this 'eval' the same as an include with respect to behavior.
As noted by #bwoebi in this answer to my question, the eval substitution does not respect the file path context of the included file. As a test case:
Baz.php:
<?php return __FILE__;
Foo.php:
<?php
echo eval('?>' . file_get_contents('Baz.php', FILE_USE_INCLUDE_PATH)) . "\n";
echo (include 'Baz.php') . "\n";
Result of executing php Foo.php:
$ php Foo.php
/path/to/file/Foo.php(2) : eval()'d code
/path/to/file/Baz.php
I don't know of any way to change the __FILE__ constant and friends at runtime, so I do not think there is any general way to define include in terms of eval.
Only eval('?>' . file_get_contents('external.php')); variant is correct replacement for include.
See tests:
<?php
$includes = array(
'some text',
'<?php print "some text"; ?>',
'<?php print "some text";',
'some text<?php',
'some text<?php ',
'some text<?php;',
'some text<?php ?>',
'<?php ?>some text',
);
$tempFile = tempnam('/tmp', 'test_');
print "\r\n" . "Include:" . "\r\n";
foreach ($includes as $include)
{
file_put_contents($tempFile, $include);
var_dump(include $tempFile);
}
unlink($tempFile);
print "\r\n" . "Eval 1:" . "\r\n";
foreach ($includes as $include)
var_dump(eval('?>' . $include . '<?php '));
print "\r\n" . "Eval 2:" . "\r\n";
foreach ($includes as $include)
var_dump(eval('?>' . $include));
print "\r\n" . "Eval 3:" . "\r\n";
foreach ($includes as $include)
var_dump(eval('?>' . $include . '<?php;'));
Output:
Include:
some textint(1)
some textint(1)
some textint(1)
some text<?phpint(1)
some textint(1)
some text<?php;int(1)
some textint(1)
some textint(1)
Eval 1:
some textNULL
some textNULL
bool(false)
some text<?phpNULL
bool(false)
some text<?php;NULL
some textNULL
some textNULL
Eval 2:
some textNULL
some textNULL
some textNULL
some text<?phpNULL
some textNULL
some text<?php;NULL
some textNULL
some textNULL
Eval 3:
some text<?php;NULL
some text<?php;NULL
bool(false)
some text<?php<?php;NULL
bool(false)
some text<?php;<?php;NULL
some text<?php;NULL
some text<?php;NULL
Some thoughts about the solutions above:
Temporary file
Don't. It's very bad for performance, just don't do it. Not only does it drive your opcode cache totally crazy (cache hit never happens + it tries to cache it again every time) but also gives you the headache of filesystem locking under high (even moderate) loads, as you have to write the file and Apache/PHP has to read it.
Simple eval()
Acceptable in rare cases; don't do it too often. Indeed it's not cached (poor opcode cache just doesn't know it's the same string as before); at the same time, if your code is changing each time, eval is A LOT BETTER than include(), mostly because include() fills up the opcode cache on each call. Just like the tempfile case. It's horrible (~4x slower).
In-memory eval()
Actually, eval is very fast when your script is already in the string; most of the time it's the disk operation that pulls it back, now surely this depends on what you do in the script but in my very-small-script case, it was ~400 times faster. (Do you have memcached? Just thinking loud) So what include() can't do is evaluate the same thing twice without file operation, and this is very important. If you use it for ever-changing, small, memory-generated strings, obviously it's eval to choose - it's many-many times faster to load once + eval again and again than an iterated include().
TL;DR
Same code, once per request: include
Same code, several calls per request: eval
Varying code: eval
here is my approach.
it creates temporary php file and includes it.
but this way if code you want to run on this function has errors program exits before removing temporary file
so i make an autoclean procedure in function. this way it cleans old temporary files by an timeout everytime function runs. you can set timeout or disable it from options at start of function
i also added ignore error option for solving non removed temporary files. if errors ignored, program will continue and remove temporary file.
also some projects have to disable autoclean because it scans whole directory everytime it runs. it could hurt disk performance.
function eval2($c) {
$auto_clean_old_temporary_files=false; //checks old temporary eval2 files for this spesific temporary file names generated by settings below
$ignore_all_errors=true; //if you ignore errors you can remove temporary files even there is an error
$tempfiledirectory=''; //temporary file directory
$tempfileheader='eval2_'; // temporary file header
$tempfiletimeseperator='__'; // temporary file seperator for time
$tempfileremovetimeout=200; // temp file cleaning time in seconds
if ($auto_clean_old_temporary_files===true) {
$sd=scandir('.'); //scaning for old temporary files
foreach ($sd as $sf) {
if (strlen($sf)>(32+strlen($tempfileheader)+strlen($tempfiletimeseperator)+3)) { // if filename long enough
$t1=substr($sf,(32+strlen($tempfileheader)),strlen($tempfiletimeseperator)); //searching time seperator
$t2=substr($sf,0,strlen($tempfileheader)); //searching file header
if ($t1==$tempfiletimeseperator && $t2==$tempfileheader) { //checking for timeseperator and file name header
$ef=explode('.',$sf);
unset($ef[count($ef)]);//removing file extension
$nsf=implode('.',$ef);//joining file name without extension
$ef=explode($tempfiletimeseperator,$nsf);
$tm=(int)end($ef); //getting time from filename
$tmf=time()-$tm;
if ($tmf>$tempfileremovetimeout && $tmf<123456 && $tmf>0) { // if time passed more then timeout and difference with real time is logical
unlink($sf); // finally removing temporary file
}
}
}
}
}
$n=$tempfiledirectory.$tempfileheader . md5(microtime().rand(0,5000)). $tempfiletimeseperator . time() .'.php'; //creating spesific temporary file name
$c='<?php' . PHP_EOL . $c . PHP_EOL; //generating php content
file_put_contents($n,$c); //creating temporary file
if ($ignore_all_errors===true) { // including temporary file by your choise
$s=#include($n);
}else{
$s=include($n);
}
return $s;
}
I have a config.inc file in a web application that I am building. It contains an array with configuration values for things like the MySQL database, etc. I would like these to be entered by using a simple form, that asks for the server, login/password for the database, etc, then these get written to the configuration file.
Is there a preferred method of doing this? I am not sure how to write to a file, and update an array.
You just want writing, correct? Is it a serialized array or is it parsed?
One way to read a config file is parse_ini_file(). I wouldn't necessarily call it preferred, but it's a method. You'd still need to write the file.
Another way would to write a "config.inc.php" and just include it in, to write it you'd just output actual PHP code (e.g. $var = "myval";).
This is a way you could write a simple "output" function that took an array of configuration values and output them as name=value, assuming $config was an associative array.
foreach ($config as $name => $value) {
$output .= $name . '=' . $value . "\n";
}
if (!file_put_contents($filename, $output)) {
die("Error writing config file.");
}
There's a lot of decent ways to do it. It's really based on your requirements. Does it need to be in a specific format or do you have leeway?
It is not recommended to modify PHP configuration files via your application, you should use CSV files or a database table.
In case you want to save it in a CSV file then I suggest you keep a CSV file for each configuration type (e.g CSV file for database configurations) and always overwrite the previous one using file_put_contents
Save data example:
$csvStructure = array("dbUser","dbPassword","dbHostname","dbPort"); // array used for both loading data and saving it
$csvData = array();
foreach ($csvStructure as $field) {
$csvData[] = $_POST[$field]; // so it'd get $_POST["dbUser"],$_POST["dbPasword"], etc..
}
file_put_contents("filename",implode("\t",$csvData));
Load data example:
$csvStructure = array("dbUser","dbPassword","dbHostname","dbPort"); // array used for both loading data and saving it
$dbConfig = array();
$csvData = explode("\t",file_get_contents("filename"));
foreach ($csvStructure as $key => $field) { // $key would have the location of the requested field in our CSV data (0,1,2, etc..).
$dbConfig[$field] = $csvData[$key]; // populate $dbConfig["dbUser"],$dbConfig["dbPasword"], etc..
}
I believe using an ini file is a wise option, because user, password, schema, paths, etc. are things that usually will be modified by hand, so using var_export isn't because modifying it by hand it's not so clean and may crash your application if you make a mistake in the PHP syntax.
But parsing big ini files can be expensive, so it would be OK to cache the ini with var_export() or serlialize(). It's a better choice, I think, and read the ini only when the cache file doesn't exists.
PHP has a dedicated function for this, its called var_export();
Just do:
file_put_contents("config.php",var_export($config,true));
Well, to write a file, fwrite() php function does exactly what you want. From its PHP.NET documentation page (see example below).
Now, on the question as to what to output to that file - I'm assuming that file will have to be included as a configuration .php file into the rest of the project. I'm imagining you'll do something like this - where you're creating strings with PHP code on the fly, based on the submitted form:
$strDatabaseConfig = "\$databaseConfig = array('" . $_POST['login'] . "," . $_POST['password'] . "');";
And here's the snippet for fwrite:
$filename = 'test.txt';
$somecontent = "Add this to the file\n";
// Let's make sure the file exists and is writable first.
if (is_writable($filename)) {
// In our example we're opening $filename in append mode.
// The file pointer is at the bottom of the file hence
// that's where $somecontent will go when we fwrite() it.
if (!$handle = fopen($filename, 'a')) {
echo "Cannot open file ($filename)";
exit;
}
// Write $somecontent to our opened file.
if (fwrite($handle, $somecontent) === FALSE) {
echo "Cannot write to file ($filename)";
exit;
}
echo "Success, wrote ($somecontent) to file ($filename)";
fclose($handle);
} else {
echo "The file $filename is not writable";
}
Here's one way: wp-admin/setup-config.php from WordPress.
I prefer to have a file with a bunch of define statements.
These are constants globally available (and of course immutable) which is what you need for configuration settings.
Constants offer better memory management and efficiency in reading as they don't need the extra memory required by a variable so that it can be changed.
Let's say your config.inc file looks like this:
$config = array(
'blah' => 'mmm',
'blah2' => 'www',
//...
);
You want to update it, so you create a simple form, fill text fields with current values. PHP script that overwrites current configuration could looks like this:
$newConfig = ...; // data from form - of course validate it first
$config = ...; // data from config.inc
$config = array_merge($config, $newConfig);
file_put_contents('config.inc', '<?php $config = ' . var_export($config, true));
And you're done.