<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Dekompozice &#8211; Medio Blog</title>
	<atom:link href="https://blog.medio.cz/stitky/dekompozice/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.medio.cz</link>
	<description></description>
	<lastBuildDate>Thu, 19 Feb 2015 16:49:51 +0000</lastBuildDate>
	<language>cs</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>
	<item>
		<title>Jak zobrazujeme obrázky</title>
		<link>https://blog.medio.cz/jak-zobrazujeme-obrazky</link>
					<comments>https://blog.medio.cz/jak-zobrazujeme-obrazky#comments</comments>
		
		<dc:creator><![CDATA[Matěj Humpál]]></dc:creator>
		<pubDate>Wed, 05 Nov 2014 07:00:02 +0000</pubDate>
				<category><![CDATA[Programování a vývoj]]></category>
		<category><![CDATA[Dekompozice]]></category>
		<category><![CDATA[Dependency Injection]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://blog.medio.cz/?p=2304</guid>

					<description><![CDATA[V předchozím článku jsme si ukázali, jak ukládáme soubory obrázků na úložiště a mapujeme je na entity. To bylo jednoduché. Mnohem zajímavější je, jak obrázky zobrazit v aplikaci navenek.]]></description>
										<content:encoded><![CDATA[<p>V <a href="https://blog.medio.cz/jak-ukladame-obrazky">předchozím článku</a> jsme si ukázali, jak v <a href="http://www.medio.cz/">Mediu</a> ukládáme soubory obrázků na úložiště a mapujeme je na entity. To bylo jednoduché. Mnohem zajímavější je, jak obrázky zobrazit v aplikaci navenek.</p>
<p>Těžko budete nutit klienta, aby každý obrázek před nahráním zmenšil a ořízl na velikost odpovídající zobrazení na webu. Už vůbec ne na všechny velikosti, které na webu používá. A co když přidáte další zobrazení? Nebo se změní stávající velikosti kvůli redesignu? Zmenšovat obrázky jen přes HTML je pak neuctivé k času načítání stránky a majitelům mobilních FUP. Jak elegantně to děláme u nás?</p>
<p>Malá lákací ukázka: zobrazení obrázku na <a href="https://www.ticketon.cz/">titulce jednoho našeho projektu</a> vypadá v kódu takto:</p><pre class="crayon-plain-tag">{$performance-&gt;getImage() |thumbnail:large |imagehtml, $performance-&gt;getDescription()}</pre><p>Ano, správně, je to zápis v šablonovacím jazyce <a href="http://latte.nette.org">Latte</a>. Getter <code>$performance-&gt;getImage()</code> vrací, tadá, instanci třídy <code>Image</code>, kterou jsme si popsali v <a href="https://blog.medio.cz/jak-ukladame-obrazky">předchozím článku</a>. Ta se předá šablonovacímu filtru (v dřívějších verzích Nette se filtrům říkalo helpery) <code>thumbnail</code>, spolu s klíčem, který obsahuje informaci o tom, jaké změny před zobrazením na obrázku použít. Ten vrátí cestu k souboru na disku a předá ji filtru <code>imagehtml</code>, který cestu zkrátí na URL, vytvoří HTML tag<code><img alt="" /></code>, cestu mu nastaví do atributu <code>src</code>, a popisek z <code>$performance-&gt;getDescription()</code> do atributu <code>alt</code>. Elegantněji už to snad ani nejde.</p>
<p>Rozeberme si to teď trochu podrobněji. Filtr <code>thumbnail</code> volá třídu <code>ThumbnailGenerator</code>. Ta krom instance <code>IFileRepository</code> a instance <code>IImageFileNamingStrategy</code> dostane pole klíčů jednotlivých velikostí obrázků používaných v dané aplikaci. Třeba z konfigurace.</p>
<div class="bulb"><code>ThumbnailGenerator</code> by měl obdržet sice instanci stejné třídy <code>FilesystemRepository</code>, ale s jiným <code>$rootDir</code>, než je nastaven v instanci pro originály obrázků. Originály chceme typicky ukládat někde mimo document root, zatímco zmenšeniny by měly být z internetu dostupné.</div>
<p></p><pre class="crayon-plain-tag">imageSizes:
	square:
		width: 150
		height: 150
		flag: crop
	large:
		width: 960
		height: 960
		flag: fit</pre><p>Atributy <code>width</code> a <code>height</code> jsou jasné, vlajky <code>flag</code> používáme tři: <code>fit</code> vsouká obrázek do daných rozměrů a nikdy jej nezvětšuje, <code>crop</code> jej na ně ořízne. Pofiderní vlajka <code>enlarge</code> funguje jako <code>fit</code>, ale vsouká obrázek na dané rozměry, i kdyby byl sebemenší.</p>
<p>Klíče nám slouží jen pro pohodlnější určení varianty zmenšení a předávání parametru v šabloně (viz poznámky). Klidně bychom mohli předávat přímo trojici argumentů, ale takto alespoň víme, co se všechno v aplikaci používá, máme to přehledně na jednom místě a bráníme divokým vývojářům v přílišném rozletu.</p>
<p>Pak se nám bude hodit metoda <code>getThumbnailName()</code>, o kterou obohatíme rozhraní <code>IImageFileNamingStrategy</code>, potažmo v našem konkrétním případě jeho implementaci <code>HashFileNamingStrategy</code>. Ta bude přes <code>ThumbnailGenerator</code> vracet jméno souboru zmenšeniny obrázku, podle cesty k originálu a zadaných parametrů. Parametry jsme podle klíče z šablony našli v konfiguraci.</p><pre class="crayon-plain-tag">public function getThumbnailKey($path, $width, $height, $flag)
{
	// rozparsujeme jméno původního souboru pomocnou metodou
	$infoFromOriginalPath = $this-&gt;getInfoFromOriginalPath($path);
	
	$imageId = $infoFromOriginalPath['id'];
	$md5 = $infoFromOriginalPath['hash'];

	$sizes = $width . 'x' . $height . '-' . $flag;

	$key = $md5
			. '-' . $imageId
			. '-' . $sizes
			. '.'
			. strtolower(pathinfo($path, PATHINFO_EXTENSION));
	return $key;
}</pre><p>Tím dostaneme z cesty k souboru, který jsme si uložili, a klíče <code>large</code> jméno souboru.</p><pre class="crayon-plain-tag">2f/25/2f2555d95ee03f950c9ddae2f1692c55-42-960x960-fit.jpg</pre><p>Ten předáme šabloně a ta ho (po úpravách filesystémové cesty na korektní URL s cestou k obrázkům) vypíše na výstup. A máme hotovo.</p>
<p>&#8230;cože? Že jsme nikde nevytvořili samotnou zmenšeninu obrázku? Správně, nevytvořili. Starat se o vytváření všech zmenšenin při uploadu je zbytečně náročné. Navíc některým nahraným obrázkům se nikdy nemusejí vytvořit všechny možné velikosti. A znovu &#8211; co když variantu přidáme? Museli bychom projít všechny originály a variantu jim vytvořit ad hoc.</p>
<p>Ne, vytvoření raději necháme na aplikaci spolu s chytrým rewritem a/nebo routou.</p>
<h2>Jak obrázkům vytváříme zmenšeniny</h2>
<p>Pokud už soubor s požadovaným obrázkem na disku existuje, vrátí ho webový server rovnou. Nespouští se vůbec žádné PHP s nějakým Nette a vším tím overheadem okolo. Prostě se ze serveru vydá statický soubor.</p>
<p>Co se ale stane, když se uživatel pokusí přistoupit ke zmenšenině obrázku, která ještě na serveru není? Využijeme teď toho, že se frameworku předávají všechny požadavky na přístup k cestám, které na serveru fyzicky neexistují.</p>
<p>Zásadní technikou zpracování požadavků frameworkem je přesměrování všech potřebných volání na jednotný vstupní bod – v případě Nette je to soubor <code>index.php</code>. Ten pak už požadavky zpracuje interně.</p><pre class="crayon-plain-tag">$this[] = new Route('/&lt;? [a-z0-9]{2}&gt;/&lt;? [a-z0-9]{2}&gt;/&lt;key (.*)&gt;', array(
	'module' =&gt; 'Front',
	'presenter' =&gt; 'ImageService',
	'action' =&gt; 'default',
));</pre><p>Toto je routa Nette Frameworku, která požadavky na neexistující zmenšeniny posílá speciálnímu presenteru <code>ImageServicePresenter</code>, který:</p>
<ul>
<li>metodou v <code>HashFileNamingStrategy</code> z parametru <code>$key</code> zrekonstruuje informace o zmenšenině,</li>
<li>zkontroluje, jestli je kombinace vlastností zmenšeniny platná, tedy dostupná v nastavení (určitě bychom nechtěli, aby nám nějaký vtipálek pustil generování všech možných kombinací velikostí a vlajek),</li>
<li>zkontroluje, jestli existuje entita, pro kterou má zmenšeninu vytvořit,</li>
<li>pro jistotu ověří existenci originálního souboru,</li>
<li>vytvoří zmenšeninu podle zadaných kritérií, <strong>uloží ji na disk</strong> a vrátí její data (aby je uživatel viděl přímo ve svém požadavku).</li>
</ul>
<p>Pokud jakákoliv z kontrol neprojde, vrací požadavek chybu <code>404</code>.</p>
<p>Při dalším pokusu o přístup k té samé zmenšenině už server vrátí rovnou její statickou variantu, protože soubor fyzicky existuje.</p>
<div class="bulb">Na začátku routy pro zjednodušení chybí cesta do adresáře s obrázky, samozřejmě nemáme 256 adresářů struktury hned v rootu webu.</div>
<div class="bulb">Ve výchozím nastavení serveru nginx se do PHP-FPM vůbec nepouštějí požadavky na soubory s příponami typickými pro statický obsah. To je při zprovozňování <code>ImageServicePresenteru</code> potřeba mít na paměti a tuto podmínku odstranit.</div>
<h2>Poznámky z jedné kapsy</h2>
<p>K výše zmíněným jménům souboru jsme došli přes několik iterací. Původně jsme měli název originálu jako čistý MD5 hash a jméno souboru zmenšeniny jako jiný MD5 hash. Což je sice zvenku možná krásně čisté, aplikace se o to postará, ale pro debugování je to peklo.</p>
<p>Proto jsme do jména originálu přidali ID souboru (poznámka o kolizích byl jen lehký trolling), abychom mohli lépe dohledat, ke které entitě soubor patří.</p>
<p>Soubory se i celkem jednoduše procházejí běžnými konzolovými nástroji jako find, takže není problém třeba promazat ty, jejichž parametry už nevyhovují nastavení projektu.</p>
<p>Jedna ze slepých cestiček také byla místo trojice výška/šířka/vlajka ukládat do jména souboru jen klíč, ale záhy jsme zjistili, že pak soubory budou vyhnívat, můžou vznikat nepotřebné duplicitní soubory, při změně parametrů klíče se musí původní soubory ručně promazat, jinak se nenavytváří znovu, a jiné.</p>
<p>Co může být problém je změna velikosti obrázku na nějaké hodně navštěvované stránce &#8211; server pak začne generovat víc zmenšenin než běžně a může ho to i zabít. Pak je na pořadu dne zmenšeniny navytvářet nějakým workerem bokem v době, kdy není zátěž na serveru taková.</p>
<p>Při práci se jmény souborů používáme všude absolutní cesty.</p>
<p>Samozřejmě se může stát, že getter nevrátí instanci <code>Image</code> &#8211; nemusí být pro <code>$performance</code> povinná. Proto vypadá šablona častěji nějak takto:</p><pre class="crayon-plain-tag">{if $performance-&gt;getImage()}
	{$performance-&gt;getImage() |thumbnail:large |imagehtml, $performance-&gt;getDescription()}
{else}
	{NULL |dummyimagehelper:large |imagehtml}
{/if}</pre><p><code>dummyimagehelper</code> vrací dummy obrázky připravené specificky pro náš projekt. Ať už jsou vytvořené pro jednotlivé varianty zmenšenin ručně, nebo ať už je necháváme generovat z nějakého předpřipraveného zdroje podobně, jako zmenšeniny skutečných originálů.</p>
<h2 style="color: #000000;">Související články</h2>
<ul style="color: #474747;">
<li>Matěj Humpál: <a href="https://blog.medio.cz/jak-ukladame-obrazky">Jak ukládáme obrázky</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.medio.cz/jak-zobrazujeme-obrazky/feed</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title>Jak ukládáme obrázky</title>
		<link>https://blog.medio.cz/jak-ukladame-obrazky</link>
					<comments>https://blog.medio.cz/jak-ukladame-obrazky#comments</comments>
		
		<dc:creator><![CDATA[Matěj Humpál]]></dc:creator>
		<pubDate>Wed, 29 Oct 2014 07:00:31 +0000</pubDate>
				<category><![CDATA[Programování a vývoj]]></category>
		<category><![CDATA[Dekompozice]]></category>
		<category><![CDATA[Dependency Injection]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://blog.medio.cz/?p=2095</guid>

					<description><![CDATA[Mnoho webových projektů, od malých až po ty největší, potřebuje nějak pracovat s obrázky. Ty je třeba ukládat, zpracovávat a jednoduše zobrazovat. Pojďme se podívat na to, jak obrázky ukládáme u nás v Mediu. Později řeč přijde i na elegantntí zobrazování uživatelům.]]></description>
										<content:encoded><![CDATA[<p>Mnoho webových projektů, od malých až po ty největší, potřebuje nějak pracovat s obrázky. Ty je třeba ukládat, zpracovávat a jednoduše zobrazovat. Pojďme se podívat na to, jak obrázky ukládáme <a href="http://www.medio.cz/">u nás v Mediu</a>. Později řeč přijde i na elegantní zobrazování uživatelům.</p>
<p>Řekněme, že každý obrázek (abychom o nich někde měli přehled a mohli k nim mít i nějaká metadata) je zastoupen entitou <code>Image</code>. Ta nejjednodušší může vypadat třeba nějak takto (pro ještě větší zjednodušení vynechávám gettery, settery a další):</p><pre class="crayon-plain-tag">class Image
{
	private $id;
	private $filename;
	private $extension;
}</pre><p>Abychom vytvořili vztah mezi naší entitou uloženou v jednom úložišti (typicky v databázi) a samotným souborem obrázku uloženým v jiném úložišti (disk, cloud, co si představíte), vytvoříme si třídu <code>ImageRepository</code>. Takto může vypadat:</p><pre class="crayon-plain-tag">class ImageRepository
{
	/** @var IFileRepository */
	private $fileRepository;

	/** @var IImageFileNamingStrategy */
	private $fileNamingStrategy;
	
	public function save(Image $image, $data)
	{
		return $this-&gt;fileRepository-&gt;save(
			$this-&gt;fileNamingStrategy-&gt;getOriginalName($image),
			$data
		);
	}
}</pre><p>Podívejme se na dvě závislosti třídy. <code>$fileNamingStrategy</code> je z nich trochu záhadnější. Protože každá instance projektu (nebo, řekněme, klient) může mít jiné požadavky na pojmenování souborů, implementují si po svém rozhraní <code>IImageFileNamingStrategy</code> s metodou <code>getOriginalName</code>. Například takto:</p><pre class="crayon-plain-tag">class HashFileNamingStrategy implements IImageFileNamingStrategy
{
	public function getOriginalName(Image $image)
	{
		return md5($image-&gt;getId() . $image-&gt;getFilename())
			. '-' . $image-&gt;getId() 
			. '.' . $image-&gt;getExtension();
	}
}</pre><p>Nebojte se, nehashujeme hesla. MD5 je tady jako hashovací algoritmus <em>víceméně</em> v pohodě. Aby bylo všemu učiněno zadost a nedostali jsme nedejbože duplicitní jméno souboru pro jinou instanci <code>Image</code> kvůli kolizi v algoritmu (oči divoce protáčející smajlík), můžeme do nezahashované části jména souboru přidat ID entity.</p>
<p>Dostaneme tak jméno souboru, které vypadá třeba takhle:</p><pre class="crayon-plain-tag">2f2555d95ee03f950c9ddae2f1692c55-42.jpg</pre><p>Závislost <code>IFileRepository</code> je nasnadě – o úroveň nižší repozitář, který volá funkce samotného úložiště, čtení, ukládání, zjištění existence souboru – ten nepotřebuje vědět nic o tom, že pracuje s třídou <code>Image</code>, a zároveň odstiňuje <code>ImageRepository</code> od konkrétní implementace ukládání &#8211; jeho výměnou (u nás typicky změnou jednoho řádku v konfiguraci DI kontejneru) můžeme vyměnit celé úložiště souborů – třeba z lokálního filesystému na Amazon S3.</p>
<p>Řekněme, že budeme obrázky ukládat do filesystému. <code>FilesystemRepository</code> tedy bude mít jako property nějaký kořenový adresář <code>$rootDir</code> (který každá instance dostane nejlépe jako parametr konstruktoru z konfigurace DI kontejneru).</p>
<p>Soubory na disku je třeba podle něčeho organizovat. Tisíce souborů v jednom adresáři už můžou dát strojům pěkně zabrat &#8211; hodí se nám tedy, aby soubory byly nějak rozumně rozloženy. Tady přijde ke cti MD5 začátek jména souboru &#8211; <code>FilesystemRepository</code> každý soubor uloží do stuktury podle prvních dvou dvouznaků jeho jména. Máme vyzkoušeno, že se tak soubory do adresářové struktury rozloží pěkně rovnoměrně. 256×256 adresářů should be enough for everyone. Soubor zmíněný výše relativně k <code>$rootDir</code> se uloží takto:</p><pre class="crayon-plain-tag">2f/25/2f2555d95ee03f950c9ddae2f1692c55-42.jpg</pre><p>Podle jména souboru víme, jak ho hledat v adresářové struktuře (kdybychom se chtěli ručně podívat, kde je třeba případný problém) a na konci souboru máme ID jeho entity – to kdyby byl problém na straně modelu.</p>
<div class="&quot;bulb">Dokonce bychom klidně mohli md5 hash zkrátit, třeba na 8 znaků, to by mělo stačit (o kolize nám už, jak jste mohli postřehnout, až tolik nejde).</div>
<p>Metoda save <code>ImageRepository</code> tedy uloží data obrázku. Podle podobného klíče bude v <code>ImageRepository</code> fungovat i vyzvednutí obrázku: předáme instanci <code>Image</code> metodě <code>getPath</code> a dostaneme cestu k souboru. Metoda <code>load</code> pak může vracet zase obrázek jako data.</p>
<p>Originály souborů, v našem případě obrázků, máme uložené na disku, dokážeme podle entity najít cestu k nim a dále s nimi manipulovat. V <a href="https://blog.medio.cz/jak-zobrazujeme-obrazky">následujícím článku</a> se podíváme na mnohem zajímavější věc, a to jak s obrázky dále manipulujeme a posíláme je uživatelům.</p>
<h2 style="color: #000000;">Související články</h2>
<ul style="color: #474747;">
<li>Matěj Humpál: <a href="https://blog.medio.cz/jak-zobrazujeme-obrazky">Jak zobrazujeme obrázky</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.medio.cz/jak-ukladame-obrazky/feed</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
	</channel>
</rss>
