Как стать автором
Обновить

Finder component: найдите ваши файлы

Время на прочтение5 мин
Количество просмотров3.8K
Автор оригинала: Fabien Potencier

Дня два назад на github появился новый компонент для Symfony 2 под названием Finder. И вот сегодня в твиттере Фабьена я увидел ссылку на новый пост в его блоге об этом компоненте. Ну что ж, давайте разбираться. Под катом перевод поста Find your Files. Итак начнем.

Лучшие практики для поиска файлов при помощи PHP в последние годы сильно развились. Возвращаясь в 2004 год, одной из первых вещей, которую я сделал на PHP было портирование Perl-овского модуля File::Find::Rule на PHP. File::Find::Rule это замечательный путь описания файлов и каталогов с которыми вы желаете работать. Я использовал встроенные PHP функции opendir, readdir, и closedir, и он делал свою работу достаточно хорошо. PHP класс был назван sfFinder, и он до сих пор может быть найден в версиях symfony. Даже если класс привязан к symfony, я знаю что некоторые люди используют его для задач почти всех типов, не обязательно связанных с symfony.

Но код начал показывать свой возраст; во-первых, потому что я многое узнал с того времени про PHP, и так же потому, что сейчас есть лучшие варианты. Появились итераторы! PHP 5 поставляется с целой связкой классов для итераций, которые упрощают все типы переборов и повторений. Вы можете пройти по итератору стандартным оператором foreach, очень мощной PHP конструкцией.

Итераторы в PHP


Итак, как получить все файлы и директории рекурсивно при помощи PHP итераторов? Откровенно говоря, я не знаю. Хорошо, я более-менее знаю, какие классы использовать и как их скомпоновать, но чтобы не заморачиваться, я всегда использую существующий блок кода для того чтобы сделать это правильно. Вот этот код:
// некоторые флаги для фильтрации ... и следовать символическим ссылкам (symlink)
$flags = \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS;
// создаем простой рекурсивный итератор директорий
$iterator = new \RecursiveDirectoryIterator($dir, $flags);
// делаем его действительно рекурсивным итератором
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
// проходим по нему
foreach ($iterator as $file)
{
 // делаем что-то с $file (экземпляром \SplFileInfo)
}

Примечание: Обратили внимание на причудливый символ \ перед каждым встроенным классом? Это путь обращения к встроенным PHP классам, когда вы используете их в контексте пространства имен PHP 5.3.

Как вы могли убедиться, ничего сложного. Вы просто должны знать какой итератор использовать, какие флаги можно применять, и как использовать их вместе. То есть, первичный входной барьер — это обучение. В интернете есть множество презентаций и туторов про итераторы, но в официальной документации на php.net, вероятно, недостает нескольких хороших примеров.

Другая «проблема» что все очень объектно ориентировано. И как только вы хотите отфильтровать итератор, вы должны создать свои собственные классы, которые в большинстве случаев выглядят не практично. Это потому, что PHP итераторы очень мощные и были написаны для того чтобы быть итераторами общего назначения.

Что такое фильтрация? Давайте, например, я хочу исключить все файлы заканчивающиеся на .rb из итератора. Я могу создать простой \FilterIterator для этого:
class ExcludeRubyFilesFilterIterator extends \FilterIterator
{
 public function accept() {
  $fileinfo = $this->getInnerIterator()->current();

  if (preg_match('/\.rb$/', $fileinfo)) {
   return false;
  }

  return true;
 }
}


Фильтрующий итератор может быть использован с предыдущим кодом, например таким включением:
$iterator = new ExcludeRubyFilesFilterIterator($iterator);

Это довольно просто. Но когда мне нужно найти файлы или директории, мне всегда нужны фильтры одного специализированного типа, по типу исключения файлов систем контроля версий (такие как .svn и .git директории), фильтрация файлов по имени или размеру.

Finder компонент в Symfony


Вместо того чтобы еще и еще раз писать одни и те же итераторы, я скомпоновал их в виде компонента Symfony: как Finder компонент.

Компонент Finder в Symfony снабжен многими специализированными классами итератора для нахождения файлов и директорий. Он также добавляет обертку для легкости их каждодневного использования.

Как каждый компонент Symfony, в начале вам нужно загрузить ваш скрипт любым загрузчиком классов который может загружать классы следуя стандартам совместимости PHP 5.3, таким как класс Symfony UniversalClassLoader:
require_once '/path/to/src/Symfony/Foundation/UniversalClassLoader.php';

use Symfony\Foundation\UniversalClassLoader;

$classLoader = new UniversalClassLoader();
$classLoader->registerNamespace('Symfony', '/path/to/src');
$classLoader->register();


Теперь, давайте посмотрим как использовать класс Finder, главный класс компонента:
use Symfony\Components\Finder\Finder;

$finder = new Finder();
$iterator = $finder->files()->in(__DIR__);

foreach ($iterator as $file)
{
 print $file->getRealpath()."\n";
}


Код сверху рекурсивно печатает имена всех файлов в текущей директории. Заметьте что класс Finder использует fluent interface (когда вызовы методов можно делать в виде цепочки), что означает что все методы возвращают экземпляр класса Finder. Единственным исключением есть метод in(), который строит и возвращает итератор для данной категории, или массива директорий:
$iterator = $finder->files()->in(array('/path1', '/path2'));

Примечание: вы можете конвертировать итератор в массив с помощью метода iterator_to_array(), и получить количество элементов при помощи iterator_count().

Если вы хотите ограничить итератор на возвращение только PHP файлов в текущей директории, используйте методы name() и maxDepth():
$iterator = $finder
 ->files()
 ->name('*.php')
 ->maxDepth(0)
 ->in(__DIR__);

Метод name() поддерживает globs, strings, или regexes:
$finder
 ->files()
 ->name('/\.php$/');


Есть также методы для исключения файлов по имени или исключения целых директорий содержимого из совпадений:
$finder
 ->files()
 ->name('test.*')
 ->notName('*.rb')
 ->exclude('ruby');


Результат будет содержать файлы с названием test любого расширения, но только если они не заканчиваются на .rb (исключается test.rb), и итератор не будет совпадать с файлами в ruby директориях (ruby/foo/test.php к примеру, не совпадет).

Если вы хотите следовать ссылкам, используйте метод followLinks():
$finder
 ->files()
 ->followLinks();


Вы также можете ограничивать файлы по размеру:
$finder
 ->files()
 ->name('/\.php$/')
 ->size('< 1.5K');


Большинство методов кумулятивные. То есть, если вы хотите получить все файлы на PHP и Python с размером между 1 и 2 K, можно использовать подобный код:
$finder
 ->files()
 ->name('*.php')
 ->name('*.py/')
 ->size('>= 1K')
 ->size('<= 2K');


Примечание: по умолчанию, итератор игнорирует популярные файлы систем контроля версий. Это может быть изменено при помощи метода ignoreVCS().

Так как метод in() возвращает экземпляр \Iterator, вы можете обернуть его своим специализированным итеретором. Но вместо создания класса, вы можете также использовать метод filter():
$filter = function (\SplFileInfo $fileinfo)
{
  if (strlen($fileinfo) > 10) {
   return false;
  }
};

$finder
 ->files()
 ->name('*.php')
 ->filter($filter);


Этот пример исключает все файлы с длиной файла более 10 символов.

Если хотите отсортировать результат по имени, используйте метод sortByName():
$finder
 ->files()
 ->name('*.php')
 ->sortByName();


Заметьте, что методы sort* требуют прохода всех совпавших элементов для того чтобы сделать свою работу. Для больших итераторов это может быть довольно медленно.

За занавесом, класс Finder использует специализированные классы итераторы:
  • ChainIterator
  • CustomFilterIterator
  • DateRangeFilterIterator (coming soon)
  • ExcludeDirectoryFilterIterator
  • FileTypeFilterIterator
  • FilenameFilterIterator
  • IgnoreVcsFilterIterator
  • LimitDepthFilterIterator
  • SizeRangeFilterIterator
  • SortableIterator
Взгляните на код для того чтобы узнать больше про эти итераторы и то, как они работают.
Теги:
Хабы:
+13
Комментарии7

Публикации

Изменить настройки темы

Истории

Работа

PHP программист
155 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн