This is a very simple class to handle files.
It allows to access, create and modify files in the system or 2 fake files (one in memory and other similar to /dev/null
or nul
).
Windows has a 'feature' that automatically converts all the line endings to the correct one. That is one of the features included.
The fake /dev/null
is simply a file that works similarly to the 'real deal'.
All reads to it succeed, all writes success, using seek
on it succeeds but its size is always 0.
<?
namespace IO;
final class InvalidFilename extends \Exception {
function __construct( $filename ){
$this->message = 'Invalid filename: "' . $filename . '"';
}
}
final class InvalidMode extends \Exception {
function __construct( $mode ){
$this->message = 'Invalid mode: "' . $mode . '"';
}
}
final class WriteError extends \Exception {
function __construct( $filename ){
$this->message = 'Could not write into "' . $filename . '"';
}
}
final class ReadError extends \Exception {
function __construct( $filename ){
$this->message = 'Could not read from "' . $filename . '"';
}
}
final class EOF extends \Exception {
function __construct( $filename ){
$this->message = 'End Of File "' . $filename . '"';
}
}
final class File {
const READ = 1;
const WRITE = 2;
const BINARY = 4;
const MEMORY = "1円";
const VOID = "0円";
const EOL = PHP_EOL;
const EOL_WIN = "\r\n";
const EOL_LINUX = "\n";
const EOL_OLD_MAC = "\r";
private $info=array(
'content'=>'',
'path'=>'',
'name'=>'',
'length'=>0,
'p'=>0,
'mode'=>1
);
function __construct( $filename, $mode = 1 ){
$this->info['mode'] = $mode & 7;
if( !$this->info['mode'] )
{
throw new InvalidMode( $mode );
}
if( $filename != self::MEMORY && $filename != self::VOID )
{
$this->info['path'] = dirname( $filename );
if( !$this->info['path'] )
{
$this->info['path'] = getcwd();
}
$this->info['name'] = basename( $filename );
if( !$this->info['path'] )
{
throw new InvalidFilename( $filename );
}
if( $mode & self::READ )
{
$this->info['content'] = file_get_contents( $filename );
if( $this->info['content'] === false)
{
throw new ReadError( $filename );
}
if( !( $mode & self::BINARY ) )
{
$this->fix_eol();
}
$this->info['length'] = strlen( $this->info['content'] );
}
}
else
{
$this->info['path'] = $this->info['name'] = $filename;
}
}
function fix_eol()
{
$this->info['content'] = str_replace( array( self::EOL_WIN, self::EOL_LINUX, self::EOL_OLD_MAC ), self::EOL, $this->info['content'] );
}
function eof()
{
switch( $this->info['name'] )
{
case self::VOID:
return false;
default:
return $this->info['p'] >= $this->info['length'];
}
}
function read( $bytes = -1 )
{
if( $this->info['name'] == self::VOID )
{
return '';
}
if( $this->info['name'] != self::MEMORY && !( $this->info['mode'] & self::READ ) )
{
throw new ReadError( $this->info['name'] );
}
if( $this->eof() )
{
throw new EOF( $this->info['name'] );
}
if( $bytes < 0 || $bytes > $this->info['length'] - $this->info['p'] )
{
$bytes = $this->info['length'] - $this->info['p'];
}
$p = $this->info['p'];
$this->info['p'] += $bytes;
return '' . substr( $this->info['content'], $p, $bytes );
}
function readln( $trim = true )
{
if( $this->info['name'] == self::VOID )
{
return '';
}
if( $this->info['name'] != self::MEMORY && !( $this->info['mode'] & self::READ ) )
{
throw new ReadError( $this->info['name'] );
}
if( $this->eof() )
{
throw new EOF( $this->info['name'] );
}
$line_end = strpos( $this->info['content'], self::EOL, $this->info['p'] );
$p = $this->info['p'];
if( $line_end === false )
{
$this->info['p'] = $this->info['length'];
return '' . substr( $this->info['content'], $p );
}
else
{
$this->info['p'] = $line_end + strlen( self::EOL );
return '' . substr( $this->info['content'], $p, $trim ? $line_end : $this->info['p'] );
}
}
function read_bytes( $bytes )
{
$data = $this->read( $bytes );
$return = array();
for( $i = 0, $l = strlen( $data ); $i < $l; $i++ )
{
$return[] = ord( $data[$i] );
}
return $return;
}
function write( $data )
{
if( $this->info['name'] != self::VOID )
{
if( $this->info['name'] != self::MEMORY && !( $this->info['mode'] & self::WRITE ) )
{
throw new WriteError( $this->info['name'] );
}
list($begin,$end) = array( substr( $this->info['content'],0, $this->info['p'] ), substr( $this->info['content'], $this->info['p'] ) );
$this->info['p'] = strlen( $begin . $data );
$this->info['content'] = $begin . $data . $end;
if( !( $this->info['mode'] & self::BINARY ) )
{
$this->fix_eol();
}
$this->info['length'] = strlen( $this->info['content'] );
}
return $this;
}
function writeln( $data )
{
return $this->write( $data . self::EOL );
}
function write_bytes()
{
$data = '';
foreach( func_get_args() as $byte )
{
$data .= chr( $byte );
}
return $this->write( $data );
}
function seek( $byte )
{
if( $this->info['name'] != self::VOID && is_numeric( $byte ) )
{
if( $byte >= $this->info['length'] )
{
$this->info['p'] = $this->info['length'];
}
else if( $byte >= 0)
{
$this->info['p'] = (int)$byte;
}
else
{
$this->info['p'] = $this->info['length'] - 1;
}
}
return $this;
}
function start()
{
return $this->seek( 0 );
}
function end()
{
return $this->seek( -1 );
}
function pos()
{
return $this->info['p'];
}
function size()
{
return $this->info['length'];
}
function save( $return = false )
{
if( $this->info['name'] == self::VOID || $this->info['name'] == self::MEMORY )
{
return !!$return;
}
else
{
return file_put_contents( $this->info['path'] . DIRECTORY_SEPARATOR . $this->info['name'], $this->info['content'] );
}
}
function destroy()
{
$this->info = array(
'content'=>'',
'path'=>'',
'name'=>'',
'length'=>0,
'p'=>0,
'mode'=>1
);
}
}
It is quite lengthy.
Here's an example of usage:
$file = new IO\File(File::MEMORY);
//Should output 12345
echo $file->writeln(123)->start()->writeln(12345)->start()->readln(true);
In terms of usability and readability, what else should I change?
Notice the lack of atomic changes and blocking. That wasn't the aim. The aim was to get the content, change it and then save it to the file (if it is a 'real' file).
-
\$\begingroup\$ @Quill Sorry, I've only noticed that now. I usually start function with the curly bracket on the same line, but on loops it is in a new line. Probably a bad habit I got from my boss when writting CSS. But I've noticed that it isn't following always that style. \$\endgroup\$Ismael Miguel– Ismael Miguel2015年04月14日 22:52:17 +00:00Commented Apr 14, 2015 at 22:52
-
\$\begingroup\$ Why do you have an info array with six keys instead of six private variables? \$\endgroup\$bdsl– bdsl2017年08月02日 23:20:08 +00:00Commented Aug 2, 2017 at 23:20
-
\$\begingroup\$ @bdsl I have no idea at this point. Both work properly, but maybe separate variables would be better. \$\endgroup\$Ismael Miguel– Ismael Miguel2017年08月03日 00:49:23 +00:00Commented Aug 3, 2017 at 0:49
1 Answer 1
Each class belongs in an own file
It seems to be overhead but remember: Each class has one task to solve and the same goes for files. In small projects it does not matter that much but as soon as a project grows it helps a lot. My namespaces are similiar to the path of the class location. As of this I know where every class is located - everytime I instantiate a class.
Namespaces
If you have not read yourself into namespaces I recommend it. Namespaces combined with an autoloader are a great way to make your daily developer life a lot easier. When using an autoloader you have another reason for having one class in one file.
further aspects
- In function save of class File you wrote
return !!$return
I guess there's one ! too much.
-
\$\begingroup\$ Well, I agree about the namespaces, but a class per file isn't it too much? And woouldn't the code be worst since I have to look around where I'm declaring the exception? And on the
save
method, I think it should be actuallyreturn true;
. I don't remember... (Add there: comment the code!). About the namespace, I totally agree! And that would be the best option. Thank you for the idea! \$\endgroup\$Ismael Miguel– Ismael Miguel2015年04月14日 19:47:30 +00:00Commented Apr 14, 2015 at 19:47 -
\$\begingroup\$ "If you have not read yourself into namespaces I recommend it." --> 2nd line... Autoloaders are a bad idea since they make the code even harder to read and debug. When you get the WSOD (White Screen Of Death), you won't know which one of the 6 files is failing. \$\endgroup\$Ismael Miguel– Ismael Miguel2015年04月14日 20:16:05 +00:00Commented Apr 14, 2015 at 20:16
-
\$\begingroup\$ Oh my bad. Well, if I experience a blank page I will take a look into the error log of the php application. In case it's empty or does not help I take a look in the error log of the apache server. Edit: An application should make usage of log files. \$\endgroup\$Alexander– Alexander2015年04月14日 20:22:19 +00:00Commented Apr 14, 2015 at 20:22
-
\$\begingroup\$ Yes, it should, but in a production server you don't want those since a small mistake (using
$array
when undeclared or$array[a]
instead of$array['a']
) or by mistake leavingerror_reporting(0)
behind or even having the error log disabled on thephp.ini
loaded by php without you knowing or any other reason might cause the white page without an error log. \$\endgroup\$Ismael Miguel– Ismael Miguel2015年04月14日 20:35:37 +00:00Commented Apr 14, 2015 at 20:35 -
\$\begingroup\$ You're right this should not happen but it's not recommend to develop on a production server anyway. Well, ifI have to I will enable error reporting only for my ip before making further steps. So even if I forget to remove it users will not see any errors but the blank page. \$\endgroup\$Alexander– Alexander2015年04月14日 20:43:36 +00:00Commented Apr 14, 2015 at 20:43
Explore related questions
See similar questions with these tags.