开发者

Equivalent to glob() which would work with an Array instead of File system

Original title was algorithm to list directories/files only from a particular object path (S3, Google Storage)

https://gist.github.com/9a353e1589ff3ce84c02

Can anyone suggest an algorithm to list directories/files only within a particular object path? For example ahostel.lt/img/ should list only directoies languages and psd, and files background.png, [..]. My algorithm is lengthy and uses three foreach cycles, which is bad for performance but maybe anyone have a better idea how to achieve it using regex or other alternatives.

My system is running on PHP but general logarithm would do just fine as long as it is possible to开发者_运维技巧 convert it to PHP.

In other words, I am looking for an algorithm like glob() which would work with an Array instead of File system.

Simplified directory list: https://gist.github.com/d0c3fa12d4b894938ba5


It looks like you've got a simple array, so here is an alternative which filters the array using a regular expression on the keys.

// Matches only immediate files of ahostel.lt/img/
$pattern = '#^ahostel\.lt/img/[^/]+\.[^/]+$#D';
$keys    = preg_grep($pattern, array_keys($array));
$items   = array_intersect_key($array, array_flip($keys));

Another way, since iterators are awesome, without writing a bespoke one, would be to use a RegexIterator to do the work of filtering the keys. You would then just loop over the filtered iterator, or use iterator_to_array() to get an array containing only the filtered values.

$items = new RegexIterator(
    new ArrayIterator($array),
    '#^ahostel\.lt/img/[^/]+\.[^/]+$#D',
    RegexIterator::MATCH,
    RegexIterator::USE_KEY
);

There are a bazillion different ways that you could use or create iterator filtering, even using something like fnmatch() within the accept() method of a FilterIterator to use the wildcard patterns like with glob().

class GlobKeyFilterIterator extends FilterIterator
{
    protected $pattern;
    public function __construct(Iterator $it, $pattern)
    {
        $this->pattern = $pattern;
        parent::__construct($it);
    }
    public function accept()
    {
        return fnmatch($this->pattern, $this->key(), FNM_PATHNAME);
    }
}

$items = new GlobKeyFilterIterator(
    new ArrayIterator($array),
    'ahostel.lt/img/*.*'
);


Many php programmers tend to overcomplicate things. A simple problem always has a simple solution.

$result = array();
foreach($dir as $k => $v)
    if(strpos($k, 'ahostel.lt/img/') === 0)
        $result[$k] = $v;

Not only this is more readable then any convoluted smart-alec code, but also much faster.


In other words, I am looking for an algorithm like glob() which would work with an Array instead of File system.

You can use an ArrayIterator and wrap it into a custom FilterIterator

class CustomFilterIterator extends FilterIterator
{
    public function accept()
    {
        return strpos($this->key(), 'ahostel.lt/img/') === 0 &&
           pathinfo($this->key(), PATHINFO_EXTENSION) === 'png';
    }
}

The accept method has to return a boolean. If the boolean is TRUE, the currently iterated element will be considered for inclusion in the iteration. In the above example, anything not starting with 'ahostel.lt/img/' and ending in a png extension will be ignored. You can add additional filter criteria as you seem fit. To access the key, you use $this->key(). For the value, use $this->current().

Usage (codepad)

$iterator = new CustomFilterIterator(new ArrayIterator($yourArray));

// to create a subset of the original array use
$filteredArray = iterator_to_array($iterator);

// or use good old foreach
foreach ($iterator as $path => $fileProperties) {
    var_dump($path, $fileProperties);
}

As an alternative or addition, you could use a RegexIterator.

The two main benefits when using iterators is reuse and testability: iterators can be stacked, so the above CustomFilterIterator could be broken into two iterators, like a PathFilter and an ExtensionFilter. Then you'd just wrap the ArrayIterator into both filter iterators to create a flexible chain of filter on top. Because iterators are classes, they can easily be tested and mocked in classes that have the iterator as a dependency, which is something you cannot do when putting the filtering logic into the foreach loop.

Additional resources about iterators and SPL in general:

  • https://stackoverflow.com/questions/1957133/php-spl-reference-documentation/4218646#4218646


$already_included       = array();

    foreach($list as $key => $object)
    {
        $clean_key  = substr($key, strlen($uri));
        $explode    = explode('/', $clean_key);

        if(count($explode) >= 1 && !in_array($explode[0], $already_included))
        {
            $already_included[] = $explode[0];

            $files['directories'][] = array
            (
                'path'          => $uri . $explode[0] . '/',
                'name'          => $explode[0],
                'last_modified' => $object['last_modified'],
            );

        }

        if(substr_count($key, '/', $path_str_length) === 0)
        {
            $basename   = pathinfo($key, PATHINFO_BASENAME);

            if(strpos($basename, '.') !== FALSE)
            {
                $files['files'][]   = array
                (
                    'path'          => $key,
                    'name'          => $basename,
                    'size'          => $object['size'],
                    'last_modified' => $object['last_modified'],
                );
            }
            elseif(strrpos($basename, '_$folder$') !== FALSE)
            {
                $files['directories'][] = array
                (
                    'path'          => $key,
                    'name'          => substr($basename, 0, -9),
                    'last_modified' => $object['last_modified'],
                );
            }

        }
    }   
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜