Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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).

share|improve this question
1  
Keep your programming style the same in terms of whether you allocate a whole line to an open curly bracket, or whether it says on the same line as the function. –  Quill Apr 14 at 22:31
    
@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. –  Ismael Miguel Apr 14 at 22:52
    
Some programs do it automatically, like Visual Studio. You should be able to change the settings if that happens otherwise, don't worry too much about it. –  Quill Apr 14 at 22:58
    
@Quill I usually write my code on Notepad++. I'm not incredibly worried, but you just sent my OCPD through the roof. But it was a really good observation and I give you credit for that. But there must be a plugin or some sort to do the code formatting for me –  Ismael Miguel Apr 14 at 23:22

1 Answer 1

up vote 1 down vote accepted

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.
share|improve this answer
    
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 actually return 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! –  Ismael Miguel Apr 14 at 19:47
    
I have edited my post. –  AMartinNo1 Apr 14 at 20:09
    
"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. –  Ismael Miguel Apr 14 at 20:16
    
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. –  AMartinNo1 Apr 14 at 20:22
    
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 leaving error_reporting(0) behind or even having the error log disabled on the php.ini loaded by php without you knowing or any other reason might cause the white page without an error log. –  Ismael Miguel Apr 14 at 20:35

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.