total rebuild + tests + more powerfull functions
This commit is contained in:
parent
fb5738c4b0
commit
ddf593e42d
|
@ -30,5 +30,11 @@
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"mirzaev\\csv\\": "mirzaev/csv/system/"
|
"mirzaev\\csv\\": "mirzaev/csv/system/"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\csv\\tests\\": "mirzaev/csv/tests"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\csv;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\csv\traits\file;
|
||||||
|
|
||||||
|
// Built-in libraries
|
||||||
|
use exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database
|
||||||
|
*
|
||||||
|
* Comma-Separated Values by RFC 4180
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/rfc4180 RFC 4180
|
||||||
|
* @see https://en.wikipedia.org/wiki/Create,_read,_update_and_delete CRUD
|
||||||
|
*
|
||||||
|
* @package mirzaev\csv
|
||||||
|
*
|
||||||
|
* @var string FILE Path to the database file
|
||||||
|
* @var array $columns Database columns
|
||||||
|
*
|
||||||
|
* @method void __construct(array|null $columns) Constructor
|
||||||
|
* @method void create() Write to the database file
|
||||||
|
* @method array|null read(int $amount, int $offset, bool $backwards, ?callable $filter) Read from the database file
|
||||||
|
* @method void update() @todo create + tests
|
||||||
|
* @method void delete() @todo create + teste
|
||||||
|
* @method void __set(string $name, mixed $value) Write the parameter
|
||||||
|
* @method mized __get(string $name) Read the parameter
|
||||||
|
* @method void __unset(string $name) Delete the parameter
|
||||||
|
* @method bool __isset(string $name) Check for initializing the parameter
|
||||||
|
*
|
||||||
|
* @license http://www.wtfpl.net Do What The Fuck You Want To Public License
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
|
||||||
|
class database
|
||||||
|
{
|
||||||
|
use file {
|
||||||
|
file::read as protected file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File
|
||||||
|
*
|
||||||
|
* Path directories to the file will not be created automatically to avoid
|
||||||
|
* checking the existence of all directories on every read or write operation.
|
||||||
|
*
|
||||||
|
* @var string FILE Path to the database file
|
||||||
|
*/
|
||||||
|
public const string FILE = 'database.csv';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Columns
|
||||||
|
*
|
||||||
|
* This property is used instead of adding a check for the presence of the first row
|
||||||
|
* with the designation of the column names, as well as reading these columns,
|
||||||
|
* which would significantly slow down the library.
|
||||||
|
*
|
||||||
|
* @see https://www.php.net/manual/en/function.array-combine.php Used when creating a record instance
|
||||||
|
*
|
||||||
|
* @var array $columns Database columns
|
||||||
|
*/
|
||||||
|
public protected(set) array $columns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param array|null $columns Columns
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(?array $columns = null)
|
||||||
|
{
|
||||||
|
// Initializing columns
|
||||||
|
if (isset($columns)) $this->columns = $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize
|
||||||
|
*
|
||||||
|
* Checking for existance of the database file and creating it
|
||||||
|
*
|
||||||
|
* @return bool Is the database file exists?
|
||||||
|
*/
|
||||||
|
public static function initialize(): bool
|
||||||
|
{
|
||||||
|
if (file_exists(static::FILE)) {
|
||||||
|
// The database file exists
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// The database file is not exists
|
||||||
|
|
||||||
|
// Creating the database file and exit (success/fail)
|
||||||
|
return touch(static::FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create
|
||||||
|
*
|
||||||
|
* Create records in the database file
|
||||||
|
*
|
||||||
|
* @param record $record The record
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function write(record $record, array &$errors = []): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Opening the database file
|
||||||
|
$file = fopen(static::FILE, 'c');
|
||||||
|
|
||||||
|
if (flock($file, LOCK_EX)) {
|
||||||
|
// The file was locked
|
||||||
|
|
||||||
|
// Writing the serialized record to the database file
|
||||||
|
fwrite($file, $record->serialize());
|
||||||
|
|
||||||
|
// Applying changes
|
||||||
|
fflush($file);
|
||||||
|
|
||||||
|
// Unlocking the file
|
||||||
|
flock($file, LOCK_UN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinitializing unnecessary variables
|
||||||
|
unset($serialized, $record, $before);
|
||||||
|
|
||||||
|
// Closing the database file
|
||||||
|
fclose($file);
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to the buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read
|
||||||
|
*
|
||||||
|
* Read records in the database file
|
||||||
|
*
|
||||||
|
* @param int $amount Amount of records
|
||||||
|
* @param int $offset Offset of rows for start reading
|
||||||
|
* @param bool $backwards Read from end to beginning?
|
||||||
|
* @param callable|null $filter Filter for records function($record, $records): bool
|
||||||
|
* @param array &$errors Buffer of errors
|
||||||
|
*
|
||||||
|
* @return array|null Readed records
|
||||||
|
*/
|
||||||
|
public static function read(int $amount = 1, int $offset = 0, bool $backwards = false, ?callable $filter = null, array &$errors = []): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Opening the database file
|
||||||
|
$file = fopen(static::FILE, 'r');
|
||||||
|
|
||||||
|
// Initializing the buffer of readed records
|
||||||
|
$records = [];
|
||||||
|
|
||||||
|
// Continuing reading
|
||||||
|
offset:
|
||||||
|
|
||||||
|
foreach (static::file(file: $file, offset: $offset, rows: $amount, position: 0, step: $backwards ? -1 : 1) as $row) {
|
||||||
|
// Iterating over rows
|
||||||
|
|
||||||
|
if ($row === null) {
|
||||||
|
// Reached the end or the beginning of the file
|
||||||
|
|
||||||
|
// Deinitializing unnecessary variables
|
||||||
|
unset($row, $record, $offset);
|
||||||
|
|
||||||
|
// Closing the database file
|
||||||
|
fclose($file);
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $records;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing record
|
||||||
|
$record = new record($row)->combine($this);
|
||||||
|
|
||||||
|
if ($record) {
|
||||||
|
// Initialized record
|
||||||
|
|
||||||
|
if ($filter === null || $filter($record, $records)) {
|
||||||
|
// Filter passed
|
||||||
|
|
||||||
|
// Writing to the buffer of readed records
|
||||||
|
$records[] = $record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinitializing unnecessary variables
|
||||||
|
unset($row, $record);
|
||||||
|
|
||||||
|
if (count($records) < $amount) {
|
||||||
|
// Fewer rows were read than requested
|
||||||
|
|
||||||
|
// Writing offset for reading
|
||||||
|
$offset += $amount;
|
||||||
|
|
||||||
|
// Continuing reading (enter to the recursion)
|
||||||
|
goto offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinitializing unnecessary variables
|
||||||
|
unset($offset);
|
||||||
|
|
||||||
|
|
||||||
|
// Closing the database file
|
||||||
|
fclose($file);
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $records;
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Write to the buffer of errors
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,94 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace mirzaev\csv\interfaces;
|
|
||||||
|
|
||||||
// Files of the project
|
|
||||||
use mirzaev\csv\traits\csv as csv_trait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSV
|
|
||||||
*
|
|
||||||
* Comma-Separated Values by RFC 4180
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc4180
|
|
||||||
*
|
|
||||||
* @used-by csv_trait
|
|
||||||
* @package mirzaev\csv\interfaces
|
|
||||||
*
|
|
||||||
* @var string FILE Path to the database file
|
|
||||||
*
|
|
||||||
* @method void static write() Write to the database file
|
|
||||||
* @method array|null static read() Read from the database file
|
|
||||||
* @method string|false static serialize() Preparing data for writing to the database
|
|
||||||
* @method array|false static deserialize() Preparing data from the database to processing
|
|
||||||
*
|
|
||||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
|
||||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
||||||
*/
|
|
||||||
interface csv
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* File
|
|
||||||
*
|
|
||||||
* Path directories to the file will not be created automatically to avoid
|
|
||||||
* checking the existence of all directories on every read or write operation.
|
|
||||||
*
|
|
||||||
* @var string FILE Path to the database file
|
|
||||||
*/
|
|
||||||
public const string FILE = 'database.csv';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write
|
|
||||||
*
|
|
||||||
* Write to the database file
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function write(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read
|
|
||||||
*
|
|
||||||
* Read from the start of the database file
|
|
||||||
*
|
|
||||||
* @param int $rows Amount of rows for reading
|
|
||||||
*
|
|
||||||
* @return array|null Readed records
|
|
||||||
*/
|
|
||||||
public static function read(int $rows = 1): ?array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last
|
|
||||||
*
|
|
||||||
* Read from the end of the database file
|
|
||||||
*
|
|
||||||
* @param int $rows Amount of rows for reading
|
|
||||||
*
|
|
||||||
* @return array|null Readed records
|
|
||||||
*/
|
|
||||||
public static function last(int $rows = 1): ?array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize
|
|
||||||
*
|
|
||||||
* Preparing data for writing to the database
|
|
||||||
*
|
|
||||||
* @param array $parameters Values for serializing
|
|
||||||
*
|
|
||||||
* @return string|false Serialized data
|
|
||||||
*/
|
|
||||||
public static function serialize(array $parameters): string|false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserialize
|
|
||||||
*
|
|
||||||
* Preparing data from the database to processing
|
|
||||||
*
|
|
||||||
* @param string $row Record for deserializing
|
|
||||||
*
|
|
||||||
* @return array|false Serialized data
|
|
||||||
*/
|
|
||||||
public static function deserialize(string $row): array|false;
|
|
||||||
}
|
|
|
@ -0,0 +1,237 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\csv;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use mirzaev\csv\database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSV
|
||||||
|
*
|
||||||
|
* Comma-Separated Values by RFC 4180
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/rfc4180 RFC 4180
|
||||||
|
*
|
||||||
|
* @package mirzaev\csv
|
||||||
|
*
|
||||||
|
* @var array $parameters Parameters of the record
|
||||||
|
*
|
||||||
|
* @method void __construct(string|null $row) Constructor
|
||||||
|
* @method string static serialize() Convert record instance to values for writing into the database
|
||||||
|
* @method void static unserialize(string $row) Convert values from the database and write to the record instance
|
||||||
|
* @method void __set(string $name, mixed $value) Write the parameter
|
||||||
|
* @method mized __get(string $name) Read the parameter
|
||||||
|
* @method void __unset(string $name) Delete the parameter
|
||||||
|
* @method bool __isset(string $name) Check for initializing the parameter
|
||||||
|
*
|
||||||
|
* @license http://www.wtfpl.net Do What The Fuck You Want To Public License
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class record
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parameters
|
||||||
|
*
|
||||||
|
* Mapped with database::COLUMN
|
||||||
|
*
|
||||||
|
* @var array $parameters Parameters of the record
|
||||||
|
*/
|
||||||
|
public protected(set) array $parameters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string|null $row Row for converting to record instance parameters
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(?string $row = null)
|
||||||
|
{
|
||||||
|
// Initializing parameters
|
||||||
|
if (isset($row)) $this->parameters = static::deserialize($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Columns
|
||||||
|
*
|
||||||
|
* Combine parameters of the record with columns of the database
|
||||||
|
* The array of parameters of the record will become associative
|
||||||
|
*
|
||||||
|
* @return static The instance from which the method was called (fluent interface)
|
||||||
|
*/
|
||||||
|
public function columns(database $database): static
|
||||||
|
{
|
||||||
|
// Combining database columns with record parameters
|
||||||
|
$this->parameters = array_combine($database->columns, $this->parameters);
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize
|
||||||
|
*
|
||||||
|
* Convert record instance to values for writing into the database
|
||||||
|
*
|
||||||
|
* @return string Serialized record
|
||||||
|
*/
|
||||||
|
public function serialize(): string
|
||||||
|
{
|
||||||
|
// Declaring the buffer of generated row
|
||||||
|
$serialized = '';
|
||||||
|
|
||||||
|
foreach ($this->parameters as $value) {
|
||||||
|
// Iterating over parameters
|
||||||
|
|
||||||
|
// Generating row by RFC 4180
|
||||||
|
$serialized .= ',' . preg_replace('/(?<=[^^])"(?=[^$])/', '""', preg_replace('/(?<=[^^]),(?=[^$])/', '\,', $value ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trimming excess first comma in the buffer of generated row
|
||||||
|
$serialized = mb_substr($serialized, 1, mb_strlen($serialized));
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize
|
||||||
|
*
|
||||||
|
* Convert values from the database and write to the record instance
|
||||||
|
*
|
||||||
|
* @param string $row Row from the database
|
||||||
|
*
|
||||||
|
* @return array Deserialized record
|
||||||
|
*/
|
||||||
|
public function deserialize(string $row): array
|
||||||
|
{
|
||||||
|
// Separating row by commas
|
||||||
|
preg_match_all('/(.*)(?>(?<!\\\),|$)/Uu', $row, $matches);
|
||||||
|
|
||||||
|
// Deleting the last matched element (i could not come up with a better regular expression)
|
||||||
|
array_pop($matches[1]);
|
||||||
|
|
||||||
|
// Generating parameters by RFC 4180
|
||||||
|
foreach ($matches[1] as &$match) {
|
||||||
|
// Iterating over values
|
||||||
|
|
||||||
|
// Declaring buffer of the implementated value
|
||||||
|
$buffer = null;
|
||||||
|
|
||||||
|
if ($match === 'null' || empty($match)) {
|
||||||
|
// Null
|
||||||
|
|
||||||
|
// Writing to the matches buffer
|
||||||
|
$match = null;
|
||||||
|
} else if (($buffer = filter_var($match, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE)) !== null) {
|
||||||
|
// Boolean
|
||||||
|
|
||||||
|
// Writing to the matches buffer
|
||||||
|
$match = $buffer;
|
||||||
|
} else if (($buffer = filter_var($match, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE)) !== null) {
|
||||||
|
// Integer
|
||||||
|
|
||||||
|
// Writing to the matches buffer
|
||||||
|
$match = $buffer;
|
||||||
|
} else if (($buffer = filter_var($match, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE)) !== null) {
|
||||||
|
// Float
|
||||||
|
|
||||||
|
// Writing to the matches buffer
|
||||||
|
$match = $buffer;
|
||||||
|
} else {
|
||||||
|
// String
|
||||||
|
|
||||||
|
// Deinitializing unnecessary variables
|
||||||
|
unset($buffer);
|
||||||
|
|
||||||
|
// Removing quotes from both sides (trim() is not suitable here)
|
||||||
|
$unquoted = preg_replace('/"(.*)"/u', '$1', $match);
|
||||||
|
|
||||||
|
// Unescaping commas
|
||||||
|
$commaded = preg_replace('/\\\,/', ',', $unquoted);
|
||||||
|
|
||||||
|
// Unescaping quotes (by RFC 4180)
|
||||||
|
$quoted = preg_replace('/""/', '"', $commaded);
|
||||||
|
|
||||||
|
// Removing line break characters
|
||||||
|
/* $unbreaked = preg_replace('/[\n\r]/', '', $quoted); */
|
||||||
|
|
||||||
|
// Removing spaces from both sides
|
||||||
|
/* $unspaced = trim($unbreaked); */
|
||||||
|
|
||||||
|
// Writing to the matches buffer
|
||||||
|
$match = $quoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinitializing unnecessary variables
|
||||||
|
unset($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write
|
||||||
|
*
|
||||||
|
* Write the parameter
|
||||||
|
*
|
||||||
|
* @param string $name Name of the parameter
|
||||||
|
* @param mixed $value Content of the parameter
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __set(string $name, mixed $value = null): void
|
||||||
|
{
|
||||||
|
// Writing the parameter and exit (success)
|
||||||
|
$this->parameters[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read
|
||||||
|
*
|
||||||
|
* Read the parameter
|
||||||
|
*
|
||||||
|
* @param string $name Name of the parameter
|
||||||
|
*
|
||||||
|
* @return mixed Content of the parameter
|
||||||
|
*/
|
||||||
|
public function __get(string $name): mixed
|
||||||
|
{
|
||||||
|
// Reading the parameter and exit (success)
|
||||||
|
return $this->parameters[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete
|
||||||
|
*
|
||||||
|
* Delete the parameter
|
||||||
|
*
|
||||||
|
* @param string $name Name of the parameter
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __unset(string $name): void
|
||||||
|
{
|
||||||
|
// Deleting the parameter and exit (success)
|
||||||
|
unset($this->parameter[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for initializing
|
||||||
|
*
|
||||||
|
* Check for initializing the parameter
|
||||||
|
*
|
||||||
|
* @param string $name Name of the parameter
|
||||||
|
*
|
||||||
|
* @return bool Is the parameter initialized?
|
||||||
|
*/
|
||||||
|
public function __isset(string $name): bool
|
||||||
|
{
|
||||||
|
// Checking for initializing the parameter and exit (success)
|
||||||
|
return isset($this->parameters[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,134 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace mirzaev\csv\traits;
|
|
||||||
|
|
||||||
// Files of the project
|
|
||||||
use mirzaev\csv\interfaces\csv as csv_interface;
|
|
||||||
|
|
||||||
// Built-in libraries
|
|
||||||
use exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSV
|
|
||||||
*
|
|
||||||
* Comma-Separated Values by RFC 4180
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc4180
|
|
||||||
*
|
|
||||||
* @uses csv_interface
|
|
||||||
* @package mirzaev\csv\traits
|
|
||||||
*
|
|
||||||
* @method static array|null read(int $rows, array &$errors) Read from the start of the database file
|
|
||||||
* @method static string|false serialize(array $parameters, bool $created) Preparing data for writing to the database
|
|
||||||
* @method static array|false deserialize(string $row) Preparing data from the database to processing
|
|
||||||
*
|
|
||||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
|
||||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
||||||
*/
|
|
||||||
trait csv
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Read
|
|
||||||
*
|
|
||||||
* Read from the start of the database file
|
|
||||||
*
|
|
||||||
* @param int $rows Amount of rows for reading
|
|
||||||
* @param array &$errors Buffer of errors
|
|
||||||
*
|
|
||||||
* @return array|null Readed records
|
|
||||||
*/
|
|
||||||
public static function read(int $rows = 0, &$errors = []): ?array
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// Initializing the buffer of readed records
|
|
||||||
$records = [];
|
|
||||||
|
|
||||||
// Opening the file with views records
|
|
||||||
$file = fopen(static::FILE, 'c+');
|
|
||||||
|
|
||||||
while (--$rows >= 0 && ($row = fgets($file, 4096)) !== false) {
|
|
||||||
// Iterating over rows (records)
|
|
||||||
|
|
||||||
// Deserealizing record
|
|
||||||
$deserialized = static::deserialize($row);
|
|
||||||
|
|
||||||
if ($deserialized) {
|
|
||||||
// Deserialized record
|
|
||||||
|
|
||||||
// Writing to the buffer of readed records
|
|
||||||
$records[] = $deserialized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closing file with views records
|
|
||||||
fclose($file);
|
|
||||||
|
|
||||||
// Exit (success)
|
|
||||||
return $records;
|
|
||||||
} catch (exception $e) {
|
|
||||||
// Write to the buffer of errors
|
|
||||||
$errors[] = [
|
|
||||||
'text' => $e->getMessage(),
|
|
||||||
'file' => $e->getFile(),
|
|
||||||
'line' => $e->getLine(),
|
|
||||||
'stack' => $e->getTrace()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit (fail)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize
|
|
||||||
*
|
|
||||||
* Preparing data for writing to the database
|
|
||||||
*
|
|
||||||
* @param array $parameters Values for serializing
|
|
||||||
* @param bool $created Add date of creating at the end?
|
|
||||||
*
|
|
||||||
* @return string|false Serialized data
|
|
||||||
*/
|
|
||||||
public static function serialize(array $parameters, bool $created = false): string|false
|
|
||||||
{
|
|
||||||
// Declaring the buffer of serialized values
|
|
||||||
$serialized = '';
|
|
||||||
|
|
||||||
// Sanitizing values
|
|
||||||
foreach ($parameters as $value) $serialized .= ',' . preg_replace('/(?<=[^^])"(?=[^$])/', '""', preg_replace('/(?<=[^^]),(?=[^$])/', '\,', $value ?? ''));
|
|
||||||
|
|
||||||
// Writing date of creating to the buffer of serialized values
|
|
||||||
if ($created) $serialized .= ',' . time();
|
|
||||||
|
|
||||||
// Trimming excess first comma in the buffer of serialized values
|
|
||||||
$serialized = mb_substr($serialized, 1, mb_strlen($serialized));
|
|
||||||
|
|
||||||
// Exit (success/fail)
|
|
||||||
return empty($serialized) ? false : $serialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserialize
|
|
||||||
*
|
|
||||||
* Preparing data from the database to processing
|
|
||||||
*
|
|
||||||
* @param string $row Record for deserializing
|
|
||||||
*
|
|
||||||
* @return array|false Deserialized data
|
|
||||||
*/
|
|
||||||
public static function deserialize(string $row): array|false
|
|
||||||
{
|
|
||||||
// Separating row by commas
|
|
||||||
preg_match_all('/(?:^|,)(?=[^"]|(")?)"?((?(1)[^"]*|[^,"]*))"?(?=,|$)/', $row, $matches);
|
|
||||||
|
|
||||||
// Converting double quotes to single quotes
|
|
||||||
foreach ($matches[2] as &$match)
|
|
||||||
if (empty($match = preg_replace('/[\n\r]/', '', preg_replace('/""/', '"', preg_replace('/\\\,/', ',', trim((string) $match, '"'))))))
|
|
||||||
$match = null;
|
|
||||||
|
|
||||||
// Exit (success/fail)
|
|
||||||
return empty($matches[2]) ? false : $matches[2];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace mirzaev\site\repression\models\traits;
|
namespace mirzaev\csv\traits;
|
||||||
|
|
||||||
// Built-in libraries
|
// Built-in libraries
|
||||||
use Exception as exception,
|
use Exception as exception,
|
||||||
|
@ -26,15 +26,15 @@ trait file
|
||||||
* Read the file
|
* Read the file
|
||||||
*
|
*
|
||||||
* @param resource $file Pointer to the file (fopen())
|
* @param resource $file Pointer to the file (fopen())
|
||||||
* @param int $offset Offset of rows for start reading
|
|
||||||
* @param int $rows Amount of rows for reading
|
* @param int $rows Amount of rows for reading
|
||||||
|
* @param int $offset Offset of rows for start reading
|
||||||
* @param int $position Initial cursor position on a row
|
* @param int $position Initial cursor position on a row
|
||||||
* @param int $step Reading step
|
* @param int $step Reading step
|
||||||
* @param array &$errors Buffer of errors
|
* @param array &$errors Buffer of errors
|
||||||
*
|
*
|
||||||
* @return generator|null|false
|
* @return generator|null|false
|
||||||
*/
|
*/
|
||||||
private static function read($file, int $offset = 0, int $rows = 10, int $position = 0, int $step = 1, array &$errors = []): generator|null|false
|
private static function read($file, int $rows = 10, int $offset = 0, int $position = 0, int $step = 1, array &$errors = []): generator|null|false
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
while ($offset-- > 0) {
|
while ($offset-- > 0) {
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use mirzaev\csv\database,
|
||||||
|
mirzaev\csv\record;
|
||||||
|
|
||||||
|
// Importing files of thr project and dependencies
|
||||||
|
require(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php');
|
||||||
|
|
||||||
|
// Initializing the counter of actions
|
||||||
|
$action = 0;
|
||||||
|
|
||||||
|
// Initializing the test database
|
||||||
|
$database = new database([
|
||||||
|
'empty_value_at_the_beginning',
|
||||||
|
'name',
|
||||||
|
'second_name',
|
||||||
|
'age_between_quotes',
|
||||||
|
'true',
|
||||||
|
'empty_value',
|
||||||
|
'empty_value_between_quotes',
|
||||||
|
'number',
|
||||||
|
'null',
|
||||||
|
'null_between_quotes',
|
||||||
|
'float_between_quotes',
|
||||||
|
'float',
|
||||||
|
'float_long',
|
||||||
|
'float_with_two_dots',
|
||||||
|
'string_with_doubled_quotes',
|
||||||
|
'string_with_doubled_quotes_twice',
|
||||||
|
'string_with_space_at_the_beginning',
|
||||||
|
'string_with_escaped_comma',
|
||||||
|
'string_with_unicode_symbols'
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo '[' . ++$action . "] Created the database instance with columns\n";
|
||||||
|
|
||||||
|
// Initializing the test record with the test row
|
||||||
|
$record = new record(',"Arsen","Mirzaev","23",true,,"",100,null,"null","102.1",300.34,1001.23145,5000.400.400,"test ""value""","another"" test "" value with ""two double quotes pairs"" yeah"," starts with space","has\, an escaped comma inside","unicode символы"');
|
||||||
|
|
||||||
|
echo '[' . ++$action . "] Created the record with the test row\n";
|
||||||
|
|
||||||
|
// Initializing the counter of tests
|
||||||
|
$test = 0;
|
||||||
|
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[0] === null ? 'SUCCESS' : 'FAIL') . "] The empty value at the beginning is need to be null\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[1] === 'Arsen' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"Arsen\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[2] === 'Mirzaev' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"Mirzaev\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[3] === '23' ? 'SUCCESS' : 'FAIL') . "] The age between quotes value is need to be \"23\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[4] === true ? 'SUCCESS' : 'FAIL') . "] The value is need to be true\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[5] === null ? 'SUCCESS' : 'FAIL') . "] The empty value is need to be null\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[6] === '' ? 'SUCCESS' : 'FAIL') . "] The empty value between quotes is need to be \"\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[7] === 100 ? 'SUCCESS' : 'FAIL') . "] The value is need to be 100 integer\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[8] === null ? 'SUCCESS' : 'FAIL') . "] The null value is need to be null\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[9] === 'null' ? 'SUCCESS' : 'FAIL') . "] The null value between quotes is need to be \"null\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[10] === '102.1' ? 'SUCCESS' : 'FAIL') . "] The float value between quotes is need to be \"102.1\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[11] === 300.34 ? 'SUCCESS' : 'FAIL') . "] The float value is need to be 300.34 float \n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[12] === 1001.23145 ? 'SUCCESS' : 'FAIL') . "] The long float value is need to be 1001.23145 float\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[13] === '5000.400.400' ? 'SUCCESS' : 'FAIL') . "] The float value with two dots is need to be \"5000.400.400\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[14] === 'test "value"' ? 'SUCCESS' : 'FAIL') . "] The value with quotes is need to be \"test \"value\"\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[15] === 'another" test " value with "two double quotes pairs" yeah' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"another\" test \" value with \"two double quotes pairs\" yeah\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[16] === ' starts with space' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \" starts with space\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[17] === 'has, an escaped comma inside' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"has, an escaped comma inside\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->parameters[18] === 'unicode символы' ? 'SUCCESS' : 'FAIL') . "] The valueis need to be \"unicode символы\" string\n";
|
||||||
|
|
||||||
|
// Combining database columns with record parameters
|
||||||
|
$record->columns($database);
|
||||||
|
|
||||||
|
echo '[' . ++$action . "] Combined database columns with record parameters\n";
|
||||||
|
|
||||||
|
|
||||||
|
// Reinitializing the counter of tests
|
||||||
|
$test = 0;
|
||||||
|
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->empty_value_at_the_beginning === null ? 'SUCCESS' : 'FAIL') . "] The empty value at the beginning is need to be null\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->name === 'Arsen' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"Arsen\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->second_name === 'Mirzaev' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"Mirzaev\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->age_between_quotes === '23' ? 'SUCCESS' : 'FAIL') . "] The age between quotes value is need to be \"23\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->true === true ? 'SUCCESS' : 'FAIL') . "] The value is need to be true\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->empty_value === null ? 'SUCCESS' : 'FAIL') . "] The empty value is need to be null\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->empty_value_between_quotes === '' ? 'SUCCESS' : 'FAIL') . "] The empty value between quotes is need to be \"\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->number === 100 ? 'SUCCESS' : 'FAIL') . "] The value is need to be 100 integer\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->null === null ? 'SUCCESS' : 'FAIL') . "] The null value is need to be null\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->null_between_quotes === 'null' ? 'SUCCESS' : 'FAIL') . "] The null value between quotes is need to be \"null\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->float_between_quotes === '102.1' ? 'SUCCESS' : 'FAIL') . "] The float value between quotes is need to be \"102.1\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->float === 300.34 ? 'SUCCESS' : 'FAIL') . "] The float value is need to be 300.34 float \n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->float_long === 1001.23145 ? 'SUCCESS' : 'FAIL') . "] The long float value is need to be 1001.23145 float\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->float_with_two_dots === '5000.400.400' ? 'SUCCESS' : 'FAIL') . "] The float value with two dots is need to be \"5000.400.400\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->string_with_doubled_quotes === 'test "value"' ? 'SUCCESS' : 'FAIL') . "] The value with quotes is need to be \"test \"value\"\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->string_with_doubled_quotes_twice === 'another" test " value with "two double quotes pairs" yeah' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"another\" test \" value with \"two double quotes pairs\" yeah\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->string_with_space_at_the_beginning === ' starts with space' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \" starts with space\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->string_with_escaped_comma === 'has, an escaped comma inside' ? 'SUCCESS' : 'FAIL') . "] The value is need to be \"has, an escaped comma inside\" string\n";
|
||||||
|
echo '[' . ++$action . '][' . ++$test . '][' . ($record->string_with_unicode_symbols === 'unicode символы' ? 'SUCCESS' : 'FAIL') . "] The valueis need to be \"unicode символы\" string\n";
|
Loading…
Reference in New Issue