Jak vypadá přidání nové funkce aneb "když už to dělám"
Narazil jsem teď na hezký příklad pro demonstraci, jak přibližně přistupujeme k vývoji našeho eshopového řešení. Předvedu vám myšlenkový postup, jak se primitivní zadání během chvíle zamotá.
Zadání
Klient poslal ticket, že má špatně zarovnaná videa u produktů. Jsou nacpaná u popisu, místo aby se vypsala nějak hezky.
Popis produktu je totiž převzatý přímo od dodavatele a ten má videa vložena přímo do popisu (běžná praxe).
Řešení hrubou silou
Potřebujeme odstranit Youtube iframe z popisu a vypsat ho samostatně. Není problém, začarujeme s regulárem.
<div class="description">
{% if "<iframe" in product.description %}
{{product.description|replace('~<iframe [^<]*src\s*=\s*["']http[^"']+youtube.com.*?</iframe>~s', '')}}
{% endif %}
</div>
Tím bychom měli odstraněný iframe. Pak už jen druhý kód na vypsání videa, máme hotovo a můžeme jít dál. Jenže už ten první je dost ošklivý a ani neřeší všechny situace. Pokud za týden bude potřeba řešit něco dalšího s popisem, tak kód jen poroste a nikdo se v něm nevyzná.
Když už to dělám, tak to vyřeším rovnou obecněji, ať to se tenhle problém v budoucnu nevrací.
Poznámka na okraj: všimněte si, že v kódu je nejdřív podmínka s "in". Reguláry jsou totiž daleko pomalejší než strpos a videa nejsou u každého produktu. Díky té podmínce se výrazně sníží počet spuštění reguláru a o nějakou milisekundu se zrychlí načítání.
Řešení u zdroje
Popis je přímo od dodavatele, který se importuje skrz ShopAPI.cz - vyřeším to raději už v něm. Díky tomu mám problém vyřešen nejen v nahlášeném případě, ale rovnou i u všech ostatních dodavatelů a hlavně i u ostatních klientů.
Na rozdíl od Twig šablony mám navíc v ShopAPI daleko lepší možnosti, jak se k problému postavit. Místo regulárního výrazu můžu popis raději zpracovat jako DOM - najdu videa ve všech možných zápisech a vyexportuji samostatně.
<export>
<!-- původní data -->
<product>
<uid>6bumvbj3lt44</uid>
<name>Quercetti Daisy Box Castello</name>
<description>Mozaiková stavebnice rozvíjející jemnou motoriku.
<br>
<br>
<iframe width="640" height="360" src="//www.youtube.com/embed/UbZXL91d_i8?rel=0" frameborder="0"></iframe></description>
</product>
<!-- vyparsovaná data -->
<product>
<uid>6bumvbj3lt44</uid>
<name>Quercetti Daisy Box Castello</name>
<description>Mozaiková stavebnice rozvíjející jemnou motoriku.</description>
<video>
<uid>d5h4hjk87wer</uid>
<type>youtube</type>
<code>UbZXL91d_i8</code>
</video>
</product>
</export>
Díky vyparsování z textu jsem hned vyřešil i několik dalších problémů - nebudou se používat zastaralé způsoby vkládání videa (vložím si ho sám) a nebude problém s načítání videa z nezabezpečeného http://
Validace
Když už to dělám, tak bych mohl rovnou vyřešit i kontrolu videí. Pokud totiž byla videa v popisu vložená přes zastaralý object, tak lze předpokládat, že část videí už ani neexistuje. Po vyparsování tak raději použiju Youtube API a vyloučím smazaná videa. V eshopu se tenhle problém už nemusí nikdy řešit - když video zmizí, skryje se i v eshopu.
Rozšíření dat
Když už to dělám a pracuji s Youtube API, tak bych mohl k videím stáhnout i další meta data jako je titulek nebo délka.
<export>
<product>
<uid>6bumvbj3lt44</uid>
<name>Quercetti Daisy Box Castello</name>
<description>Mozaiková stavebnice rozvíjející jemnou motoriku.</description>
<video>
<uid>d5h4hjk87wer</uid>
<type>youtube</type>
<code>UbZXL91d_i8</code>
<title>Daisy Box Castello</title>
<duration>00:00:43</duration>
<description>Mozaiková stavebnice rozvíjející jemnou motoriku.</description>
</video>
</product>
</export>
Vložení do šablony
S takto hezkými daty už můžu jednoduše pracovat. V šabloně už nemusím čarovat s regulárními výrazy a video vložím hezky po svém.
{% for video in product.videos %}
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/{{video.code}}"
frameborder="0"
allowfullscreen
></iframe>
{% endfor %}
Lepší vložení do šablony
Když už to dělám, tak bych to určitě mohl vyřešit i lépe. Pokud bude v budoucnu k dispozici lepší způsob vkládání, tak bychom museli měnit všechny šablony. Nemluvě o tom, že Youtube není jediný zdroj videí a nebudeme takhle chtít řešit zvlášť i Vimeo další.
Raději proto vyrobíme univerzálnější zápis - podobný, jako už používáme pro obrázky.
{% for video in product.videos %}
<i:video width="560" height="315" src="video" />
{% endfor %}
Takhle s šablonou pracuje kodér, ale interně se zkompiluje zhruba do této podoby:
{% for video in product.videos %}
{% if video.type == 'youtube' %}
<iframe width="560" height="315" src="https://www.youtube.com/embed/{{video.code}}" frameborder="0" allowfullscreen></iframe>
{% elseif video.type == 'vimeo' %}
<iframe...
{% endif %}
{% endfor %}
S tímto zápisem už se v šabloně nemusí do budoucna nic měnit. Pokud by se změnil doporučený způsob vložení do HTML, tak to můžeme změnit centrálně. Stejně tak kdybychom chtěli přidat mezi podporované servery nějaký další, tak to zase lze vyřešit snadno centrálně pro všechny eshopy.
A co mobily?
Když už to řeším, tak bych mohl pořešit i mobily. On totiž takový nenápadný iframe způsobí 12 požadavků a stáhne 575 kB. To není zase takový problém, když je video hlavním prvkem stránky, ale ne když je jen doplňkovou informací, kterou třeba ani většina návštěvníků nepoužije.
Pokud tedy je návštěvník na mobilu a nevíme jistě, zda má rychlé připojení, a nebo je naopak na počítači, ale víme, že má pomalé, zobrazíme úspornější verzi. Vypíšeme místo iframe jenom obrázek s popiskem (hodí se nám, že ho známe) a tlačítkem pro přehrání. Vizuálně to vypadá shodně jako normální přehrávač, ale ten se ve skutečnosti načte až po kliknutí. Návštěvník nejspíš ani nepozná rozdíl, ale načítání stránky bude znatelně datově lehčí.
V šabloně je zápis pořád stejný:
{% for video in product.videos %}
<i:video width="560" height="315" src="video" />
{% endfor %}
Při kompilaci se ale šablona převede do komplikovanější podoby:
{% for video in product.videos %}
{% if slowNetwork %}
<div data-video-type="{{video.type}}" data-video-code="{{video.code}}">
<span>{{video.title}}</span>
<img src="{{video.previewImage}}" alt="{{video.title}}">
</div>
{% else %}
{% if video.type == 'youtube' %}
<iframe width="560" height="315" src="https://www.youtube.com/embed/{{video.code}}" frameborder="0" allowfullscreen></iframe>
{% elseif video.type == 'vimeo' %}
<iframe...
{% endif %}
{% endif %}
{% endfor %}
Takový kód (a to je ještě zjednodušený a bez nutného javascriptu) nechceme řešit u každého eshopu, takže je dobře, že ho máme vyřešený jednoduchým zápisem.
Výsledek
Výsledek vypadá takto:
Nějaký prostor pro vylepšení tam samozřejmě pořád je, ale to už se může řešit v šabloně. Podstatné je, že všechny hlavní problémy jsou vyřešeny už někde mimo a kodér tak v šabloně řeší skutečně jen vzhled a nemusí se s nimi zdržovat.
Technický dluh
Původní zadání šlo vyřešit za pár minut. Když se ale raději vyřešilo delší cestou, tak nevznikl do budoucna dluh a zároveň se vyřešilo i několik dalších problémů, na které by se později stejně narazilo.
Díky tomuto způsobu řešení si můžeme dovolit mít šablony na míru pro každý eshop a ještě k nim dát přístup klientům, aby si dělali změny sami. Není to krása?