Lekce 4 - Knihovna StringUtils pro práci s textem v PHP
V předešlém cvičení, Řešené úlohy k 1.-3. lekci knihovny v PHP, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V dnešním dílu si naprogramujeme knihovnu StringUtils
.
Motivace
PHP poskytuje bohatou škálu funkcí pro práci s textovými řetězci. Některé důležité funkce zde však zcela chybí a my je proto musíme implementovat sami a stále znovu a znovu.
Knihovna StringUtils
Do knihovny StringUtils
vložíme několik užitečných
funkcí, které PHP postrádá. Třída je pomocného charakteru, nebude mít
žádný vnitřní stav a její funkce budeme opět používat často a na mnoha
místech našich aplikací. Proto budou všechny její metody statické.
class StringUtils
{
}
Třídu si můžete vložit do jmenného prostoru Utility
.
Ačkoli je to snad samozřejmé, raději připomenu, že pro vnitřní
implementaci budeme používat výhradně PHP funkce s prefixem
mb_
, které podporují znakovou sadu Unicode.
Metody startsWith()
a
endsWith()
Často bychom potřebovali vědět, zda nějaký text začíná nebo končí
určitým podřetězcem. Např. parsujeme nějaké položky a právě podle
částí jejich názvu se chceme orientovat. Metoda endsWith()
se
hodí zejména pro autoloader, kde podle koncovky názvu třídy určujeme z
jaké složky se má načíst:
public static function startsWith(string $haystack, string $needle): bool { return (mb_strpos($haystack, $needle) === 0); } public static function endsWith(string $haystack, string $needle): bool { return ((mb_strlen($haystack) >= mb_strlen($needle)) && ((mb_strpos($haystack, $needle, mb_strlen($haystack) - mb_strlen($needle))) !== false)); }
Metody berou v prvním parametru prohledávaný text a v druhém hledaný
podřetězec. V anglické literatuře se při problémech prohledávání
používají označení kupka sena (haystack) a jehla
(needle). Asi tušíte proč
Na metodě startsWith()
není nic složitého, pouze musíme
použít trojrovnítko ===
, jelikož funkce
mb_strpos()
vrací hodnotu false
při neúspěchu a
hodnotu 0
při nalezení na první pozici.
Metoda endsWith()
je již zajímavější. Kupka musí být
delší nebo alespoň stejně dlouhá jako jehla, jinak v ní nemá smysl
hledat. Dále necháme funkci mb_strpos()
prohledat kupku až od
té pozice, kde by mohla jehla začínat (délka kupky - délka jehly),
předchozí výskyty nás nezajímají.
Funkce vyzkoušejme, nezapomeňte nastavit interní kódování na
UTF-8
a cestu ke knihovně:
require_once('models/System/Utility/StringUtils.php'); mb_internal_encoding('utf-8'); var_dump(StringUtils::startsWith('Čtvrtletní hodnocení', 'Čtvrtletní')); var_dump(StringUtils::startsWith('Pololetní hodnocení', 'Čtvrtletní')); var_dump(StringUtils::startsWith('Pololetní a čtvrtletní hodnocení', 'Čtvrtletní')); var_dump(StringUtils::endsWith('ArticleController', 'Controller')); var_dump(StringUtils::endsWith('ArticleHelperController', 'Helper'));
Výsledek:
Metody capitalize()
a
uncapitalize()
Výše uvedené funkce převedou počáteční písmeno řetězce na velký
nebo na malý tvar. Metoda capitalize()
se nám hodí při výpisu
např. tabulkových dat, kde chceme, aby uživatel viděl hodnoty s
počátečním velkým písmenem, ale vnitřně se s nimi nějak pracuje a proto
jsou malými písmeny. Metoda uncapitalize()
první písmeno naopak
zmenší. Obě funkce budeme využívat ještě pro vnitřní potřeby
knihovny.
Metoda uncapitalize()
má pravděpodobně
nesprávný anglický tvar, nicméně mi připadá zřejmější, než např.
lowerFirst()
nebo něco podobného.
public static function capitalize(string $text): string { return mb_strtoupper(mb_substr($text, 0, 1)) . mb_substr($text, 1, mb_strlen($text)); } public static function uncapitalize(string $text): string { return mb_strtolower(mb_substr($text, 0, 1)) . mb_substr($text, 1, mb_strlen($text)); }
Kód je zřejmý, pojďme ho tedy vyzkoušet:
echo(StringUtils::capitalize('žluťoučký kůň') . '<br />'); echo(StringUtils::uncapitalize('Žluťoučký kůň') . '<br />');
Výsledek:
Zkrácení textu
Často vypisujeme nějaké popisky např. k položkám v eshopu a chceme, aby měl popisek nějaký maximální počet znaků, jelikož delší text se ke zboží v dané šabloně nevejde. Pokud je text delší, než určitá maximální délka, potřebujeme ho zkrátít a přidat na konec tři tečky, aby bylo jasné, že část chybí. Pokud byl kratší, nepřidáváme nic. Maximální délka se počítá již s případnými tečkami:
public static function shorten(string $text, int $length): string { if (mb_strlen($text) - 3 > $length) $text = mb_substr($text, 0, $length - 3) . '...'; return $text; }
Metodu někdy naleznete ve frameworcích pod názvem truncate. Můžeme vyzkoušet:
echo(StringUtils::shorten('Notebook - Intel Pentium 2020M Ivy Bridge, 15.6" LED 1366x768 lesklý, RAM 4GB, Intel HD Graphics, HDD 500GB 5400 otáček, DVD, Wi-Fi, Bluetooth, Webkamera, USB 3.0, Windows 8 64-bit', 71) . '<br />'); echo(StringUtils::shorten('Notebook - Intel Pentium 2020M', 71) . '<br />');
Výsledek jsem schválně usekl u znaku s diakritikou, vše funguje správně:
Odstranění diakritiky
Dnešní díl zakončeme odstraněním diakritiky. To je často potřeba, když vyrábíme např. ze jména emailovou adresu daného člověka, z názvu článku jeho URL adresu, pokud chceme ověřit, že uživatel nezadal do hesla diakritiku (heslo musí být shodné s heslem bez diakritiky, jinak je v něm diakritika) a podobně.
Přiznám se, že kód metody jsem vygooglil, přesněji ho vygooglil sczdavos. Ukažme si ho:
public static function removeAccents(string $text): string { $chars = array( // Decompositions for Latin-1 Supplement chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A', chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A', chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A', chr(195) . chr(135) => 'C', chr(195) . chr(136) => 'E', chr(195) . chr(137) => 'E', chr(195) . chr(138) => 'E', chr(195) . chr(139) => 'E', chr(195) . chr(140) => 'I', chr(195) . chr(141) => 'I', chr(195) . chr(142) => 'I', chr(195) . chr(143) => 'I', chr(195) . chr(145) => 'N', chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O', chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O', chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U', chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U', chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y', chr(195) . chr(159) => 's', chr(195) . chr(160) => 'a', chr(195) . chr(161) => 'a', chr(195) . chr(162) => 'a', chr(195) . chr(163) => 'a', chr(195) . chr(164) => 'a', chr(195) . chr(165) => 'a', chr(195) . chr(167) => 'c', chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e', chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e', chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i', chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i', chr(195) . chr(177) => 'n', chr(195) . chr(178) => 'o', chr(195) . chr(179) => 'o', chr(195) . chr(180) => 'o', chr(195) . chr(181) => 'o', chr(195) . chr(182) => 'o', chr(195) . chr(182) => 'o', chr(195) . chr(185) => 'u', chr(195) . chr(186) => 'u', chr(195) . chr(187) => 'u', chr(195) . chr(188) => 'u', chr(195) . chr(189) => 'y', chr(195) . chr(191) => 'y', // Decompositions for Latin Extended-A chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a', chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a', chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a', chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c', chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c', chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c', chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c', chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd', chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd', chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e', chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e', chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e', chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e', chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e', chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g', chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g', chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g', chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g', chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h', chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h', chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i', chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i', chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i', chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i', chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i', chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij', chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j', chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k', chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L', chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L', chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L', chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L', chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L', chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N', chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N', chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N', chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N', chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N', chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o', chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o', chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o', chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe', chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r', chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r', chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r', chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's', chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's', chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's', chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's', chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't', chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't', chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't', chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u', chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u', chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u', chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u', chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u', chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u', chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w', chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y', chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z', chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z', chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z', chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's', // Euro Sign chr(226) . chr(130) . chr(172) => 'E', // GBP (Pound) Sign chr(194) . chr(163) => '' ); return strtr($text, $chars); }
Jde vlastně jen o výčet diakritických znaků, které jsou v UTF-8
reprezentovány jako několik ASCII znaků za sebou (např. háček a
c
). Výčet je zapsán jako pole, aby pomocí něj mohla následně
PHP funkce strtr()
provést nahrazení.
Kód je součástí metody nějakého modulu WordPressu, kde je výčet ještě mnohem delší, nám však bude tento stačit.
Metodu vyzkoušejme:
echo(StringUtils::removeAccents('Příliš žluťoučký kůň úpěl ďábelské ódy. PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY. <br />')); echo(StringUtils::removeAccents('Příliš žluťoučký kůň úpěl ďábelské ódy. PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY. <br />'));
Výsledek:
To by bylo pro dnešek vše.
Příště, v lekci Dokončení knihovny StringUtils v PHP, v kniihovně StringUtils
zajistíme
převod na pomlčky mezi snake_case a camelCase. Také si
vygenerujeme hesla.