forked from caefer/sfImageTransformExtraPlugin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
608 lines (440 loc) · 26.8 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# Image manipulation made even easier - sfImageTransformExtraPlugin
![sfImageTransformExtraPlugin](http://www.symfony-project.org/uploads/plugins/4366ffec3f32548c30e31094c3dcbcea.png)
## Table of Contents
* [About](#about)
* [Installation](#installation)
* [Configuring the image formats you need](#formats)
* [Configuring the location of the original image](#sources)
* [Your source image is a local file referenced by the filename](#file-source)
* [Your source image is a local file which location is stored in the database (Doctrine and Propel)](#database-source)
* [Your source image is a remote file accessed over HTTP](#http-source)
* [Your source image is different - write your own source class](#custom-source)
* [Caching the generated images](#caching)
* [Verifying your cache settings](#verify-caching)
* [Invalidating the cached images manually](#manual-invalidation)
* [Invalidating the cached images automatically](#auto-invalidation)
* [Demonstration](#demo)
* [How does it work?](#internals)
- - -
## <a name="about">About</a>
On a website you ususally find lots of images and a set of formats.
For example let's say a user avatar is always ``80x80 PNG`` while a homepage top image is always ``320x140 JPG`` with
round corners.
As it is far too costly to prepare all these different formats by hand there are automated ways to generate them from
source images uploaded by a user. One of the best tools for this in the symfony world is
[sfImageTransformPlugin](http://www.symfony-project.org/plugins/sfImageTransformPlugin) which enables you to perform
many sophisticated transformations on your images such as resizing, color manipulation, overlays and much much more. It
is not only easy to use but easy to extend as well. Writing your own transformation is a bliss.
Using such an automatism means you have to write code and perform all necessary transformation - usually on upload, no
matter if the generated files are ever requested. It also means that design changes that change the formats as well lead
to a change of business logic rather than just templates.
This is where sfImageTransformExtraPlugin springs into action as it provides a way to configure formats with multiple
transformations.
In your templates you only refer to the format by name which results in an SEO friendly image URL (a URL that you have
full control over). The image itself will be generated on first request and (in production environments) written to the
filesystem.
Here are some of the key features:
* Configure image transformation for your thumbnail formats
* Format changes without the need to change code
* Unobstrusive implementation (No need to write code)
* Generating images on request
* Can be run as a web service for a content delivery network (CDN)
* Supporting image file sources stored in databases via Doctrine or Propel, remote files and more
* Full control over the thumbnail URLs and therefor easy to adapt to your SEO requirements
* Generated API documentation
* Unit tested
* Easy to extend
To get an idea what you can do you might want to check out a quick [demonstration](#demo) or see [how it works internally](#internals).
## <a name="installation">Installation</a>
To install the plugin for a symfony project, the usual process is to use the symfony command line:
$ php symfony plugin:install sfImageTransformExtraPlugin
Alternatively, if you don't have PEAR installed, you can download the latest package attached to this plugin's wiki page
and extract it under your project's ``plugins/`` directory.
Activate the plugin in your ``ProjectConfiguration.class.php``.
// /config/ProjectConfiguration.class.php
...
$this->enablePlugins(..., 'sfImageTransformPlugin', 'sfImageTransformExtraPlugin');
...
Enable the generating module in your ``settings.yml``.
// /apps/yourapplication/config/settings.yml
all:
.settings:
enabled_modules: [ ..., sfImageTransformator ]
...
You also need to configure automatic mime detection for sfImageTransformPlugin in your applications ``app.yml``.
// /apps/yourapplication/config/app.yml
all:
sfImageTransformPlugin:
mime_type:
auto_detect: true
library: gd_mime_type # gd_mime_type (GD), Fileinfo (PECL), MIME_Type (PEAR)
font_dir: %SF_PLUGINS_DIR%/sfImageTransformExtraPlugin/data/example-resources/fonts
...
Automatic mime detection is absolutely necessary!
Of course you can point the ``font_dir`` to your own location containing True Type Fonts.
Clear the cache to enable the autoloading to find the new classes:
$ php symfony cc
> Note: The plugin requires sfImageTransformPlugin to be installed as well. The dependencies described there apply as
well so please follow the [README](http://www.symfony-project.org/plugins/sfImageTransformPlugin?tab=plugin_readme
"sfImageTransformPlugin README").
The default routes of the plugin expect all thumbails to be called from the relative URL ``/thumbnails/..``. So you have
to make sure, that this folder (``SF_ROOT_DIR/web/thumbnails``) exists and is writable to the web server.
$ mkdir SF_ROOT_DIR/web/thumbnails
$ chmod 777 SF_ROOT_DIR/web/thumbnails
The ``/thumbnails`` path is specified in your routes. If you make changes to the routes you have to make sure that the
equivalent path exists and is writable.
To ensure that the caching of your generated files works please also have a look at [verifying your cache settings](#verify-caching).
## <a name="formats">Configuring the image formats you need</a>
The best way to learn how you configure your format transformations is to walk through a rather complex format configuration.
Of course you can chain transformations. In fact most of the above thumbnails have two transformations applied one for resizing and another for the effect.
Let's see how it works and start with the original again. The format looks like this.
star0:
quality: 25
mime_type: image/png
transformations: ~
With the following result.
![star0](http://stat.ical.ly/thumbnails/star0.png "No Transformation")
Now let's add a ``crop`` transformation to get to the correct dimensions.
star1:
quality: 25
mime_type: image/png
transformations:
- { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
![star1](http://stat.ical.ly/thumbnails/star1.png "Just cropped")
And just to be a bit different we want to rotate.
star2:
quality: 25
mime_type: image/png
transformations:
- { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
- { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
![star2](http://stat.ical.ly/thumbnails/star2.png "Now it's rotated as well")
The blank spots are not what we want so let's crop it again.
star3:
quality: 25
mime_type: image/png
transformations:
- { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
- { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
- { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
![star3](http://stat.ical.ly/thumbnails/star3.png "Cropped again")
So we are back to the dimensions we wanted. Now we want a watermark on top of it.
star4:
quality: 25
mime_type: image/png
transformations:
- { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
- { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
- { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
- { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/logo.png, position: center }}
![star4](http://stat.ical.ly/thumbnails/star4.png "Watermarked")
Of course this is not "Web 2.0" enough yet..
star5:
quality: 25
mime_type: image/png
transformations:
- { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
- { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
- { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
- { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/logo.png, position: center }}
- { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/star_frame.png, position:
center }}
![star5](http://stat.ical.ly/thumbnails/star5.png "Star frame added")
Now we want to get rid of the bits that stick out by applying an alpha mask.
star6:
quality: 25
mime_type: image/png
transformations:
- { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
- { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
- { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
- { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/logo.png, position: center }}
- { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/star_frame.png, position:
center }}
- { adapter: GD, transformation: alphaMask, param: { mask: sfImage|masks/star_mask.gif }}
![star6](http://stat.ical.ly/thumbnails/star6.png "Stamped out")
Done! ;)
> Please note that for overlay and alphaMask transformations
> you need to prefix the resource path with "sfImage|" as this
> automatically converts the path into an sfImage object.
## <a name="sources">Configuring the location of the original image</a>
sfImageTransformExtraPlugins uses custom PHP stream wrappers to map your images URLs to the real location of the source images. There are a few sources already available but of course you can easily add your own.
### <a name="file-source">Your source image is a local file referenced by the filename</a>
Your image sources lie in a directory accessible to your web server and you want to keep the filenames.
Create a route like the following in your applications ``routing.yml``.
// /apps/yourapplication/config/routing.yml
sf_image:
class: sfImageTransformRoute
url: /thumbnails/:format/:filepath.:sf_format
param: { module: sfImageTransformator, action: index }
requirements:
format: '[\w_-]+'
filepath: '[\w/]+'
sf_format: 'gif|png|jpg'
sf_method: [ get ]
options:
image_source: File
...
You can now generate ``<img />`` tags to these images like this.
<?php
echo image_tag(url_for('sf_image_file', array('format' => 'pixelate', 'filepath' => 'logo.png')));
// resulting in: http://localhost/thumbnails/pixelate/logo.png.jpg
?>
> Please note that the prefix filepath - ``/thumbnails`` in the above example - is required to exist and be writable for
the webserver!
### <a name="database-source">Your source image is a local file which location is stored in the database (Doctrine and Propel)</a>
When you develop on symfony chances are that uploaded image files are located in the ``sf_upload_dir`` and their
filenames are stored in the database. sfImageTransformExtraPlugin comes with two images source classes that you can use
to work with these files.
Your image sources are located in a directory accessible to your web server and you want to keep the filenames.
Create a route like the following in your applications ``routing.yml``.
// /apps/yourapplication/config/routing.yml
sf_image:
class: sfImageTransformRoute
url: /thumbnails/:type/:format/:path/:slug-:id.:sf_format
param: { module: sfImageTransformator, action: index, attribute: file }
requirements:
format: '[\w_-]+'
path: '[\w/]+'
slug: '[\w_-]+'
id: '\d+(?:,\d+)?'
sf_format: 'gif|png|jpg'
sf_method: [ get ]
options:
image_source: Doctrine # or Propel
segment_separators: [ '/', '.', '-' ]
...
You can now generate ``<img />`` tags to these images like this.
<?php
echo image_tag(url_for('sf_image_doctrine', array('format' => 'pixelate', 'sf_subject' => $record)));
// resulting in: http://localhost/thumbnails/News/pixelate/01/00/00/mytest-1.jpg
?>
``$record`` in this example is either a Doctrine or Propel record.
The ``:path`` attribute is provided by the sfImageTransformExtraPlugins own route class ``sfImageTransformRoute``. It
will take the ``id`` attribute and form a three level deep path from it.
// examples
1 => 01/00/00
37410 = 10/74/03
..
This path can ensure that you won't store more than 100 files per directory which is necessary to avoid imperformance
from the filesystem.
### <a name="http-source">Your source image is a remote file accessed over HTTP</a>
If you plan to build a content delivery network (CDN) and run sfImageTransformExtraPlugin as a webservice your image
sources will not be available locally but instead over HTTP.
Your image sources are located in a directory not accessible to your web server and you want to keep the filenames.
Create a route like the following in your applications ``routing.yml``.
// /apps/yourapplication/config/routing.yml
sf_image:
class: sfImageTransformRoute
url: /thumbnails/sfweb/:format/:filepath.:sf_format
param: { module: sfImageTransformator, action: index, protocol: http, domain: www.symfony-project.org }
requirements:
format: '[\w_-]+'
protocol: 'http|https'
domain: '[\w-_.]+'
filepath: '[\w/-_.]+'
sf_format: 'gif|png|jpg'
sf_method: [ get ]
options:
image_source: HTTP
...
You can now generate ``<img />`` tags to these images like this.
<?php
echo image_tag(url_for('sf_image_http', array('format' => 'pixelate', 'filepath' =>
'images/symfony-reloaded.png')));
// resulting in: http://localhost/thumbnails/sfweb/pixelate/images/symfony-reloaded.png.jpg
?>
### <a name="custom-source">Your source image is different - write your own source class</a>
It doesn't matter where your source images are stored and how you need to receive the filepath/URL you can easily
implement a solution by following two easy steps:
1. Write a new route
When defining the URL of the new route the only thing you need to care about is the list of parameters. You have to
provide all parameters that are necessary to retrieve the images location.
In the case of a Doctrine model storing the filename of a local file you need to know the model type (i.e.
NewsBulletin), the ID of the object (i.e. 123) and the model attribute or field which stores the filename (i.e. file).
Depending on your requirements you might need different parameters to identify the source.
You will also most certainly need a parameter for the desired format.
sf_image:
class: sfImageTransformRoute
ulr: /thumbnails/:format/:special.sf_format
param: { module: sfImageTransformator, action: index }
requirements:
format: '[\w_-]+'
sf_format: http|https
somespecialparameter: '[\w-_.]+'
sf_method: [ get ]
options:
image_source: Special
2. Write a new sfImageSource class
Once you defined the parameters you can implement the sfImageSource class by implementing ``sfImageSourceInterface`` and
``barfoo``.
If the image source files are stored on the local filesystem accessible to the webserver your need to implement
``sfImageSourceLocal``. For all other locations please use ``sfImageSourceRemote`` as PHP functions like ``stat()`` only
work with local files and therefor need to be faked .
class sfImageSourceSpecial extends sfImageSourceRemote implements sfImageSourceInterface
{
/**
* Returns an sfImageSource:// URL specific to the implementing stream wrapper
*
* @param array $parameters Current request parameters
* @return string sfImageSource:// URI
* @throws InvalidArgumentException
*/
public static function buildURIfromParameters(array $parameters)
{
...
}
/**
* Translates the given stream URL to the abolute path of the source image
*
* @param string $path The given stream URL
* @return string
*/
private function translatePathToFilename($path)
{
...
}
}
In most cases you only have to change/adapt the methods in the above example. That is all you have to do.
Please make sure that the base URL (in the above example ``/thumbnails``) exists and is writable. See [verifying your cache settings](#verify-caching) for details.
## <a name="caching">Caching the generated images</a>
sfImageTransformExtraPlugin provides functionality to cache your generated images so that instead of processing the images from scratch per request they get stored on the local filesystem from where your webserver can serve them statically without even spawning a PHP process.
### <a name="verify-caching">Verifying your settings</a>
sfImageTransformExtraPlugin provides a symfony task to check your settings to ensure that caching is working properly.
$ ./symfony help transforms:check-caching
Usage:
symfony transforms:check-caching [--env="..."] [--route-name="..."] application
Arguments:
application The application name
Options:
--env The environment (default: prod)
--route-name The sfImageTransform routename (default: sf_image)
Description:
The transforms:check-caching task performs a series of tests on your project to verify the thumbnail caching to work.
Call it with:
php symfony transforms:check-caching application
Please read the output carefully especially if one or more checks fail.
You can also run the tests for a specific environment by providing the env option. It defaults to prod which in most cases is the only environment you want your cache to be enabled.
php symfony transforms:check-caching application --env=prod
The tasks assumes the default route name sf_image for your thumbnails. If you use a different one you can specify it with the route-name option.
php symfony transforms:check-caching application --route-name=your_thumbnail_route
Please note that the permission checks can not be reliable as they are performed with the system permissions of your current user account while your web server should run with a different user account which might have different priviledges.
### <a name="manual-invalidation">Invalidating the cached images manually</a>
Sometimes you want to remove already generated images from your filesystem i.e. if the original source image was
changed.
For this there is a symfony task which takes two mandatory arguments.
$ ./symfony help transforms:remove
Usage:
symfony transforms:remove application route
Aliases: remove-thumbnails
Arguments:
application The application name
route The sf_image route that generated the image(s) you want to remove
Description:
Removes thumbnails generated by sfImageTransformExtraPlugin.
Let's take a route from the previous examples.
// /apps/yourapplication/config/routing.yml
sf_image:
class: sfImageTransformRoute
url: /thumbnails/:format/:filepath.:sf_format
param: { module: sfImageTransformator, action: index }
requirements:
format: '[\w_-]+'
filepath: '[\w/]+'
sf_format: 'gif|png|jpg'
sf_method: [ get ]
options:
image_source: File
Assuming that this route is active for your frontend application you can now do the following to remove all images
generated by that route.
$ ./symfony transforms:remove frontend sf_image_file
Do you really want to delete all generated images?
y
>> files- Generated images removed.
To be more specific you can provide all attributes available for the route.
To remove only images for this route that used the ``default`` format:
$ ./symfony transforms:remove frontend sf_image_file --format=default
>> files- Generated images removed.
To remove only images for this route that are of type ``jpg``:
$ ./symfony transforms:remove frontend sf_image_file --sf_format=jpg
>> files- Generated images removed.
Of course you can combine all available attributes. In this case to remove all ``jpg`` images that were generated using
the ``default`` format (for example if you changed the mimetype of that particular format).
$ ./symfony transforms:remove frontend sf_image_file --format=default --sf_format=jpg
>> files- Generated images removed.
### <a name="auto-invalidation">Invalidating the cached images automatically</a>
**t.b.d.**
## <a name="demo">Demonstration</a>
Here are a few examples to see what is possible.
Consider the following source image.
![original](http://stat.ical.ly/thumbnails/original.jpg "No Transformation")
Here are just a few examples that were all transformed from the above image.
![resize](http://stat.ical.ly/thumbnails/resize.jpg "Resize Transformation")
![fit](http://stat.ical.ly/thumbnails/fit.jpg "Thumbnail Fit Transformation")
![crop](http://stat.ical.ly/thumbnails/crop.jpg "Crop Transformation")
![flip](http://stat.ical.ly/thumbnails/flip.jpg "Flip Transformation")
![rotate](http://stat.ical.ly/thumbnails/rotate.jpg "Rotate Transformation")
![mirror](http://stat.ical.ly/thumbnails/mirror.jpg "Mirror Transformation")
![border](http://stat.ical.ly/thumbnails/border.jpg "Border Transformation")
![line](http://stat.ical.ly/thumbnails/line.jpg "Line Transformation")
![rectangle](http://stat.ical.ly/thumbnails/rectangle.jpg "Rectangle Transformation")
![arc](http://stat.ical.ly/thumbnails/arc.jpg "Arc Transformation")
![ellipse](http://stat.ical.ly/thumbnails/ellipse.jpg "Ellipse Transformation")
![overlay](http://stat.ical.ly/thumbnails/overlay.jpg "Overlay Transformation")
![colorize](http://stat.ical.ly/thumbnails/colorize.jpg "Colorize Transformation")
![contrast](http://stat.ical.ly/thumbnails/contrast.jpg "Contrast Transformation")
![edgeDetect](http://stat.ical.ly/thumbnails/edgeDetect.jpg "Edge Detect Transformation")
![emboss](http://stat.ical.ly/thumbnails/emboss.jpg "Emboss Transformation")
![greyscale](http://stat.ical.ly/thumbnails/greyscale.jpg "Greyscale Transformation")
![negate](http://stat.ical.ly/thumbnails/negate.jpg "Negate Transformation")
![opacity](http://stat.ical.ly/thumbnails/opacity.jpg "Opacity Transformation")
![noise](http://stat.ical.ly/thumbnails/noise.jpg "Noise Transformation")
![blur](http://stat.ical.ly/thumbnails/blur.jpg "Blur Transformation")
![pixelize](http://stat.ical.ly/thumbnails/pixelize.jpg "Pixelize Transformation")
![scatter](http://stat.ical.ly/thumbnails/scatter.jpg "Scatter Transformation")
![sketchy](http://stat.ical.ly/thumbnails/sketchy.jpg "Sketchy Transformation")
![text](http://stat.ical.ly/thumbnails/text.jpg "Text Transformation")
![rounded_corners](http://stat.ical.ly/thumbnails/rounded_corners.gif "Rounded Corners Transformation")
![reflection](http://stat.ical.ly/thumbnails/reflection.jpg "Reflection Transformation")
![frame](http://stat.ical.ly/thumbnails/frame.png "Alpha Mask (Frame) Transformation")
![pattern](http://stat.ical.ly/thumbnails/pattern.png "Alpha Mask (Pattern) Transformation")
![just](http://stat.ical.ly/thumbnails/just.gif "Create & Text Transformation")
And of course a simple scaling:
![scale](http://stat.ical.ly/thumbnails/scale.jpg "Thumbnail Scale Transformation")
## <a name="internals">How does it work?</a>
The generation process that we designed so far works like this:
1. An HTTP Request is made to a thumbnail
2. All necessary parameters are parsed from the URL
3. The requested source image is found
4. The thumbnail gets generated according to the format specification
5. The thumbnail gets cached (saved to disk)
6. The thumbnail is send out to the user
![Thumbnail request lifecycle](http://yuml.me/54355ffc)
The thumbnail image is generated on the first request. All succeeding requests are coming from cache (per default the
filesystem) and are served by Apache without spawning a PHP process.
Image generation is quite expansive in terms of CPU and memory usage. This is why sfsfImageTransformExtraPlugin by
default stores generated images for the production environment on the filesystem.
For this the custom cache class ``sfRawFileCache`` is used which is basically a copy of sfFileCache but does not prepend
the cached file with expire time information but instead saves only the generated image. It also maintains the real
filename and does not add a ``.cache`` suffix.
You can see the typical lifecycle of a generated image in the following Firebug screenshots.
![firebug-1-generation](http://stat.ical.ly/thumbnails/firebug-1-generation.jpg "First request on a thumbnail triggering
the generation and caching")
The image is generated, saved to the filesystem and sent to the requesting browser.
![firebug-2-static](http://stat.ical.ly/thumbnails/firebug-2-static.jpg "Second request on a thumbnail already directly
served from filesystem")
The image is read directly from the filesystem without invoking a PHP process.
![firebug-3-notmodified](http://stat.ical.ly/thumbnails/firebug-3-notmodified.jpg "Third and succeeding requests on a
thumbnail will be served from the browser cache")
Apache automatically informs the browser that the image has not been modified by sending a ``304`` status (depending on
your Apache configuration, however the described behaviour is the default).
As you can see the time needed to deliver a generated image after the second request is drastically reduced.
(The times will vary on different installations.)
The deletion of generated images use the sfCache interface and can be triggered by a task of a symfony event.
- - -
sfImageTransformExtraPlugin is fully unit tested with PHPUnit. You can run the test suite like this.
$ cd /path/to/sfImageTransformExtraPlugin
$ phpunit --tap test/sfImageTransformExtraPluginsTests.php
sfImageTransformExtraPlugin has a (quite) complete API documentation using PHPdocumentor. You can generate the API documentation like this.
$ cd /path/to/sfImageTransformExtraPlugin
$ phpdoc -o HTML:frames:DOM/phphtmllib -d . -t /path/to/where/you/want/the/apidocs/to/be/generated