Last active
June 10, 2016 11:29
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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