Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save edmondscommerce/8c75bac8a39c6223bb5a6038a72d72b6 to your computer and use it in GitHub Desktop.
Save edmondscommerce/8c75bac8a39c6223bb5a6038a72d72b6 to your computer and use it in GitHub Desktop.
This is used to remember where you where in a file the last time that you closed it. I use it when parsing log file to prevent having to go over the entire file each time when I only want to check the lines that have been added
<?php
/**
* @category EdmondsCommerce
* @package EdmondsCommerce_
* @author Ross Mitchell <[email protected]>
*/
namespace EdmondsCommerce\ServerHardening;
use Symfony\Component\Yaml\Yaml;
/**
* Class FileHandler - This is used to handle opening the logs files.
*
* The reason that we are using a custom class for this is so we can save where we were in the file when we finished
* reading it. This means the next time we will start from there, rather than re-processing the entire file.
*
* @package EdmondsCommerce\ServerHardening
*/
class FileHandler extends \SplFileObject
{
const STATE_FILE = __DIR__ . '/config/state.yaml';
private $fileName;
/**
* FileHandler constructor. - This is where half the magic happens.
*
* First we open the file as normal and tell it to process the file.
*
* We then get the content of the state file and jump to the last know position. If this position is outside the
* file boundary, i.e. the file has been rotated, then jump back to the start of the file
*
* @param $file_name
* @param string $open_mode
* @param bool $use_include_path
* @param null $context
*/
public function __construct($file_name, $open_mode = 'r', $use_include_path = false, $context = null)
{
$this->fileName = $file_name;
parent::__construct($file_name, $open_mode, $use_include_path, $context);
$this->setFlags(FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$position = $this->_getFilePositionFromStateFile();
$this->seek($position);
if (!$this->valid()) {
$this->seek(0);
}
}
/**
* This is the other half of the magic. When the file is destroyed record the current line number to a file so it
* can be used next time the file is opened
*/
public function __destruct()
{
// The current position is the last line number plus one, so jump back one so the file will pass validation when
// it is opened
$currentPosition = $this->key() - 1;
$state = $this->getStateFile();
if (!is_array($state)) {
$state = [];
}
$state[$this->fileName] = $currentPosition;
$fileContent = $this->generateStateFile($state);
$this->writeStateFile($fileContent);
}
/**
* This is used to get the position of the current file. As we may have multiple files open, the sate file keeps an
* associate array of positions with the file path used as the key. If the file is not found in the array, then
* this will return 0 i.e. the start of the file
*
* @return int
*/
protected function _getFilePositionFromStateFile()
{
$state = $this->getStateFile();
$position = 0;
if (isset($state[$this->fileName])) {
$position = $state[$this->fileName];
}
return $position;
}
/**
* This is used to generate the state file. The array passed into the method will be YAML encoded and a comment is
* added to the top of the file explaining not to edit the file
*
* @param array $details - The array of file positions.
*
* @return string
*/
private function generateStateFile(array $details)
{
$yaml = Yaml::dump($details);
return <<<YAML
#This file is automatically generated and is used to record the where the different log files have been read to.
#Do Not edit this file
$yaml
YAML;
}
/**
* This is used to get the details from the state file. If it does not exist then it will be created with the
* current file position set to 0
*
* @return mixed
*/
private function getStateFile()
{
$stateFile = self::STATE_FILE;
if (!file_exists($stateFile)) {
$this->writeStateFile([$this->fileName => 0]);
}
$details = file_get_contents($stateFile);
$decoded = Yaml::parse($details);
return $decoded;
}
/**
* This is used to write the state file to disk - in the future I may look at saving this in a memory based caching
* system.
*
* Be aware that there is no file locking, another thing that could be improved going forward
*
* @param $data
*/
private function writeStateFile($data)
{
file_put_contents(self::STATE_FILE, $data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment