Bazy danych z płaskim plikiem [zamknięte]

120

Jakie są najlepsze praktyki dotyczące tworzenia płaskich struktur baz danych plików w PHP?

Wiele bardziej dojrzałych frameworków PHP z płaskim plikiem, które próbuję zaimplementować składnię zapytań podobną do SQL, która w większości przypadków jest przesadzona. (W tym momencie użyłbym tylko bazy danych).

Czy są jakieś eleganckie sztuczki, które pozwalają uzyskać dobrą wydajność i funkcje przy niewielkim narzucie kodu?

saint_groceon
źródło
1
Chciałbym dodać, że jest tutaj pakiet dla bazy danych płaskich plików github.com/tmarois/Filebase Wiem, że to stare pytanie, ale ten pakiet jest najnowszą kompilacją i utrzymywaniem, a także zawiera wiele funkcji, których większość zaniedbuje się uwzględnić .
tmarois
Opracowuję CMS i korzystam z bazy danych tekstowych w postaci płaskich plików tekstowych. Wykonanie i ponowne złamanie zajęło wiele godzin, ale działa idealnie. Zapytania będą wykonywane znacznie szybciej przy w pełni zindeksowanej i zoptymalizowanej bazie danych. Jednak unikam zapytań, przechowując metadane i starannie organizując i strukturalnie. Kiedy potrzebuję danych, otrzymuję je bez for loop(chyba że używam wszystkich danych w folderze), dlatego działa o wiele szybciej niż baza danych. Poszedłbym szczegółowo i udzieliłbym bardzo dobrej odpowiedzi, ale niestety to pytanie jest zamknięte.
Dan Bray

Odpowiedzi:

75

Cóż, jaka jest natura płaskich baz danych. Czy są duże czy małe. Czy są to proste tablice zawierające tablice? jeśli jest to coś prostego, powiedzmy profile użytkowników zbudowane jako takie:

$user = array("name" => "dubayou", 
              "age" => 20,
              "websites" => array("dubayou.com","willwharton.com","codecream.com"),
              "and_one" => "more");

i zapisać lub zaktualizować rekord db dla tego użytkownika.

$dir = "../userdata/";  //make sure to put it bellow what the server can reach.
file_put_contents($dir.$user['name'],serialize($user));

i załadować rekord dla użytkownika

function &get_user($name){
    return unserialize(file_get_contents("../userdata/".$name));
}

ale znowu ta implementacja będzie się różnić w zależności od zastosowania i charakteru potrzebnej bazy danych.

w-ll
źródło
48

Możesz rozważyć SQLite . To prawie tak proste, jak pliki płaskie, ale otrzymujesz silnik SQL do wykonywania zapytań. To działa dobrze z PHP zbyt.

yukondude
źródło
6
SQLite został domyślnie wbudowany w 5.0+, ale zdyskontowany (!) Od PHP 5.4+ na !!! Kiedy piszę to w lipcu 2012 roku, SQLite nie będzie już domyślnie działać na aktualnych systemach. Oficjalne oświadczenie tutaj
Sliq
Instalacja sterownika SQLite PDO jest dość trywialna, jeśli masz dostęp do serwera. W systemie Ubuntu / Debian z uruchomionym Apache2 po prostu wykonaj apt-get install php5-sqlite service apache2 restart
siliconrockstar
4
W odpowiedzi na komentarz @Sliq, stwierdzenie, że „SQLite został… wycofany” jest w pewnym sensie prawdą: rozszerzenie o nazwie „SQLite” zostało wycofane, a „SQLite3” jest teraz domyślnie włączone. php.net/manual/en/sqlite.installation.php "Od PHP 5.0 to rozszerzenie zostało dołączone do PHP. Począwszy od PHP 5.4, to rozszerzenie jest dostępne tylko przez PECL." php.net/manual/en/sqlite3.installation.php "Rozszerzenie SQLite3 jest domyślnie włączone od PHP 5.3.0." „To rozszerzenie było przez krótki czas rozszerzeniem PECL, ale ta wersja jest zalecana tylko do użytku eksperymentalnego”.
Paul van Leeuwen
Nie odpowiedziałeś na pytanie
JG Estiot
20

Moim zdaniem używanie „Flat File Database” w sensie, który masz na myśli (i zaakceptowanej przez Ciebie odpowiedzi) nie jest koniecznie najlepszym sposobem na załatwienie sprawy. Po pierwsze, używanie serialize()i unserialize()może powodować DUŻE bóle głowy, jeśli ktoś wejdzie i edytuje plik (w rzeczywistości może umieścić kod arbritrary w twojej "bazie danych", aby był uruchamiany za każdym razem).

Osobiście powiedziałbym - dlaczego nie spojrzeć w przyszłość? Tak wiele razy miałem problemy, ponieważ tworzyłem własne „zastrzeżone” pliki, a projekt eksplodował do punktu, w którym potrzebna jest baza danych, i myślę „wiesz, chciałbym Napisałem to dla bazy danych, aby zacząć od „- ponieważ refaktoryzacja kodu wymaga zbyt wiele czasu i wysiłku.

Z tego dowiedziałem się, że zabezpieczanie mojej aplikacji w przyszłości, tak, że gdy się rozrośnie, nie będę musiał iść i spędzać dni na refaktoryzacji, jest dobrym rozwiązaniem. Jak mam to zrobic?

SQLite. Działa jako baza danych, używa SQL i jest dość łatwa do zmiany na mySQL (szczególnie jeśli używasz abstrakcyjnych klas do manipulacji bazą danych, tak jak ja!)

W rzeczywistości, szczególnie w przypadku metody „zaakceptowanej odpowiedzi”, może drastycznie zmniejszyć użycie pamięci przez aplikację (nie musisz ładować wszystkich „REKORDÓW” do PHP)

Mez
źródło
To prawda. serialize()również do tego może się przydać. Myślę, że trik do wymyślenia realnego systemu polega na znalezieniu sposobu na indeksowanie węzłów danych bez zabijania się ze złożonością.
saint_groceon
12

Jedną z platform, które rozważam, byłaby platforma blogowa. Ponieważ prawie każdy możliwy widok danych, który chcesz, byłby sortowany według daty, myślałem o tej strukturze:

Jeden katalog na węzeł treści:

./content/YYYYMMDDHHMMSS/

Podkatalogi każdego węzła, w tym

/tags  
/authors  
/comments  

Jak również proste pliki tekstowe w katalogu węzłów dla treści renderowanych przed i po renderowaniu i tym podobne.

Umożliwiłoby to proste glob()wywołanie PHP (i prawdopodobnie odwrócenie tablicy wyników) na zapytanie o prawie wszystko w strukturze treści:

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

Zwróci ścieżki zawierające wszystkie artykuły oznaczone jako „śmieszne”.

saint_groceon
źródło
9

Oto kod, którego używamy dla Liliny:

<?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);
    }
}

?>

Przechowuje każdy wpis jako osobny plik, który, jak stwierdziliśmy, jest wystarczająco wydajny do użycia (żadne niepotrzebne dane nie są ładowane, a zapisywanie jest szybsze).

Ryan McCue
źródło
8

Jeśli zamierzasz używać płaskiego pliku do utrwalania danych, użyj XML do strukturyzowania danych. PHP ma wbudowany parser XML .

Jason
źródło
I postępuj zgodnie z regułami XML dotyczącymi czytelności dla człowieka lub równie dobrze możesz użyć serializacji, json lub czegoś podobnego.
Ben
Bardzo słaba rada. Nigdy nie należy używać XML. To gruba aberracja.
JG Estiot
@JGEstiot Care, aby wyjaśnić dalej?
UncaughtTypeError
7

Jeśli chcesz uzyskać czytelny dla człowieka wynik, możesz również użyć tego typu pliku:

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

W ten sposób masz tylko jeden plik, możesz go łatwo debugować (i ręcznie naprawić), możesz dodać pola później (na końcu każdej linii), a kod PHP jest prosty (dla każdej linii podziel według |).

Jednak wadą jest to, że powinieneś przeanalizować cały plik, aby coś wyszukać (jeśli masz miliony wpisów, to nie jest w porządku) i powinieneś obsługiwać separator w danych (na przykład, jeśli nick to WaR | ordz).

ofaurax
źródło
7

Napisałem dwie proste funkcje przeznaczone do przechowywania danych w pliku. Możesz sam ocenić, czy jest to przydatne w tym przypadku. Chodzi o to, aby zapisać zmienną php (jeśli jest to tablica, łańcuch lub obiekt) do pliku.

<?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;
}
jpcrevoisier
źródło
Wydało mi się to interesujące i jest to LEPSZY sposób, ponieważ po prostu zrzucamy sformatowaną tablicę do pliku. Nie musimy go ponownie konstruować, po prostu wczytaj. Ponadto edycja zmiennych jest trochę łatwa. Nigdy nie użyję tego do przechowywania dużych danych, ale uznałem, że praktyczne jest przechowywanie modułów programu bez bazy danych. Dziękuję Ci.
m3nda
7

To jest inspirujące jako praktyczne rozwiązanie:
https://github.com/mhgolkar/FlatFire
Wykorzystuje wiele strategii przetwarzania danych ...
[skopiowane z pliku Readme]

Bezpłatne lub Strukturalne lub Mieszane

- 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
omran
źródło
7

IMHO, masz dwie opcje, jeśli chcesz uniknąć warzenia czegoś w domu:

  1. SQLite

    Jeśli znasz PDO, możesz zainstalować sterownik PDO, który obsługuje SQLite. Nigdy go nie używałem, ale używałem PDO mnóstwo z MySQL. Mam zamiar dać temu szansę na bieżący projekt.

  2. XML

    Zrobiono to wiele razy dla stosunkowo niewielkich ilości danych. XMLReader to lekka, odczytywana do przodu klasa w stylu kursora. SimpleXML ułatwia odczytanie dokumentu XML do obiektu, do którego można uzyskać dostęp, tak jak do każdej innej instancji klasy.

siliconrockstar
źródło
5

Zwracam tylko uwagę na potencjalny problem z płaską bazą danych w tego typu systemie:

data|some text|more data

row 2 data|bla hbalh|more data

...itp

Problem polega na tym, że dane komórki zawierają znak „|” lub "\ n" wtedy dane zostaną utracone. Czasami byłoby łatwiej podzielić kombinacje liter, których większość ludzi by nie używała.

Na przykład:

Rozdzielacz kolumnowy: #$% (Shift+345)

Rozdzielacz rzędów: ^&* (Shift+678)

Plik tekstowy: test data#$%blah blah#$%^&*new row#$%new row data 2

Następnie użyj: explode("#$%", $data); use foreach, the explode again to separate columns

Albo cokolwiek podobnego. Mogę również dodać, że bazy danych o płaskich plikach są dobre dla systemów z małą ilością danych (tj. Mniej niż 20 wierszy), ale stają się olbrzymią ilością pamięci dla większych baz danych.

Michael Burt
źródło
Słuszne uwagi. Idąc o krok dalej, PHP może naprawdę łatwo serializować JSON. Escapowanie danych wejściowych jest znacznie prostsze, więc nie musisz używać śmiesznych kombinacji ciągów, więc plik jest bardziej czytelny.
Cypher