Symfony po krůčkách – Filesystem a Finder

V dnešním článku si ukážeme, jak v Symfony za pomocí komponent Filesystem a Finder pracovat se soubory. Projdeme si jednotlivé funkce a vše si vyzkoušíme na jednoduchém projektu.

Seriál: Symfony po krůčkách (18 dílů)

  1. Symfony po krůčkách – Event Dispatcher 30.11.2015
  2. Symfony Console jako první rande se Symfony 7.12.2015
  3. Symfony po krůčkách – Filesystem a Finder 14.12.2015
  4. Symfony po krůčkách – Paralýza možností? OptionsResolver tě zachrání 21.12.2015
  5. Symfony po krůčkách – spouštíme procesy 4.1.2016
  6. Symfony po krůčkách – Translation – překlady jednoduše 11.1.2016
  7. Symfony po krůčkách – Validator (1) 18.1.2016
  8. Symfony po krůčkách – Validator (2) 25.1.2016
  9. Symfony po krůčkách – Routing 1.2.2016
  10. Symfony po krůčkách – MicroKernel 9.2.2016
  11. Konfigurujeme Symfony pomocí YAMLu 16.2.2016
  12. Symfony po krůčkách – oblékáme MicroKernel 23.2.2016
  13. Symfony po krůčkách – ClassLoader 29.2.2016
  14. Symfony po krůčkách – Twig 8.3.2016
  15. Symfony po krůčkách – Twig II. 15.3.2016
  16. Symfony po krůčkách – DomCrawler a CssSelector 23.3.2016
  17. Symfony po krůčkách – HTTP fundamentalista 12.4.2016
  18. Symfony po krůčkách – ušli jsme pořádný kus 19.4.2016

Není to vynalézání kola?

PHP má pro práci se soubory už vestavěné funkce, proč tedy přidávat další vrstvu? Odpověď je jednoduchá – nativní funkce se sice snaží replikovat příkazy, které známe z konzole, ale ne už jejich funkcionalitu. A pokud narazí na chybu, jediné, čeho se zpravidla dočkáme, je warning:

Jan-MacBook-Air:~ klatys$ php -a
php > mkdir('tmp/test');
PHP Warning: mkdir(): No such file or directory in php shell code on line 1
php > copy('./foo.log', './tmp/test/');
PHP Warning: copy(): The second argument to copy() function cannot be a directory in php shell code on line 1

vs.

Jan-MacBook-Air:~ klatys$ mkdir -p tmp/test
Jan-MacBook-Air:~ klatys$ cp foo.log ./tmp/test/

Chytrý Filesystem v Symfony

Komponenta se vám nejenže snaží poskytnout stejnou funkcionalitu, ale v případě chyby vrátí výjimku Symfony\Component\Filesystem\Exception\IOException, se kterou se lépe pracuje.

Další výhodou pak je, že některé metody (exists(), mkdir(), touch(), remove()…) dokážou na vstupu přijmout pole cest a zpracovat je naráz.

Zkusme si pro začátek vytvořit zanořenou složku a do ní zapsat soubor. Využijeme také Console komponentu z předchozího dílu.

$filesystem = new Filesystem();
//vytvoříme si soubor se kterým budeme pracovat
$filesystem->dumpFile("foo.txt", "Příšerně žluťoučký kůň úpěl ďábelské ódy");
//složku do které jej přesuneme
$filesystem->mkdir("tmp/test");
//a jdeme na to!
$filesystem->copy("foo.txt", "tmp/test/foo.txt", true);

echo "Prošlo to?\n";
echo $filesystem->exists("tmp/test/foo.txt") ? "jo!" : ":(";

a pustíme si jej:

$ ./console filesystem:1
Prošlo to?
jo!

Kompletní kód příkladu najdete na Githubu

V příkladu jsme použili mkdir() a copy(), které známe z unixových systémů, a jednu novou – dumpFile().

Kdo z vás se již někdy snažil z php atomicky měnit soubory, ví, kolik kódu kolem toho musí napsat. Metoda dumpFile() to řeší elegantně za vás – nejprve zapíše do dočasného souboru a teprve po vydumpování celého obsahu jej přejmenuje.

Konkrétně vypadá takto:

/**
 * Atomically dumps content into a file.
 *
 * @param string $filename The file to be written to.
 * @param string $content  The data to write into the file.
 *
 * @throws IOException If the file cannot be written to.
 */
public function dumpFile($filename, $content)
{
    $dir = dirname($filename);
    if (!is_dir($dir)) {
        $this->mkdir($dir);
    } elseif (!is_writable($dir)) {
        throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
    }
    $tmpFile = $this->tempnam($dir, basename($filename));
    if (false === @file_put_contents($tmpFile, $content)) {
        throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
    }
    $this->rename($tmpFile, $filename, true);
}

File Locking

Každý už také někdy jistě řešil zamykání přes soubor – typicky pokud se vám mohou překrývat crony, může docházet k nemilým situacím, kdy dva procesy zpracovávají tatáž data nebo si je navzájem přepisují. Pro tyto případy je pak dobré si vytvořit souborový zámek:

$lockHandler = new LockHandler("mujcron.lock");
if (!$lockHandler->lock()) {
   echo "Jiná instance commandu ještě běží!";
   return false;
}

Chytáme výjimky

Zkusíme si napsat další command a zapracovat také odchytnutí výjimky:

$filesystem = new Filesystem();

$files = [
    "http://placekitten.com/408/287",
    "http://placekitten.com/300/128",
    "http://placekitten.com/123/456",
    "http://placekitten.com/54/68",
    "http://foo.bar/123"
];

foreach ($files as $key => $file) {
    try {
        $targetDir = "tmp/".$key;
        $filesystem->mkdir($targetDir);
        $targetFile = $targetDir . "/" . $key . ".jpg";
        $outputInterface->write("kopíruji " . $file . " do " . $targetFile." - ");
        $filesystem->copy($file, $targetFile);
    } catch (IOException $e) {
        $outputInterface->writeln("Chyba ".$e->getMessage());
        continue;
    }
    $outputInterface->writeln("OK!");

    //Pro další příklad si ještě upravíme čas přístupu
    $accessDate = new DateTime();
    $accessDate->sub(new DateInterval("P".$key."D"));
    $filesystem->touch($targetFile, $accessDate->format("U"), $accessDate->format("U"));
}

Celý kód příkladu najdete na Githubu

Finder

Filesystem je skvělý na práci se soubory – jejich kontrolu, úpravu atributů a zápis. Finder nám pomůže s tím, jak soubory najít. S jeho pomocí tak můžeme napsat jednoduchého správce souborů.

Soubory i složky lze filtrovat dle

  • Názvu Finder::name($pattern)
    například $finder->name(“*.jpg”);
  • Obsahu Finder::contains($pattern) a Finder::notContains($pattern)
    například $finder->contains(“foo”);
  • Velikosti Finder::size($size)
    například $finder->size('< 1.5K');
  • Datumu modifikace Finder::date($date)
    například $finder->date(">= -3 day”) – dají se použít výrazy zpracovatelné funkcí strtotime()
  • Poslední možnost je napsat si vlastní callback, je však třeba mít na paměti, že se pak prochází všechny soubory, které odpovídají jiným filtrům
    $finder->files()->filter(function (\SplFileInfo $file) {
            if (strlen($file) > 10) {
                    return false;
                }
        }
    );

Ve výchozím režimu hledá Finder jak soubory, tak složky. Pokud si chceme vybrat jen z jednoho, stačí použít metodu Finder::directories() nebo Finder::files(). Například tedy:

$finder->files()->date(“since yesterday”);

V předchozích příkladech jsme si vytvořili složky se soubory a nastavili jim různá data modifikace. Pojďme si je tedy vypsat. Opět sáhneme po znalostech z minulého článku, tentokrát pro výpis použijeme Table.

$table = new Table($outputInterface);
$table->setHeaders([
    "Název",
    "Velikost",
    "Datum modifikace",
]);

foreach ($finder->in("tmp") as $key => $item) {
    /** @var SplFileInfo $item */
    $table->addRow(
        [
            $item->getRelativePathname(),
            $item->isDir() ? "---" : round(($item->getSize() / 1024), 1) . "kB",
            date("Y-m-d H:i:s", $item->getMTime()),
        ]
    );
}
$table->render();

Celý kód příkladu najdeš na Githubu

Pokud si pustíme příkaz, vypíše nám všechny soubory a složky vytvořené v předchozích příkladech
Screenshot 2015-12-11 16.42.20

Vyzkoušet si můžeme také filtrování výsledků – do příkladu jsem zahrnul filtrování:
podle jména
$finder->name((string)$input->getOption("name"));

podle datumu modifikace
$finder->date(">= -" . (int)$input->getOption("max-age") . " day");

a podle velikosti souborů
$finder->files()->size("< " . (int)$input->getOption("max-kbytes") . "K");

Díky kterým lze výpis jednoduše filtrovat:
Screenshot 2015-12-11 16.49.15

2 tipy, které se vám budou hodit

  • Finder ve výchozím stavu ignoruje běžné názvy verzovacích složek konkrétně
    private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
    ignorování lze vypnout přes $finder->ignoreVCS(false);
  • Pokud chcete procházet jen složky ke kterým máte přístup, lze použít příkaz
    $finder->ignoreUnreadableDirs();

Zase o krok dále

Dnes jsme si ukázali, že práce se soubory může být díky Symfony komponentám radost.
A to hlavně díky tomu, že:

Filesystem

  • se při ukládání souboru postará o složku
  • umí uzavírat soubory a předejít neúplným nebo neplatným datům na disku
  • převádí chyby na výjimky, které můžeme zpracovat

Finder

  • nám najde soubory i složky
  • v nich umí filtrovat podle názvu, typu, data vytvoření a velikosti
  • má pár hezkých vychytávek pro různé edge case

Celý projekt najdete na Githubu a můžete si s ním dále hrát.

Dost čtení, přijďte si taky pokecat

Už zítra 15. 12. v 18:00 hod v Praze nebo Brně nad číší vychlazeného piva. V Ostravě až od nového roku.
Poradíme vám a podpoříme vás. Těšíme se!

Jenda vede partu vývojářů v pražském Skrz.cz a společně se svým teamem přispívá nejen k unikátnosti vlastní platformy. Baví ho posouvat hranice rychlosti a automaty, které ubírají uživatelům vrásky a zpříjemňují život. Zároveň se podílí na organizaci projektu DevCirkus, který si dal za úkol dávat dohromady špičkové lidi ze zajímavých internetových firem, kteří pak hledají společná témata, ve kterých by si mohli poradit.

Komentáře: 8

Přehled komentářů

Ondřej Mirtes Stavovost Finderu
Jan Klat Re: Stavovost Finderu
OndraM Re: Stavovost Finderu
Jan Klat Re: Stavovost Finderu
jirkakoutny Re: Stavovost Finderu
Re: Stavovost Finderu
HonzaMarek Re: Stavovost Finderu
Zdroj: https://www.zdrojak.cz/?p=16932