TLDR version:
how to utilize PharData::buildFromIterator in order to build .tar archive from /path/to/project/, but excluding a list of files, like /path/to/project/file0.txt, /path/to/project/file1.txt, /path/to/project/file2.txt?
Longer version, what-have-I-tried included:
In order to build .tar archive from dir path, I tried with the simple PharData::buildFromDirectory method which has a second, optional argument which... kind of... does exact opposite of what I need
"pcre regular expression [...] Only file paths matching the regular expression will be included in the archive"
. So I guess, the only option left is to utilize PharData::buildFromIterator which has a simple usage example:
$phar->buildFromIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/path/to/project', FilesystemIterator::SKIP_DOTS)
),
'/path/to/project'
);
This example may be a starting point but from here I do not know how to exclude a list of files from iterator?
In case someone finds this question useful, I shall post an answer since I have figured it out how to do it, with solutions to similar problem found here, thanks to user Levi Morrison.
(tested OK on Windows filesystem, assumes __DIR__ is the directory where it runs)
$exclude = ['file0.txt', 'file1.txt', 'file2.txt'];
/**
* #param SplFileInfo $file
* #param mixed $key
* #param RecursiveCallbackFilterIterator $iterator
* #return bool True if you need to recurse or if the item is acceptable
*/
$filter = function ($file, $key, $iterator) use ($exclude) {
foreach($exclude as $excludefilename){
if(
strcmp(__DIR__ . DIRECTORY_SEPARATOR . $excludefilename, $file) === 0
) return false;
}
return true;
};
$innerIterator = new RecursiveDirectoryIterator(
__DIR__,
RecursiveDirectoryIterator::SKIP_DOTS
);
$iterator = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator($innerIterator, $filter)
);
$phar = new PharData('project.tar');
$phar->buildFromIterator($iterator, __DIR__);
Related
I am using the loader in Composer like so.
/** #var \Composer\Autoload\ClassLoader $loader */
$loader = require __DIR__ . '/vendor/dependencies/autoload.php';
$loader->add('MyAppNamespace', __DIR__, true);
As you can see the loader takes 3 parameters, which is documented here: https://getcomposer.org/apidoc/1.0.0/Composer/Autoload/ClassLoader.html#method_add .
I am extremely confused at to what the 3rd boolean parameter $prepend does. The link above says the $prepend means the following: Whether to prepend the directories. However I have no idea what this means.
Can someone please explain what the $prepend parameter does with an example.
Thanks!
Have a look at composer code:
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
As you can see, $prepend determines if paths provided by you (__DIR__) will be prepended or appended. Usually it doesn't matter, as you have only one path per namespace, but you could have more, and e.g. use this mechanism to override some class delivered with vendor lib by your own implementation.
This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
I have this PHP code, but I have only written one recursive function before. I want to begin in a particular subdirectory and check for the presence of a file. If it doesn't exist in the directory I want to check the parent folder for the file and then recursively go on until I either don't find the file and return "" or find the file and return its contents. Here is my function:
/**
* Recursive function to get sidebar from path and look up tree until we either
* find it or return blank.
*/
class MyClass{
public function findSidebarFromPath($path){
if(file_exists($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html")){
return file_get_contents($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html");
} else {
if(count($path) > 0){
# Pop element off end of array.
$discard = array_pop($path);
$this->findSidebarFromPath($path);
} else {
return "";
}
}
}
}
So when I call it, I do this:
$myclass = new MyClass();
$myclass->findSidebarFromPath(array("segment1", "segment2", "segment3"));
Let's say the file I am trying to find is in a directory called "segment2". The function never finds it. The array_pop function doesn't pop off the element off the end of the array and then call the findSidebarFromPath function.
If you write this as a standalone function, it will probably be more useful to you in other areas. After we understand how it works, we'll show you how you can add a public function to your class that can utilize it.
/**
* #param $root string - the shallowest path to search in
* #param $path string - the deepest path to search; this gets appended to $root
* #param $filename string - the file to search for
* #return mixed - string file contents or false if no file is found
*/
function findFile($root, $path, $filename) {
// create auxiliary function that takes args in format we want
$findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
// resolve the complete filename
$file = "{$cwd}/{$filename}";
// if the file exists, return the contents
if (file_exists($file)) return file_get_contents($file);
// if the cwd has already reached the root, do not go up a directory; return false instead
if ($cwd === $root) return false;
// otherwise check the above directory
return $findFileAux(dirname($cwd), $filename);
};
// starting checking for the file at the deepest segment
return $findFileAux("{$root}/{$path}", $filename);
}
Check the example output on ideone.
So here's how to use it
findFile($_SERVER["DOCUMENT_ROOT"], "foo/bar/qux", "sidebar.html");
Here's how you would integrate it with your class. Notice that this public function has the same API as in your original code
class MyClass {
/**
* #param $path array - path segments to search in
* #return mixed - string file contents or false if sidebar is not found
*/
public function findSidebarFromPath($path) {
// Here we call upon the function we wrote above
// making sure to `join` the path segments into a string
return findFile($_SERVER["DOCUMENT_ROOT"], join("/", $path), "sidebar.html";
}
}
Additional explanation
If $_SERVER["DOCUMENT_ROOT"] is /var/www...
Check /var/www/foo/bar/qux/sidebar.html, return if exists
Check /var/www/foo/bar/sidebar.html, return if exists
Check /var/www/foo/sidebar.html, return if exists
Check /var/www/sidebar.html, return if exists
Because we got to the root (/var/www) no further searches will happen
return false if sidebar.html did not exist in any of the above
Here's the same function with the explanatory comments removed
/**
* #param $root string - the shallowest path to search in
* #param $path string - the deepest path to search; this gets appended to $root
* #param $filename string - the file to search for
* #return mixed - string file contents or false if no file is found
*/
function findFile($root, $path, $filename) {
$findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
$file = "{$cwd}/{$filename}";
if (file_exists($file)) return file_get_contents($file);
if ($cwd === $root) return false;
return $findFileAux(dirname($cwd), $filename);
};
return $findFileAux("{$root}/{$path}", $filename);
}
You might also want to consider using DIRECTORY_SEPARATOR instead of the hard-coded "/" so that this code could be used reliably on a variety of platforms.
I'm experimenting with vfsStream for unit testing filesystem interactions and have very quickly run into a major hurdle. One of the validation checks the code under test does is execute realpath() on a supplied input path to test that it's an actual path and not nonsense. However, realpath always fails on a vfsstream path.
The following code demonstrates the problem outside of any particular class.
$content = "It rubs the lotion on its skin or else it gets the hose again";
$url = vfsStream::url ('test/text.txt');
file_put_contents ($url, $content);
var_dump ($url);
var_dump (realpath ($url));
var_dump (file_get_contents ($url));
The output is as follows:
string(27) "vfs://FileClassMap/text.txt"
bool(false)
string(61) "It rubs the lotion on its skin or else it gets the hose again"
Obviously the vfsStream created the file and wrote the given content to it, but I can't verify that the path to it is correct with realpath. As realpath is being used inside the actual code I need a way of working about this.
I really don't think removing realpath is a sensible approach because it performs an important function inside the code, and eliminating an important check just to make the code testable seems a pretty poor solution. I could also put an if around the test to make it possible to disable it for testing purposes, but again I don't think that's a good idea either. Also I'd hate to have to do that at every point in the code where I might make a call to realpath (). The third option would be to set up a RAM disk for filesystem unit tests, but that's not ideal either. You have to clean up after yourself (which is what vfsstream is supposed to help you avoid the need for) and how to actually do it will differ from OS to OS, so the unit tests would cease to be OS agnostic.
So is there a way to get a vfsstream path in a format that actually works with realpath?
For completeness, the following is the code fragment from the class I'm trying to actually test.
if (($fullPath = realpath ($unvalidatedPath))
&& (is_file ($fullPath))
&& (is_writable ($fullPath))) {
A refactoring to the following (as per potential solution 2) allows me to test with vfsStream, but I think it could be problematic in production:
// If we can get a canonical path then do so (realpath can fail on URLs, stream wrappers, etc)
$fullPath = realpath ($unvalidatedPath);
if (false === $fullPath) {
$fullPath = $unvalidatedPath;
}
if ((is_file ($fullPath))
&& (is_writable ($fullPath))) {
If you use namespaces you can override the realpath function only in the test class. I always use canonical paths in my vfsStream testcases, beause i don't want to test the realpath() function itself.
namespace my\namespace;
/**
* Override realpath() in current namespace for testing
*
* #param string $path the file path
*
* #return string
*/
function realpath($path)
{
return $path;
}
Good described here: http://www.schmengler-se.de/en/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/
I opened a bug with an implementation of Sebkrueger's method on vfsStream: https://github.com/bovigo/vfsStream/issues/207
Waiting for their feedback, here is my working realpath():
/**
* This function overrides the native realpath($url) function, removing
* all the "..", ".", "///" of an url. Contrary to the native one,
*
* #param string $url
* #param string|bool The cleaned url or false if it doesn't exist
*/
function realpath(string $url)
{
preg_match("|^(\w+://)?(/)?(.*)$|", $url, $matches);
$protocol = $matches[1];
$root = $matches[2];
$rest = $matches[3];
$split = preg_split("|/|", $rest);
$cleaned = [];
foreach ($split as $item) {
if ($item === '.' || $item === '') {
// If it's a ./ then it's nothing (just that dir) so don't add/delete anything
} elseif ($item === '..') {
// Remove the last item added since .. negates it.
$removed = array_pop($cleaned);
} else {
$cleaned[] = $item;
}
}
$cleaned = $protocol.$root.implode('/', $cleaned);
return file_exists($cleaned) ? $cleaned : false;
}
Is it possible to remove a script that was previously added like this?
$this->view->headScript()->appendFile("/js/my.js");
The situation here is that in my Bootstrap.php several JavaScript files are appended like in the above example but in a particular controller I want one particular JavaScript file not to be loaded. Is there a way to remove it during the initiation of the controller?
I am looking for something like this.
$this->view->headScript()->removeFile("/js/my.js");
This works, but really isn't a good practice but rather for exceptional purposes. I recommend trying to not load the unwanted scripts in the first place.
It is possible to remove scripts subsequently with a function like this one.
/**
* Removes a previously appended script file.
*
* #param string $src The source path of the script file.
* #return boolean Returns TRUE, if the removal has been a success.
*/
public function removeScript($src) {
$headScriptContainer = Zend_View_Helper_Placeholder_Registry::getRegistry()
->getContainer("Zend_View_Helper_HeadScript");
$iter = $headScriptContainer->getIterator();
$success = FALSE;
foreach ($iter as $k => $value) {
if(strpos($value->attributes["src"], $src) !== FALSE) {
$iter->offsetUnset($k);
$success = TRUE;
}
}
Zend_View_Helper_Placeholder_Registry::getRegistry()
->setContainer("Zend_View_Helper_HeadScript",$headScriptContainer);
return $success;
}
Note that the strpos function is used here. This will remove every script that has $src in its path. Of course you can change that to your needs.
It's an old question, but here is an alternative:
I used the minifyHeadScript and changed his helper adding this custom function:
/**
* Removes all script file.
*
*/
public function clearAllScripts(){
$this->getContainer()->exchangeArray(array());
$container = $this->getContainer();
Zend_View_Helper_Placeholder_Registry::getRegistry()->setContainer("Zend_View_Helper_HeadScript", $container);
}
I'm finalizing a code segment that lists the files in a directory. I have no problems listing the files in a directory but for some reason I can get the isDot() method to work to make sure the file isn't a "." or ".." . The following below results in this error:
Fatal error: Call to undefined method SplFileInfo::isDot() in ....
Before I switched over to using the Recursive Iterator I was using the Directory Iterator and it worked fine. Is there anything wrong with the code below? It should work.
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pathToFolder));
//if there is a subdirectory it makes sure the proper extension is passed
foreach($files as $name => $file){
if (!$file->isDot()) { //this is where it shuts me down
$realfile = str_replace($pathToFolder, "", $file);
$url = getDownloadLink($folderID, $realfile);
$fileArray[] = $url;
}
}
This is, because DirectoryIterator::current() (the method, that is call within a foreach-loop) returns an object, which is itself of type DirectoryIterator. FileSystemIterator (that RecursiveDirectoryIterator extends) returns an object of SplFileInfo be default. You can influence, what is return, via flags
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$pathToFolder,
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_SELF));
But in your case, you don't need to test, if an item is a dot-file. Just set FilesystemIterator::SKIP_DOTS and they will not appear at all. Note, that this is also the default behavior.
The other answer is excellent, but for a different approach you can set the
SKIP_DOTS flag:
<?php
$o_dir = new RecursiveDirectoryIterator($pathToFolder);
$o_dir->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
$o_iter = new RecursiveIteratorIterator($o_dir);
foreach ($o_iter as $o_info) {
echo $o_info->getPathname(), "\n";
}
https://php.net/filesystemiterator.setflags