Плоские файловые базы данных

Каковы лучшие практики создания структур баз данных с плоскими файлами в 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));
}

но, опять же, эта реализация будет зависеть от приложения и характера базы данных, которая вам нужна.

ИМХО, у вас есть два ... эээ, три варианта, если вы хотите избежать домашнего пивоварения:

  1. SQLite

Если вы знакомы с PDO, вы можете установить драйвер PDO, поддерживающий SQLite. Никогда не использовал его, но я много использовал PDO с MySQL. Я собираюсь попробовать это в текущем проекте.

  1. XML

Проделывали это много раз для относительно небольших объемов данных. XMLReader - это легкий класс в стиле курсора с упреждающим чтением. SimpleXML упрощает чтение XML-документа в объект, к которому вы можете получить доступ, как и к любому другому экземпляру класса.

  1. JSON (обновление)

Хороший вариант для небольших объемов данных, просто чтение / запись файла и json_decode / json_encode. Не уверен, что PHP предлагает структуру для навигации по дереву JSON, не загружая все это в память.

Я написал две простые функции, предназначенные для хранения данных в файле. Вы сами можете судить, пригодится ли это в данном случае. Дело в том, чтобы сохранить переменную 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 строк), но становятся огромными потребителями памяти для больших баз данных.

Это вдохновляет как практическое решение:
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

Один из фреймворков, который я рассматриваю, - это платформа для ведения блогов. Поскольку практически любое возможное представление данных, которое вы хотели бы, было бы отсортировано по дате, я думал об этой структуре:

Один каталог на узел содержимого:

./content/YYYYMMDDHHMMSS/

Поддиректории каждого узла, включая

/tags  
/authors  
/comments  

А также простые текстовые файлы в каталоге узлов для содержимого до и после рендеринга и т.п.

Это позволило бы простому glob()вызову PHP (и, вероятно, обращению массива результатов) запрашивать практически все в структуре содержимого:

glob("content/*/tags/funny");  

Вернет пути, включая все статьи с меткой "смешно".

Вы можете рассмотреть SQLite . Это почти так же просто, как плоские файлы, но вы получаете механизм SQL для запросов. Он также хорошо работает с PHP .

Если вы собираетесь использовать плоский файл для хранения данных, используйте XML для структурирования данных. PHP имеет встроенный анализатор XML .

Если вам нужен удобочитаемый результат, вы также можете использовать этот тип файла:

ofaurax|27|male|something|
another|24|unknown||
...

Таким образом, у вас есть только один файл, вы можете легко его отлаживать (и вручную исправлять), вы можете добавлять поля позже (в конце каждой строки), а код PHP прост (для каждой строки, разделенный в соответствии с |).

Однако недостатком является то, что вы должны анализировать весь файл для поиска чего-либо (если у вас есть миллионы записей, это не нормально), и вы должны обрабатывать разделитель в данных (например, если ник WaR | ordz).

На мой взгляд, использование «базы данных плоских файлов» в том смысле, в котором вы имеете в виду (и ответ, который вы приняли), не обязательно является лучшим способом решения проблем. Во-первых, использование serialize() и unserialize() может вызвать БОЛЬШИЕ головные боли, если кто-то войдет и отредактирует файл (фактически, они могут помещать произвольный код в вашу «базу данных» для выполнения каждый раз).

Лично я бы сказал - почему бы не заглянуть в будущее? Было так много раз, что у меня были проблемы, потому что я создавал свои собственные "проприетарные" файлы, и проект взорвался до такой степени, что ему нужна база данных, и я думаю: "Вы знаете, я бы хотел Я написал это для базы данных для начала "- потому что рефакторинг кода требует слишком много времени и усилий.

Из этого я узнал, что будущая проверка моего приложения, чтобы, когда оно становится больше, мне не приходилось тратить дни на рефакторинг, - это путь вперед. Как мне это сделать?

SQLite. Он работает как база данных, использует SQL и довольно легко перейти на MySQL (особенно, если вы используете абстрактные классы для управления базой данных, как это делаю я!)

Фактически, особенно с методом «принятого ответа», он может резко сократить использование памяти вашим приложением (вам не нужно загружать все «ЗАПИСИ» в PHP)

Вот код, который мы используем для Лилины:

<?php
/**
 * Handler for persistent data files
 *
 * @author Ryan McCue <[email protected]>
 * @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);
    }
}

?>

Он хранит каждую запись в виде отдельного файла, который, как мы обнаружили, достаточно эффективен для использования (ненужные данные не загружаются и их быстрее сохранять).