Базы плоских файлов
Каковы лучшие практики создания структур баз данных с плоскими файлами в PHP?
Многие из более зрелых платформ плоских файлов PHP, которые я вижу, пытаются реализовать синтаксис SQL-подобных запросов, который в большинстве случаев является чрезмерным для моих целей (я бы просто использовал базу данных на этом этапе).
Есть ли какие-нибудь изящные приемы, чтобы получить хорошую производительность и функциональные возможности с небольшим объемом кода?
11 ответов
Хорошо, какова природа плоских баз данных. Они большие или маленькие. Это простые массивы с массивами в них? если что-то простое, скажем, пользовательские профили построены так:
$user = array("name" => "dubayou",
"age" => 20,
"websites" => array("dubayou.com","willwharton.com","codecream.com"),
"and_one" => "more");
и сохранить или обновить запись БД для этого пользователя.
$dir = "../userdata/"; //make sure to put it bellow what the server can reach.
file_put_contents($dir.$user['name'],serialize($user));
и загрузить запись для пользователя
function &get_user($name){
return unserialize(file_get_contents("../userdata/".$name));
}
но опять-таки эта реализация будет зависеть от приложения и характера базы данных, которая вам нужна.
Вы могли бы рассмотреть SQLite. Это почти так же просто, как простые файлы, но вы получаете SQL-движок для запросов. Он также хорошо работает с PHP.
На мой взгляд, использование "базы данных плоских файлов" в том смысле, в каком вы имеете в виду (и ответ, который вы приняли), не обязательно является лучшим способом решения проблем. Прежде всего, используя serialize()
а также unserialize()
может вызвать ОСНОВНЫЕ головные боли, если кто-то войдет и отредактирует файл (фактически, он может поместить произвольный код в вашу "базу данных" для запуска каждый раз.)
Лично я бы сказал - почему бы не заглянуть в будущее? Было так много раз, что у меня были проблемы, потому что я создавал свои собственные "проприетарные" файлы, и проект взорвался до такой степени, что ему нужна база данных, и я думаю: "Вы знаете, я бы хотел Я написал это для базы данных, чтобы начать с " - потому что рефакторинг кода занимает слишком много времени и усилий.
Из этого я узнал, что будущее приложение защищает мое приложение, поэтому, когда оно становится больше, мне не нужно тратить дни на рефакторинг, это путь для продвижения вперед. Как мне это сделать?
SQLite. Он работает как база данных, использует SQL и довольно легко переключается на mySQL (особенно если вы используете абстрактные классы для манипулирования базой данных, как я!)
Фактически, особенно с помощью метода "принятого ответа", он может резко сократить использование памяти вашего приложения (вам не нужно загружать все "ЗАПИСИ" в PHP)
Это правда. serialize()
может быть довольно полезным для этого.
Я думаю, что хитрость в создании жизнеспособной системы заключается в том, чтобы найти способ индексировать узлы данных, не убивая себя сложностью.
Одним из основ, который я рассматриваю, будет платформа для блогов. Так как практически любое возможное представление данных, которые вы хотели бы отсортировать по дате, я думал об этой структуре:
Один каталог на узел содержимого:
./content/YYYYMMDDHHMMSS/
Подкаталоги каждого узла, включая
/tags
/authors
/comments
А также простые текстовые файлы в каталоге узла для содержимого до и после рендеринга и тому подобное.
Это позволило бы простой PHP glob()
вызов (и, возможно, обращение к результирующему массиву) для запроса практически чего-либо в структуре содержимого:
glob("content/*/tags/funny");
Возвращает пути, включая все статьи, помеченные как "смешные".
Вот код, который мы используем для Lilina:
<?php
/**
* Handler for persistent data files
*
* @author Ryan McCue <cubegames@gmail.com>
* @package Lilina
* @version 1.0
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/
/**
* Handler for persistent data files
*
* @package Lilina
*/
class DataHandler {
/**
* Directory to store data.
*
* @since 1.0
*
* @var string
*/
protected $directory;
/**
* Constructor, duh.
*
* @since 1.0
* @uses $directory Holds the data directory, which the constructor sets.
*
* @param string $directory
*/
public function __construct($directory = null) {
if ($directory === null)
$directory = get_data_dir();
if (substr($directory, -1) != '/')
$directory .= '/';
$this->directory = (string) $directory;
}
/**
* Prepares filename and content for saving
*
* @since 1.0
* @uses $directory
* @uses put()
*
* @param string $filename Filename to save to
* @param string $content Content to save to cache
*/
public function save($filename, $content) {
$file = $this->directory . $filename;
if(!$this->put($file, $content)) {
trigger_error(get_class($this) . " error: Couldn't write to $file", E_USER_WARNING);
return false;
}
return true;
}
/**
* Saves data to file
*
* @since 1.0
* @uses $directory
*
* @param string $file Filename to save to
* @param string $data Data to save into $file
*/
protected function put($file, $data, $mode = false) {
if(file_exists($file) && file_get_contents($file) === $data) {
touch($file);
return true;
}
if(!$fp = @fopen($file, 'wb')) {
return false;
}
fwrite($fp, $data);
fclose($fp);
$this->chmod($file, $mode);
return true;
}
/**
* Change the file permissions
*
* @since 1.0
*
* @param string $file Absolute path to file
* @param integer $mode Octal mode
*/
protected function chmod($file, $mode = false){
if(!$mode)
$mode = 0644;
return @chmod($file, $mode);
}
/**
* Returns the content of the cached file if it is still valid
*
* @since 1.0
* @uses $directory
* @uses check() Check if cache file is still valid
*
* @param string $id Unique ID for content type, used to distinguish between different caches
* @return null|string Content of the cached file if valid, otherwise null
*/
public function load($filename) {
return $this->get($this->directory . $filename);
}
/**
* Returns the content of the file
*
* @since 1.0
* @uses $directory
* @uses check() Check if file is valid
*
* @param string $id Filename to load data from
* @return bool|string Content of the file if valid, otherwise null
*/
protected function get($filename) {
if(!$this->check($filename))
return null;
return file_get_contents($filename);
}
/**
* Check a file for validity
*
* Basically just a fancy alias for file_exists(), made primarily to be
* overriden.
*
* @since 1.0
* @uses $directory
*
* @param string $id Unique ID for content type, used to distinguish between different caches
* @return bool False if the cache doesn't exist or is invalid, otherwise true
*/
protected function check($filename){
return file_exists($filename);
}
/**
* Delete a file
*
* @param string $filename Unique ID
*/
public function delete($filename) {
return unlink($this->directory . $filename);
}
}
?>
Он сохраняет каждую запись в виде отдельного файла, который, по нашему мнению, достаточно эффективен для использования (ненужные данные не загружаются, и его быстрее сохранить).
ИМХО, у вас есть два варианта, если вы хотите избежать домашнего приготовления чего-либо:
SQLite
Если вы знакомы с PDO, вы можете установить драйвер PDO, который поддерживает SQLite. Никогда не использовал его, но я использовал PDO тонну с MySQL. Я собираюсь дать этому шанс на текущий проект.
XML
Сделано это много раз для относительно небольших объемов данных. XMLReader - это легкий класс для чтения и пересылки в стиле курсора. SimpleXML упрощает чтение XML-документа в объект, к которому вы можете получить доступ, как и к любому другому экземпляру класса.
Если вы собираетесь использовать плоский файл для сохранения данных, используйте XML для структурирования данных. PHP имеет встроенный синтаксический анализатор XML.
Если вам нужен читабельный результат, вы также можете использовать этот тип файла:
ofaurax|27|male|something|
another|24|unknown||
...
Таким образом, у вас есть только один файл, вы можете легко отладить его (и исправить вручную), вы можете добавить поля позже (в конце каждой строки), а PHP-код прост (для каждой строки, разделить в соответствии с |).
Однако недостатком является то, что вы должны анализировать весь файл для поиска чего-либо (если у вас есть миллионы записей, это не хорошо), и вы должны обрабатывать разделитель в данных (например, если ник - это WaR|ordz).
Это вдохновляет как практическое решение:
https://github.com/mhgolkar/FlatFire
Он использует несколько стратегий для обработки данных...
[Скопировано из файла Readme]
Свободный или структурированный или смешанный
- STRUCTURED
Regular (table, row, column) format.
[DATABASE]
/ \
TX TableY
\_____________________________
|ROW_0 Colum_0 Colum_1 Colum_2|
|ROW_1 Colum_0 Colum_1 Colum_2|
|_____________________________|
- FREE
More creative data storing. You can store data in any structure you want for each (free) element, its similar to storing an array with a unique "Id".
[DATABASE]
/ \
EX ElementY (ID)
\________________
|Field_0 Value_0 |
|Field_1 Value_1 |
|Field_2 Value_2 |
|________________|
recall [ID]: get_free("ElementY") --> array([Field_0]=>Value_0,[Field_1]=>Value_1...
- MIXD (Mixed)
Mixed databases can store both free elements and tables.If you add a table to a free db or a free element to a structured db, flat fire will automatically convert FREE or SRCT to MIXD database.
[DATABASE]
/ \
EX TY
Я написал две простые функции, предназначенные для хранения данных в файле. Вы сами можете судить, полезно ли это в этом случае. Смысл в том, чтобы сохранить переменную php (если это массив или строка или объект) в файл.
<?php
function varname(&$var) {
$oldvalue=$var;
$var='AAAAB3NzaC1yc2EAAAABIwAAAQEAqytmUAQKMOj24lAjqKJC2Gyqhbhb+DmB9eDDb8+QcFI+QOySUpYDn884rgKB6EAtoFyOZVMA6HlNj0VxMKAGE+sLTJ40rLTcieGRCeHJ/TI37e66OrjxgB+7tngKdvoG5EF9hnoGc4eTMpVUDdpAK3ykqR1FIclgk0whV7cEn/6K4697zgwwb5R2yva/zuTX+xKRqcZvyaF3Ur0Q8T+gvrAX8ktmpE18MjnA5JuGuZFZGFzQbvzCVdN52nu8i003GEFmzp0Ny57pWClKkAy3Q5P5AR2BCUwk8V0iEX3iu7J+b9pv4LRZBQkDujaAtSiAaeG2cjfzL9xIgWPf+J05IQ==';
foreach($GLOBALS as $var_name => $value) {
if ($value === 'AAAAB3NzaC1yc2EAAAABIwAAAQEAqytmUAQKMOj24lAjqKJC2Gyqhbhb+DmB9eDDb8+QcFI+QOySUpYDn884rgKB6EAtoFyOZVMA6HlNj0VxMKAGE+sLTJ40rLTcieGRCeHJ/TI37e66OrjxgB+7tngKdvoG5EF9hnoGc4eTMpVUDdpAK3ykqR1FIclgk0whV7cEn/6K4697zgwwb5R2yva/zuTX+xKRqcZvyaF3Ur0Q8T+gvrAX8ktmpE18MjnA5JuGuZFZGFzQbvzCVdN52nu8i003GEFmzp0Ny57pWClKkAy3Q5P5AR2BCUwk8V0iEX3iu7J+b9pv4LRZBQkDujaAtSiAaeG2cjfzL9xIgWPf+J05IQ==')
{
$var=$oldvalue;
return $var_name;
}
}
$var=$oldvalue;
return false;
}
function putphp(&$var, $file=false)
{
$varname=varname($var);
if(!$file)
{
$file=$varname.'.php';
}
$pathinfo=pathinfo($file);
if(file_exists($file))
{
if(is_dir($file))
{
$file=$pathinfo['dirname'].'/'.$pathinfo['basename'].'/'.$varname.'.php';
}
}
file_put_contents($file,'<?php'."\n\$".$varname.'='.var_export($var, true).";\n");
return true;
}
Просто указываю на потенциальную проблему с базой данных плоских файлов в системе такого типа:
data|some text|more data
row 2 data|bla hbalh|more data
...так далее
Проблема в том, что данные ячейки содержат "|" или "\n", тогда данные будут потеряны. Иногда было бы проще разделить на комбинации букв, которые большинство людей не будет использовать.
Например:
Колонка сплиттер: #$% (Shift+345)
Разделитель строк: ^&* (Shift+678)
Текстовый файл: test data#$%blah blah#$%^&*new row#$%new row data 2
Тогда используйте: explode("#$%", $data); use foreach, the explode again to separate columns
Или что-нибудь в этом роде. Кроме того, я мог бы добавить, что базы данных с плоскими файлами хороши для систем с небольшими объемами данных (т. Е. Менее 20 строк), но становятся огромными бременами памяти для больших баз данных.