I am developing a PHP software that creates thumbnails from images.
Now I need to make sure that thumbnails are created successfully, in other words that the initial image has been resized/cropped correctly.
I think there's only one way to do this: I manually create the thumbnail to compare with the thumbnail created by the software.
But how to test?
If I use assertFileEquals() to compare the thumbnail created by me and the one created by the software, of course the test fails, even if the two images are identical.
I imagine that happening if only because the creation date of the two files is different, or for similar reasons.
So, how to do?
Storing a pair of source.png and expected_result.png (generated once by the software, verified as good and stored as the reference image) will suffice. Implementing a comparison function seems to be an overhead.
The main purpose of the unit tests is to signalize if system behavior changes, and that's what such test going to do if newly created thumbnail won't match with the reference one.
Yet, if for whichever reason software generates slightly different images every time, then, in case it's not a bug, use the suggested compare similar images approach.
What if image contents differ
In case of PNG files used in this example their contents might contain some auxiliary info such as EXIF.
So you might have to try creating a copy image without this additional info. Please verify if the following code works for you:
public function testThumbnails()
{
$this->assertPngImageContentsEquals(__DIR__ . '/test1.png', __DIR__ . '/test2.png');
}
public static function assertPngImageContentsEquals(
$expected,
$actual,
$message = 'Contents of PNG files differ'
)
{
self::assertFileExists($expected, $message);
self::assertFileExists($actual, $message);
$copy_expected = self::_makePngCopy($expected, __DIR__ . '/expected.png');
$copy_actual = self::_makePngCopy($actual, __DIR__ . '/actual.png');
var_dump($copy_expected);
var_dump($copy_actual);
self::assertFileEquals($copy_expected, $copy_actual, 'Thumbnails differ');
unlink($copy_expected);
unlink($copy_actual);
}
private static function _makePngCopy($sourceFile, $resultFile)
{
$image = imagecreatefrompng($sourceFile);
imagepng($image, $resultFile);
imagedestroy($image);
return $resultFile;
}
If assertFileEquals is failing, then there is something different between the two files. The internal code invokes file_get_contents on both files and asserts true if there are zero differences (so creation date is not part of the assertion).
Since you are manually creating the thumbnail, there must be slight differences. Instead you would need to do a "mostly the same" comparison, there are two questions related to coding this:
Compare 2 images in php
Similar images - how to compare them
Then decide how much difference is considered a pass. So you would do the "mostly the same comparison", then use asserts to determine if the answer of the "mostly the same" comparison falls within a range you can accept.
Update
I ran a quick test to ensure that assertFileEquals works properly on a binary file:
class FileEqualsTest extends PHPUnit_Framework_TestCase
{
public function test_yes_no_answer() {
file_put_contents("a.txt","\e[0m");
file_put_contents("b.txt","\e[0m");
file_put_contents("c.txt","\e[30m");
// straight get contents and comparisons
$contentsA = file_get_contents("a.txt");
$contentsB = file_get_contents("b.txt");
$this->assertEquals($contentsA, $contentsB);
$contentsC = file_get_contents("c.txt");
$this->assertNotEquals($contentsA, $contentsC);
// using file equals has same answer
$this->assertFileEquals("a.txt","b.txt");
$this->assertFileNotEquals("a.txt","c.txt");
}
..and it worked as expected on a very small scale. So it would seem there is some tiny difference in some way. You could try the options shown in the other questions above to see if there is a tiny difference, if that is important to your testing.
Related
I recently reworked the naming convention of some images on our website. When I uploaded the images with altered names, I ended up getting the images duplicated, one copy with the old naming convention and one with the new naming convention. The images numbered in the thousands and so I didn't want to manually delete them all.
So I decided that I needed to figure out a php script that would be capable of deleting the old images from the site. Luckily the old images were consistently named with either an ending of f.jpg or s.jpg. So all I had to do is find all the files with those endings and delete them. I thought it was a fairly straightforward thing, but for whatever reason the several different solutions I found listed online didn't work right. I ended up going back to some old code I had posted on Stackoverflow for a different purpose and reworked it for this. I'm posting that code as the answer to my problem in case it might be useful to anyone else.
Below is my solution to finding files matching a certain naming convention in a selected folder and its sub-folders and deleting them. To make it work for your situation. You'll want to place it above the directory that you want to delete, and you'll specify the specific folder by replacing the part where I have ./media/catalog/. You'll also want to replace the criteria I have selected, namely (substr($path, -5)=='f.jpg' || substr($path, -5)=='s.jpg'). Note that the 5 in the preceding code refers to how many letters are being matched in the criteria. If you wanted to simply match ".jpg" you would replace the 5 with a 4.
As always, when working with code that can effect a lot of files, be sure to make a backup in case the code doesn't work the way you expect it will.
<?php #stick ClearOldJpg.php above the folder you want to delete
function ClearOldJpg(){
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator("./media/catalog/"));
$files = iterator_to_array($iterator, true);
// iterate over the directory
foreach ($files as $path) {
if( is_file( $path ) && (substr($path, -5)=='f.jpg' || substr($path, -5)=='s.jpg')){
unlink($path);
echo "$path deleted<br/>";
}
}
}
$start = (float) array_sum(explode(' ',microtime()));
echo "*************** Deleting Selected Files ***************<br/>";
ClearOldJpg( );
$end = (float) array_sum(explode(' ',microtime()));
echo "<br/>------------------- Deleting selected files COMPLETED in:". sprintf("%.4f", ($end-$start))." seconds ------------------<br/>";
?>
One fun bonus of this code is that it will list the files being deleted and tell how long it took to run.
In my cache system, I want it where if a new page is requested, a check is made to see if a file exists and if it doesn't then a copy is stored on the server, If it does exist, then it must not be overwritten.
The problem I have is that I may be using functions designed to be slow.
This is part of my current implementation to save files:
if (!file_exists($filename)){$h=fopen($filename,"wb");if ($h){fwrite($h,$c);fclose($h);}}
This is part of my implementation to load files:
if (($m=#filemtime($file)) !== false){
if ($m >= filemtime("sitemodification.file")){
$outp=file_get_contents($file);
header("Content-length:".strlen($outp),true);echo $outp;flush();exit();
}
}
What I want to do is replace this with a better set of functions meant for performance and yet still achieve the same functionality. All caching files including sitemodification.file reside on a ramdisk. I added a flush before exit in hopes that content will be outputted faster.
I can't use direct memory addressing at this time because the file sizes to be stored are all different.
Is there a set of functions I can use that can execute the code I provided faster by at least a few milliseconds, especially the loading files code?
I'm trying to keep my time to first byte low.
First, prefer is_file to file_exists and use file_put_contents:
if ( !is_file($filename) ) {
file_put_contents($filename,$c);
}
Then, use the proper function for this kind of work, readfile:
if ( ($m = #filemtime($file)) !== false && $m >= filemtime('sitemodification.file')) {
header('Content-length:'.filesize($file));
readfile($file);
}
}
You should see a little improvement but keep in mind that file accesses are slow and you check three times for files access before sending any content.
I am given a lot of video files on my server folder, so adding all of them is impossible, I need to develop a php script that will read the contents of a folder and populate my database table with details such as filename, size, path, etc.
Furthermore I need to use that data for displaying the video list to users so everything must be accurate.
Please tell me how to?
I suggest you to use Iterators from SPL (Standart PHP Library) for an OOP aproach, for recursive iterations you should use DirectoryRecursiveIterator;
$directoryIterator = new RecursiveDirectoryIterator("/path/");
$recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
foreach ($recursiveIterator as $filename => $fileInfo) {
$filesize = $fileInfo->getSize();
$path = $fileInfo->getPath();
//insertDataIntoMysql($filename, $filesize, $filepath);
}
See here to see how to loop through a directory and get the filenames and file sizes. (Copied and edited here for simplicity)
$folder = '/my/path/to/dir';
foreach(glob($folder) as $file){
$size = filesize($file);
echo "Name=$file, size=$size<br />";
}
Instead of using echo to display the results you will simply use mysqli or PDO functions to run an INSERT query to insert into your table. Since this is not input coming from a user it is not so imperative to use prepare and bind but it is good practice to do so (and more efficient as well).
Then when you display the video list you will once again use mysqli or PDO functions to run a SELECT query and then echo these values onto your page in appropriate HTML syntax.
I'm guessing, based on your question, that some of these concepts might be new to you. If you do not know how to program in PHP or use SQL or create HTML pages then you are going to need to spend some serious time going through tutorials and watching youtube videos and reading books.
The Problem
I'm having an issue with the PHP function is_file().
Some preliminaries: I'm developing on Ubuntu 12.04, 32-bit, using PHP 5.5.10 and Apache 2.4.9.
I'm currently rewriting some working code to convert it to a library in Laravel (completed with a Facade and a ServiceProvider). I'm doing this mainly to clean up some code I wrote when I was young and foolish (about 6 months ago) and to implement unit testing. The library I'm writing provides methods for taking a contract (of which there are two distinct types, with more to come) and finding the path to a PDF document (the scanned paper contract). My methods for finding the path work fine and the tests are all passing.
In my old code, I used to do this:
/**
* Get a scanned contract and return it to the client
*
* #param string $type
* The contract type. Must be either static::CONTRACT1 or static::CONTRACT2.
*
* #param string $contract_id
* The contract ID
*
* #return Response
*/
public static function get($type, $contract_id)
{
// get the file name
//
$results = static::getFileName($type, $contract_id);
// did we find a file? if not, throw a ScannedContractNotFoundException
//
if(!$results)
throw new \MyApplication\Exceptions\ScannedContractNotFoundException("No file found for $type contract $contract_id");
// get the path and file name
//
$path = $results['path'];
$name = $results['name'];
// get the full path
//
$file = $path.$name;
// get the file size
//
$contents = file_get_contents($file);
$fsize = strlen($contents);
// push the file to the client
//
header("Content-type: application/pdf");
header("Content-Disposition: inline; filename=\"".$name."\"");
header("Content-length: $fsize");
header("Cache-control: private");
echo $contents;
exit;
}
And it worked just fine.
Now I'm trying to rewrite it to get rid of the echo and move the code that actually does the work of sending the file to a controller. That code will look like this:
$x = \MyApplication\Models\Contract1::find($id);
$file = \ScannedContracts::getFileName($x);
$path = $file["path"].$file["name"];
return \Response::download($path, $file["name"]);
However, this code is throwing a FileNotFoundException. The code where the exception is being thrown looks like this:
public function __construct($path, $checkPath = true)
{
if ($checkPath && !is_file($path)) {
throw new FileNotFoundException($path);
}
...
Clearly the problem is with the if statement, and in particular the call to is_file().
I have written a little script to test this with a path which is known to be good and is_file() returns false.
When I copy a file to my "public" folder, it works fine.
In the documentation for the is_file() function there is a comment stating that the permissions for the parent folder must be +x. I've examined the permissions, and the folder is world-executable, as is the parent, and the grand-parent, and the great-grand-parent, etc.
There are two possible confounding factors: first, the files I'm working with are located on a CIFS/Samba share. I should mention that the paths in question are absolute paths to the mounted share.
The closest issue to this I've found on SO is PHP is_file returns false (incorrectly) for Windows share on Ubuntu, but that doesn't have a resolution. I've also searched for PHP bug reports, but there's nothing.
Second, some of the paths contain spaces. I've tried escaping them every way I can think of, but it doesn't help.
In the absence of a solution, I'll have to do it the old fashioned way, but I really wanted to do it using the functions Laravel provides.
Questions
Do I need to escape spaces in a path passed to is_file()?
Does anyone know of a fix or a workaround that doesn't a) require changing code in a 3rd party library, or b) require wholesale changes to permissions or other configuration on the CIFS/Samba server?
Thanks in advance!
I think you need to sanitize filenames when upload to directory
function sanitize_file_name( $str ) {
return preg_replace("/[^a-z0-9\.]/", "", strtolower($str));
}
I have two folders, in one i have the videos and in the second one the configuration files for each video(3 files per video). Now if i want to delete a video i have to delete files by hand.
I found this :
<?php
$filename = 'name.of.the.video.xml';
$term = str_replace(".xml","", $filename);
$dirPath = ("D:/test/");
foreach (glob($dirPath.$term.".*") as $removeFile)
{
unlink ($removeFile);
}
?>
A echo will return:
D:/test/name.of.the.video.jpg
D:/test/name.of.the.video.srt
D:/test/name.of.the.video.xml
Is ok and it help me a lot, but i have a problem here.
Not all files are the same ex:
Name.of.The.video.jpg
Name.Of.The.Video.xml
If i echo the folder looking for that string and is not identic with the $filename will return empty.
So, my question is, how can i make that search Case insensitive?
Thank you.
You are making use of the glob function which is case sensitive. You are using the wrong function therefore to get the list of files.
You should therefore first normalize the filenames in the directory so they all share the same case (e.g. all lowercase). Or you need to use another method to get the directory listing case-insensitive. I suggest the first, however if that is not an option, why don't you glob for all files first and then filter the list of files using preg_grep which allows to specify patterns that are case-insensitive?
Which leads me to the point that it's more practicable to use DirectoryIterator with a RegexIterator:
$filename = 'name.of.the.video.xml';
$term = basename($filename, ".xml");
$files = new DirectoryIterator($dirPath);
$filesFiltered = new RegexIterator($files, sprintf('(^%s\\..*$)i', preg_quote($term)));
foreach($filesFiltered as $file)
{
printf("delete: %s\n", $file);
unlink($file->getPathname());
}
A good example of the flexibility of the Iterators code are your changed requirements: Do that for two directories at once. You just create two DirectoryIterators and append the one to the other with an AppendIterator. Job done. The rest of the code stays the same:
...
$files = new AppendIterator();
$files->append(new DirectoryIterator($dirPath1));
$files->append(new DirectoryIterator($dirPath2));
...
Voilá. Sounds good? glob is okay for some quick jobs that need just it. For everything else with directory operations start to consider the SPL. It has much more power.
Is strcasecmp() a valid function for this? Its a case insensitive str comparison function?
Surely if you know the file name and you can echo it out, you can pass this to unlink()?