Symfony Finder Component

Có bao giờ bạn phải tìm kiếm các tập tin hay thư mục (ở mức đệ quy các thư mục con) với PHP? Bạn thấy nó phức tạp và khó sử dụng không? Chúng ta thử đi xem qua đoạn code khi dùng PHP thuần xem sao nhé:

// some flags to filter . and .. and follow symlinks
$flags = \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS;
// create a simple recursive directory iterator
$iterator = new \RecursiveDirectoryIterator($dir, $flags);
// make it a truly recursive iterator
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
// iterate over it
foreach ($iterator as $file)
{
    // do something with $file (a \SplFileInfo instance)
}

Nhìn thật là chán, đúng không (yaoming)? Hôm nay mình xin giới thiệu một component của Symfony là Finder để giúp chúng ta đơn giản hóa trong việc tìm kiếm tập tin hay thư mục trong PHP nhé.

The Finder component

Finder component giúp chúng ta tìm kiếm các tập tin và thư mục một cách đơn giản và trực quan nhất

Cài đặt

Bạn có thể cài đặt Symfony Finder thông qua hai cách sau:

Sau đó, bạn cần require file vendor/autoload.php để sử dụng cơ chế autoloading của Composer để sử dụng.

Sử dụng

Chúng ta thử duyệt đệ quy tất cả các tên của tập tin trong thư mục hiện tại và in chúng ra với Finder nhé:

use Symfony\Component\Finder\Finder;

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

foreach ($finder as $file) {
    // Dump the absolute path
    var_dump($file->getRealPath());

    // Dump the relative path to the file, omitting the filename
    var_dump($file->getRelativePath());

    // Dump the relative path to the file
    var_dump($file->getRelativePathname());
}

$file bây giờ sẽ là instance của SplFileInfo của Symfony được extend từ SplFileInfo của PHP, class này cung cấp cho chúng ta các method để làm việc với các đường dẫn tương đối với tập tin (hoặc thư mục). Tất cả các method sẽ trả về instance của Finder!

Instance của Finder là một PHP Iterator. Vì vậy, bạn có thể duyệt qua Finder với method foreach và bạn có thể chuyển đổi nó thành một mảng với method iterator_to_array hoặc lấy tất cả số lượng tập tin với method interator_count.

Chú ý: Khi bạn tìm kiếm nhiều vị trí thông qua method in() thì mỗi vị trí sẽ sinh ra một iterator riêng biệt. Điều này có nghĩa là sẽ có nhiều tập tin kết hợp lại thành một. Mặc định, iterator_to_array sử dụng các key của bộ kết quả khi chuyển đổi thành mảng nên có thể dẫn đến một vài key sẽ bị duplicate và dẫn đến việc kết quả của key đó sẽ bị ghi đè. Bạn có thể tránh việc đó bằng cách thêm tham số false vào method iterator_to_array.

Các tiêu chí

Chúng ta sẽ đi tìm hiểu các cách để lọc và sắp xếp các kết quả khi sử dụng Finder nhé.

Vị trí (location)

Vị trí là một tiêu chí bắt buộc. Vì nó sẽ thông báo với thằng Finder biết cần phải tìm kiếm ở đâu:

$finder->in(__DIR__);

Bạn cũng có thể tìm kiếm ở nhiều vị trí bằng cách gọi nhiều lần (chaining calls) method in():

$finder->files()->in([__DIR__, "/elsewhere"]);
// Hoặc
$finder->in(__DIR__)->in("/elsewhere");
// Hoặc
$finder->in([__DIR__, "/elsewhere"]);

Bạn có thể sử dụng ký tự đại diện để tìm kiếm các thư mục khớp với mẫu tìm kiếm cần thiết:

$finder->in("vendor/symfony/*/*/Exception");

Mỗi mẫu tìm kiếm sẽ xử lý ít nhất một đường dẫn thư mục. Kế, bạn có thể loại trừ một (hoặc nhiều) thư mục ra khỏi vị trí cần tìm kiếm bằng cách sử dụng method exclude():

$finder->in("vendor/symfony")->exclude("console");
// Hoặc
$finder->in("vendor/symfony")->exclude(["console", "debug"]);

Hoặc bạn cũng có thể bỏ qua những thư mục mà bạn không có quyền đọc để tránh lỗi failed to open dir: Permission denied bằng cách sau:

$finder->ignoreUnreadableDirs()->in("/var/log");

Ngoài ra, bạn có thể truyền vào một URL được hỗ trợ trong danh sách các giao thức được định nghĩa tại đây:

$finder->in("ftp://ftp.uconn.edu/");

foreach ($finder->files() as $file) {
    echo $file->getPathname() . PHP_EOL;
}

Và bạn cũng có thể làm việc với các stream tự định nghĩa:

use Symfony\Component\Finder\Finder;

$s3 = new \Zend_Service_Amazon_S3($key, $secret);
$s3->registerStreamWrapper('s3');

$finder = new Finder();
$finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
foreach ($finder->in('s3://bucket-name') as $file) {
    // ... do something with the file
}

Bạn có thể đọc thêm tài liệu về Stream tại đây để biết làm sao có thể định nghĩa được một stream cho riêng mình và sử dụng nó với thằng Finder!

Tập tin và thư mục

Mặc định, Finder sẽ trả về tất cả các tập tin và thư mục. Bạn có thể sử dụng hai method files()directories() để lấy riêng biệt danh sách các tập tin hoặc thư mục:

$finder->files();

$finder->directories();

Bạn có thể follow một soft link bằng method followLinks():

$finder->files()->followLinks();

Mặc định, iterator sẽ bỏ qua các tập tin VCS, bạn có thể truyền tham số false vào method ignoreVCS():

$finder->ignoreVCS(false);

Sắp xếp

Bạn có thể sắp xếp kết quả bằng tên hoặc kiểu tập tin (thư mục trước xong mới đến tập tin):

$finder->sortByName();

$finder->sortByType();

Tất cả các method sort* cần phải có các yếu tố phù hợp để thực hiện. Vòng lặp càng lớn thì tốc độ càng chậm.

Bạn cũng có thể tự định nghĩa thuật toán sắp xếp riêng. Chúng ta thử viết kiểu sắp xếp dung lượng tập tin từ nhỏ tới lớn nhé:

$sortSizeAsc = function(\SplFileInfo $fileA, \SplFileInfo $fileB) {
    return $fileA->getSize() > $fileB->getSize();
}

$finder->sort($sortSizeAsc);

Tên

Giới hạn các tập tin trong kết quả bằng tên với method name():

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

Method name() cũng chấp nhận globs, chuỗi hay một biểu thức chính quy:

$finder->files()->name("/\.php$/");

Method notName() giúp chúng ta lọc các tập tin không mong muốn:

$finder->files()->notName("*.rb");

Nội dung

Giới hạn tập tin bằng nội dung của nó với method contains():

$finder->files()->contains("lorem ipsum");

Method contains() chấp nhận một chuỗi hoặc một biểu thức chính quy:

$finder->files()->contains("/lorem\s+ipsum$/i");

Method notContains() sẽ loại bỏ các file có nội dung chỉ định:

$finder->files()->notContains("dolor sit amet");

Đường dẫn

Giới hạn tập tin và thư mục bằng đường dẫn bằng method path():

// Tìm các tập tin có chứa từ "data" trong đường dẫn (bao gồm cả tập tin và thư mục)
$finder->path("data");

// Tìm các tập tin *.xml trong thư mục "data" và cả file data.xml (nếu nó tồn tại)
$finder->path("data")->name("*.xml");

Method path() chấp nhận kiểu dữ liệu là một chuỗi hoặc một biểu thức chính quy:

$finder->path("foo/bar");

// Hoặc

$finder->path("/^foor\/bar");

Method notPath() sẽ loại các tập tin bằng đường dẫn mà chúng ta cung cấp:

$finder->notPath("other/dir");

Kích thước

Giới hạn tập tin bằng kích thước với method size():

$finder->size("< 1.5K");

Bạn cũng có thể sử dụng khoảng kích thước muốn tìm bằng cách gọi method size() nhiều lần (chaining calls):

$finder->size(">= 1K")->size("<= 2K");

Các biểu thức so sánh mà bạn có thể sử dụng: >, >=, <, <=, ==, !=. Kích thước dữ liệu mà bạn có thể sử dụng: Kilobytes (k, ki), Megabytes (m, mi), Gigabytes (g, gi) hoặc kết thúc bằng hậu tố i phù hợp với chuẩn của IEC standard.

Thời gian

Giới hạn tập tin bằng ngày tháng sửa đổi cuối cùng với method date():

$finder->date("since yesterday");

Biểu thức so sánh mà bạn có thể sử dụng: >, >=, <, <=, ==. Bạn cũng có thể sử dụng since hoặc after thay cho >until hoặc before thay cho <. Giá trị sử dụng để so sánh thì bạn có thể sử dụng các kiểu ngày tháng theo chuẩn được hỗ trợ bởi function strtotime.

Chiều sâu

Theo mặc định, Finder sẽ duyệt đệ quy tất cả các thư mục mà nó gặp. Bạn có thể hạn chế chiều sâu của việc đệ quy bằng method dept():

$finder->depth("== 0");

$finder->depth("< 3");

Tùy chỉnh bộ lọc

Bạn có thể giới hạn các tập tin phù hợp bằng bộ lọc riêng bằng cách sử dụng method filter():

$isLinkFile = function(\SplFileInfo $file) {
    return $file->isLink();
}

$finder->files()->filter($isLinkFile);

Đọc nội dung tập tin

Bạn có thể đọc nội dung của tập tin bằng method getContents():

foreach ($finder->files() as $file) {
    $content = $file->getContents();
    // ...
}

Lời kết

Đến đây là đã kết thúc bài viết giới thiệu về component Finder của Symfony. Hy vọng qua bài viết ngắn này sẽ giúp ích cho những ai có ý định làm việc với tập tin và thư mục với PHP. Hẹn gặp lại mọi người trong bài viết tiếp theo. Có thể mình sẽ tiếp tục giới thiệu về Symfony component (hoặc không 😄) vì họ cung cấp rất nhiều component mà mình thấy khá là hay ho và hữu ích 😄! Hẹn gặp lại. Thân ái và quyết thắng (lol)!

Tài liệu tham khảo: http://symfony.com/doc/current/components/finder.html