Skip to content

Глава 1.5: Строки и регулярные выражения

Манипуляции со строками, поиск, замена, валидация


Почему строки так важны?

Строки — это 90% данных в веб-разработке:

┌─────────────────────────────────────────────────────────────────┐
│                    ГДЕ ИСПОЛЬЗУЮТСЯ СТРОКИ                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   📝 Ввод пользователя    Формы, комментарии, поиск            │
│   🌐 URL и маршруты       /users/123/edit                       │
│   📧 Email и сообщения    Шаблоны писем, уведомления           │
│   🗄️ Данные из БД         Практически всё — строки             │
│   📋 JSON/XML/HTML        Парсинг и генерация                   │
│   🔒 Безопасность         Валидация, санитизация, экранирование│
│   📊 Форматирование       Даты, деньги, телефоны               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

ЧАСТЬ 1: ОСНОВЫ СТРОК


1. Создание строк

Четыре способа

php
<?php
// 1. Одинарные кавычки — литеральная строка
$str1 = 'Привет, мир!';
$str2 = 'Это стоит $100';     // $100 — просто текст
$str3 = 'It\'s a string';     // Экранирование апострофа

// 2. Двойные кавычки — с интерполяцией
$name = "Иван";
$str4 = "Привет, $name!";           // Привет, Иван!
$str5 = "Привет, {$name}!";         // То же, явный синтаксис
$str6 = "Табуляция:\tНовая строка:\n";  // Спецсимволы работают

// 3. Heredoc — многострочная с интерполяцией
$html = <<<HTML
<div class="card">
    <h1>$name</h1>
    <p>Добро пожаловать!</p>
</div>
HTML;

// 4. Nowdoc — многострочная без интерполяции
$code = <<<'CODE'
<?php
$x = 5;
echo $x;  // $x останется как текст
CODE;

Сравнение кавычек

┌──────────────────┬────────────────────┬────────────────────┐
│                  │ Одинарные ''       │ Двойные ""         │
├──────────────────┼────────────────────┼────────────────────┤
│ Интерполяция $var│ ❌ Нет             │ ✅ Да              │
│ Спецсимволы \n   │ ❌ Только \' и \\  │ ✅ Все             │
│ Производительность│ Чуть быстрее      │ Чуть медленнее     │
│ Использование    │ Без переменных    │ С переменными      │
└──────────────────┴────────────────────┴────────────────────┘

Специальные символы (только в двойных кавычках)

php
<?php
$str = "Первая строка\nВторая строка";   // \n — новая строка
$str = "Колонка1\tКолонка2";             // \t — табуляция
$str = "Путь: C:\\folder\\file";         // \\ — обратный слэш
$str = "Он сказал: \"Привет!\"";         // \" — кавычка
$str = "Цена: \$100";                    // \$ — знак доллара
$str = "Символ: \x41";                   // \x41 — hex код (A)
$str = "Unicode: \u{1F600}";             // \u{} — Unicode (😀)

Интерполяция переменных

php
<?php
$name = "Иван";
$user = ["name" => "Мария", "age" => 25];
$obj = new stdClass();
$obj->name = "Пётр";

// Простая переменная
echo "Привет, $name!";              // Привет, Иван!

// Элемент массива — нужны {}
echo "Имя: {$user['name']}";        // Имя: Мария
echo "Имя: $user[name]";            // Работает, но не рекомендуется

// Свойство объекта
echo "Имя: {$obj->name}";           // Имя: Пётр
echo "Имя: $obj->name";             // Тоже работает

// Выражения — нужен трюк
$x = 5;
echo "Результат: {${''} . ($x * 2)}";  // Не работает!
echo "Результат: " . ($x * 2);          // Используй конкатенацию

Конкатенация

php
<?php
$first = "Hello";
$second = "World";

// Оператор .
$result = $first . " " . $second;  // "Hello World"

// Оператор .=
$str = "Hello";
$str .= " ";
$str .= "World";  // "Hello World"

// Что лучше: конкатенация или интерполяция?
$name = "Иван";

// Интерполяция — для простых случаев (читабельнее)
$greeting = "Привет, $name!";

// Конкатенация — для сложных выражений
$greeting = "Привет, " . ucfirst(strtolower($name)) . "!";

2. Базовые функции строк

Длина строки

php
<?php
$str = "Привет";

// strlen — количество БАЙТ (не символов!)
echo strlen($str);      // 12 (UTF-8: кириллица = 2 байта на символ)
echo strlen("Hello");   // 5

// mb_strlen — количество СИМВОЛОВ (для UTF-8)
echo mb_strlen($str);           // 6
echo mb_strlen($str, 'UTF-8');  // 6 (явное указание кодировки)

// Всегда используй mb_* для кириллицы!

Изменение регистра

php
<?php
$str = "Привет Мир";

// Стандартные (только ASCII!)
echo strtolower("HELLO");    // "hello"
echo strtoupper("hello");    // "HELLO"
echo ucfirst("hello world"); // "Hello world"
echo ucwords("hello world"); // "Hello World"
echo lcfirst("Hello");       // "hello"

// Для UTF-8 — используй mb_*
echo mb_strtolower("ПРИВЕТ");           // "привет"
echo mb_strtoupper("привет");           // "ПРИВЕТ"
echo mb_convert_case("привет", MB_CASE_TITLE);  // "Привет"

// ucfirst для UTF-8 (своя функция)
function mb_ucfirst(string $str): string {
    return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1);
}
echo mb_ucfirst("привет");  // "Привет"

Обрезка пробелов

php
<?php
$str = "   Привет   ";

trim($str);       // "Привет" — с обеих сторон
ltrim($str);      // "Привет   " — слева (left)
rtrim($str);      // "   Привет" — справа (right)

// Можно указать символы для удаления
$str = "...Привет...";
trim($str, ".");  // "Привет"

$str = "Hello World!!!";
rtrim($str, "!");  // "Hello World"

// Удаление переносов строк
$str = "Текст\r\n";
rtrim($str, "\r\n");  // "Текст"

Подстрока

php
<?php
$str = "Hello World";

// substr(строка, начало, длина)
echo substr($str, 0, 5);    // "Hello"
echo substr($str, 6);       // "World" (до конца)
echo substr($str, -5);      // "World" (с конца)
echo substr($str, 0, -6);   // "Hello" (без последних 6)
echo substr($str, -5, 3);   // "Wor"

// Для UTF-8
$str = "Привет Мир";
echo mb_substr($str, 0, 6);  // "Привет"
echo mb_substr($str, -3);    // "Мир"

3. Поиск в строках

Поиск позиции

php
<?php
$str = "Hello World, Hello PHP";

// strpos — первое вхождение (регистрозависимо)
echo strpos($str, "Hello");     // 0
echo strpos($str, "World");     // 6
echo strpos($str, "hello");     // false (не найдено!)
echo strpos($str, "o");         // 4

// ВАЖНО: проверка результата
if (strpos($str, "Hello") !== false) {  // !== а не !=
    echo "Найдено!";
}

// ❌ Неправильно (0 == false)!
if (strpos($str, "Hello")) {
    echo "Это не сработает для позиции 0!";
}

// stripos — без учёта регистра
echo stripos($str, "hello");    // 0

// strrpos — последнее вхождение
echo strrpos($str, "Hello");    // 13

// Поиск с позиции
echo strpos($str, "Hello", 1);  // 13 (начать поиск с позиции 1)

// Для UTF-8
$str = "Привет Мир";
echo mb_strpos($str, "Мир");    // 7

Проверка наличия подстроки

php
<?php
$str = "Hello World";

// PHP 8+ — str_contains (рекомендуется!)
if (str_contains($str, "World")) {
    echo "Содержит 'World'";
}

// PHP 8+ — str_starts_with, str_ends_with
if (str_starts_with($str, "Hello")) {
    echo "Начинается с 'Hello'";
}

if (str_ends_with($str, "World")) {
    echo "Заканчивается на 'World'";
}

// До PHP 8 — через strpos
function contains(string $haystack, string $needle): bool {
    return strpos($haystack, $needle) !== false;
}

function startsWith(string $haystack, string $needle): bool {
    return strpos($haystack, $needle) === 0;
}

function endsWith(string $haystack, string $needle): bool {
    return substr($haystack, -strlen($needle)) === $needle;
}

Подсчёт вхождений

php
<?php
$str = "Hello World, Hello PHP, Hello Universe";

echo substr_count($str, "Hello");  // 3
echo substr_count($str, "o");      // 4

4. Замена в строках

str_replace — простая замена

php
<?php
$str = "Hello World";

// Замена одной подстроки
echo str_replace("World", "PHP", $str);  // "Hello PHP"

// Замена нескольких (массивы)
$search = ["Hello", "World"];
$replace = ["Привет", "Мир"];
echo str_replace($search, $replace, $str);  // "Привет Мир"

// Замена на одно значение
echo str_replace(["a", "e", "i", "o", "u"], "*", "Hello World");
// "H*ll* W*rld"

// Подсчёт замен
$count = 0;
$result = str_replace("o", "0", "Hello World", $count);
echo $count;  // 2

// Без учёта регистра
echo str_ireplace("hello", "Привет", "HELLO World");  // "Привет World"

strtr — замена по карте

php
<?php
// Замена символов
$str = "Hello";
echo strtr($str, "el", "ip");  // "Hippo" (e→i, l→p)

// Замена по массиву (более гибко)
$trans = [
    "Hello" => "Привет",
    "World" => "Мир"
];
echo strtr("Hello World", $trans);  // "Привет Мир"

// Транслитерация
$translit = [
    "а" => "a", "б" => "b", "в" => "v", "г" => "g", "д" => "d",
    "е" => "e", "ё" => "yo", "ж" => "zh", "з" => "z", "и" => "i",
    "й" => "y", "к" => "k", "л" => "l", "м" => "m", "н" => "n",
    "о" => "o", "п" => "p", "р" => "r", "с" => "s", "т" => "t",
    "у" => "u", "ф" => "f", "х" => "h", "ц" => "ts", "ч" => "ch",
    "ш" => "sh", "щ" => "sch", "ъ" => "", "ы" => "y", "ь" => "",
    "э" => "e", "ю" => "yu", "я" => "ya", " " => "-"
];
$str = "Привет мир";
echo strtr(mb_strtolower($str), $translit);  // "privet-mir"

substr_replace — замена по позиции

php
<?php
$str = "Hello World";

// Заменить часть строки
echo substr_replace($str, "PHP", 6);       // "Hello PHP"
echo substr_replace($str, "PHP", 6, 5);    // "Hello PHP" (заменить 5 символов)
echo substr_replace($str, "Dear ", 0, 0);  // "Dear Hello World" (вставка)
echo substr_replace($str, "", 5);          // "Hello" (удаление с позиции)

// Маскирование
$card = "4111111111111111";
$masked = substr_replace($card, str_repeat("*", 12), 0, 12);
// "************1111"

5. Разделение и объединение

explode — строка в массив

php
<?php
$str = "apple,banana,orange";

$arr = explode(",", $str);
// ["apple", "banana", "orange"]

// С лимитом
$arr = explode(",", $str, 2);
// ["apple", "banana,orange"]

// Отрицательный лимит (PHP 5.4+)
$arr = explode(",", $str, -1);
// ["apple", "banana"] — без последнего

// Разделение по пробелам
$str = "Hello World PHP";
$words = explode(" ", $str);
// ["Hello", "World", "PHP"]

// Обработка CSV-строки
$csv = "Иван,25,Москва";
[$name, $age, $city] = explode(",", $csv);

implode / join — массив в строку

php
<?php
$arr = ["apple", "banana", "orange"];

echo implode(", ", $arr);   // "apple, banana, orange"
echo implode("-", $arr);    // "apple-banana-orange"
echo implode("", $arr);     // "applebananaorange"

// join — алиас implode
echo join(", ", $arr);      // "apple, banana, orange"

// Практика: формирование SQL
$ids = [1, 2, 3, 4, 5];
$sql = "SELECT * FROM users WHERE id IN (" . implode(",", $ids) . ")";
// SELECT * FROM users WHERE id IN (1,2,3,4,5)

// Практика: хлебные крошки
$breadcrumbs = ["Главная", "Каталог", "Телефоны"];
echo implode(" → ", $breadcrumbs);
// "Главная → Каталог → Телефоны"

str_split — строка в массив символов

php
<?php
$str = "Hello";

$chars = str_split($str);
// ["H", "e", "l", "l", "o"]

// По N символов
$chunks = str_split($str, 2);
// ["He", "ll", "o"]

// Для UTF-8
$str = "Привет";
$chars = mb_str_split($str);  // PHP 7.4+
// ["П", "р", "и", "в", "е", "т"]

// До PHP 7.4
$chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);

wordwrap и chunk_split

php
<?php
// wordwrap — перенос по словам
$str = "Очень длинный текст, который нужно разбить на строки определённой длины";
echo wordwrap($str, 30, "\n", true);
/*
Очень длинный текст, который
нужно разбить на строки
определённой длины
*/

// chunk_split — разбить на части
$str = "1234567890";
echo chunk_split($str, 3, "-");  // "123-456-789-0-"

6. Форматирование

sprintf — форматированные строки

php
<?php
// Основной синтаксис: %[позиция$][флаги][ширина][.точность]спецификатор

// Спецификаторы
$str = sprintf("Строка: %s", "текст");           // "Строка: текст"
$str = sprintf("Целое: %d", 42);                 // "Целое: 42"
$str = sprintf("Дробное: %f", 3.14159);          // "Дробное: 3.141590"
$str = sprintf("Дробное: %.2f", 3.14159);        // "Дробное: 3.14"
$str = sprintf("Процент: %%");                   // "Процент: %"

// Ширина и выравнивание
$str = sprintf("|%10s|", "test");                // "|      test|" — справа
$str = sprintf("|%-10s|", "test");               // "|test      |" — слева
$str = sprintf("|%010d|", 42);                   // "|0000000042|" — нули
$str = sprintf("|%'#10s|", "test");              // "|######test|" — свой символ

// Позиционные аргументы
$str = sprintf("%2\$s %1\$s", "World", "Hello"); // "Hello World"

// Практика: цены
function formatPrice(float $price): string {
    return sprintf("%01.2f ₽", $price);
}
echo formatPrice(1234.5);  // "1234.50 ₽"

// Практика: ID с ведущими нулями
$id = 42;
echo sprintf("ORDER-%05d", $id);  // "ORDER-00042"

number_format — форматирование чисел

php
<?php
$num = 1234567.891;

echo number_format($num);                    // "1,234,568" (округление!)
echo number_format($num, 2);                 // "1,234,567.89"
echo number_format($num, 2, ',', ' ');       // "1 234 567,89" (русский формат)
echo number_format($num, 2, '.', '');        // "1234567.89" (без разделителя тысяч)

// Функция для цен
function price(float $amount, string $currency = '₽'): string {
    return number_format($amount, 2, ',', ' ') . ' ' . $currency;
}
echo price(1234567.89);  // "1 234 567,89 ₽"

printf — вывод форматированной строки

php
<?php
// printf = sprintf + echo
printf("Имя: %s, Возраст: %d\n", "Иван", 25);
// Выводит: "Имя: Иван, Возраст: 25"

// vprintf — с массивом аргументов
$data = ["Мария", 30];
vprintf("Имя: %s, Возраст: %d\n", $data);

7. Безопасность строк

htmlspecialchars — защита от XSS

php
<?php
// XSS-атака: пользователь вводит HTML/JS
$userInput = '<script>alert("XSS!")</script>';

// ❌ Опасно!
echo $userInput;  // Выполнится скрипт!

// ✅ Безопасно
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// &lt;script&gt;alert(&quot;XSS!&quot;)&lt;/script&gt;

// Флаги:
// ENT_QUOTES — преобразует и " и '
// ENT_HTML5 — для HTML5
// ENT_SUBSTITUTE — заменяет невалидные символы

// Создай хелпер
function e(string $str): string {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

// Использование в шаблонах
echo '<input value="' . e($userInput) . '">';

htmlentities — все HTML-сущности

php
<?php
$str = "Привет <World> & \"Friends\"";

echo htmlentities($str, ENT_QUOTES, 'UTF-8');
// Привет &lt;World&gt; &amp; &quot;Friends&quot;

// html_entity_decode — обратное преобразование
$encoded = "&lt;div&gt;Hello&lt;/div&gt;";
echo html_entity_decode($encoded);  // "<div>Hello</div>"

strip_tags — удаление HTML

php
<?php
$html = "<p>Привет <strong>мир</strong>!</p><script>alert('XSS')</script>";

// Удалить все теги
echo strip_tags($html);  // "Привет мир!"

// Разрешить некоторые теги
echo strip_tags($html, '<p><strong>');  // "<p>Привет <strong>мир</strong>!</p>"

// PHP 7.4+ — массив разрешённых тегов
echo strip_tags($html, ['p', 'strong']);

addslashes / stripslashes

php
<?php
// Экранирование кавычек (для строк, НЕ для SQL!)
$str = "It's a \"test\"";
echo addslashes($str);  // "It\'s a \"test\""

// Обратное
echo stripslashes("It\'s a \"test\"");  // "It's a "test""

// ⚠️ Для SQL используй подготовленные выражения (PDO), не addslashes!

ЧАСТЬ 2: РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ


8. Введение в регулярные выражения

Что это?

Регулярные выражения (regex) — это язык для описания паттернов в тексте.

php
<?php
// Пример: проверка email
$email = "test@example.com";
$pattern = '/^[\w\.-]+@[\w\.-]+\.\w{2,}$/';

if (preg_match($pattern, $email)) {
    echo "Валидный email";
}

Синтаксис в PHP

php
<?php
// Регулярка заключается в разделители (обычно /)
$pattern = '/паттерн/модификаторы';

// Другие разделители (если в паттерне много /)
$pattern = '#https?://[\w./]+#';
$pattern = '~\d{4}-\d{2}-\d{2}~';

Основные функции

php
<?php
// preg_match — найти первое совпадение
// preg_match_all — найти все совпадения
// preg_replace — замена по паттерну
// preg_split — разбиение по паттерну
// preg_grep — фильтрация массива

9. Метасимволы и классы

Базовые метасимволы

┌──────────┬────────────────────────────────────────────────────┐
│ Символ   │ Значение                                           │
├──────────┼────────────────────────────────────────────────────┤
│ .        │ Любой символ (кроме \n)                            │
│ ^        │ Начало строки                                      │
│ $        │ Конец строки                                       │
│ |        │ ИЛИ (альтернатива)                                 │
│ \        │ Экранирование спецсимвола                          │
│ ()       │ Группировка                                        │
│ []       │ Класс символов                                     │
└──────────┴────────────────────────────────────────────────────┘

Классы символов

php
<?php
// [abc]   — один из: a, b или c
// [a-z]   — любая буква от a до z
// [A-Za-z]— любая буква
// [0-9]   — любая цифра
// [^abc]  — НЕ a, b или c (отрицание)
// [a-z0-9]— буква или цифра

preg_match('/[aeiou]/', "Hello");   // Найдёт "e"
preg_match('/[0-9]/', "Test123");   // Найдёт "1"
preg_match('/[^0-9]/', "123abc");   // Найдёт "a"

Предопределённые классы

┌──────────┬──────────────────────────────────────┬─────────────┐
│ Символ   │ Значение                             │ Эквивалент  │
├──────────┼──────────────────────────────────────┼─────────────┤
│ \d       │ Цифра                                │ [0-9]       │
│ \D       │ НЕ цифра                             │ [^0-9]      │
│ \w       │ "Словесный" символ                   │ [a-zA-Z0-9_]│
│ \W       │ НЕ словесный символ                  │ [^a-zA-Z0-9_]│
│ \s       │ Пробельный символ                    │ [\t\n\r\f ]│
│ \S       │ НЕ пробельный символ                 │ [^\t\n\r\f ]│
│ \b       │ Граница слова                        │             │
│ \B       │ НЕ граница слова                     │             │
└──────────┴──────────────────────────────────────┴─────────────┘
php
<?php
preg_match('/\d+/', "Price: 100");     // Найдёт "100"
preg_match('/\w+/', "Hello World");    // Найдёт "Hello"
preg_match('/\s+/', "Hello World");    // Найдёт " "
preg_match('/\bword\b/', "password");  // НЕ найдёт (word внутри слова)
preg_match('/\bword\b/', "the word");  // Найдёт "word"

10. Квантификаторы

Базовые квантификаторы

┌──────────┬────────────────────────────────────────────────────┐
│ Символ   │ Значение                                           │
├──────────┼────────────────────────────────────────────────────┤
│ *        │ 0 или более раз                                    │
│ +        │ 1 или более раз                                    │
│ ?        │ 0 или 1 раз (опционально)                          │
│ {n}      │ Ровно n раз                                        │
│ {n,}     │ n или более раз                                    │
│ {n,m}    │ От n до m раз                                      │
└──────────┴────────────────────────────────────────────────────┘
php
<?php
// Примеры
preg_match('/a*/', "aaa");       // "aaa" (0+)
preg_match('/a+/', "aaa");       // "aaa" (1+)
preg_match('/a?/', "abc");       // "a" (0 или 1)
preg_match('/a{3}/', "aaa");     // "aaa" (ровно 3)
preg_match('/a{2,4}/', "aaaaa"); // "aaaa" (от 2 до 4)

// Практические примеры
preg_match('/\d{4}/', "Year: 2025");          // "2025" — 4 цифры
preg_match('/\d{2,4}/', "123");               // "123" — от 2 до 4 цифр
preg_match('/https?/', "http://example.com"); // "http" — s опционален
preg_match('/colou?r/', "color");             // "color" — u опционален

Жадность квантификаторов

php
<?php
$str = "<p>Hello</p><p>World</p>";

// Жадный (по умолчанию) — захватывает максимум
preg_match('/<p>.*<\/p>/', $str, $matches);
echo $matches[0];  // "<p>Hello</p><p>World</p>" — всё!

// Ленивый (добавь ?) — захватывает минимум
preg_match('/<p>.*?<\/p>/', $str, $matches);
echo $matches[0];  // "<p>Hello</p>" — только первый

// Ленивые версии квантификаторов
// *?  — 0+ (ленивый)
// +?  — 1+ (ленивый)
// ??  — 0-1 (ленивый)
// {n,m}? — n-m (ленивый)

11. Группировка и захват

Группы захвата

php
<?php
$str = "2025-01-27";

// Группы () захватывают совпадения
preg_match('/(\d{4})-(\d{2})-(\d{2})/', $str, $matches);

print_r($matches);
/*
[
    0 => "2025-01-27",  // Полное совпадение
    1 => "2025",        // Группа 1
    2 => "01",          // Группа 2
    3 => "27"           // Группа 3
]
*/

// Деструктуризация
[, $year, $month, $day] = $matches;
echo "$day.$month.$year";  // "27.01.2025"

Именованные группы

php
<?php
$str = "2025-01-27";

// Синтаксис: (?P<name>pattern) или (?<name>pattern)
$pattern = '/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/';
preg_match($pattern, $str, $matches);

echo $matches['year'];   // "2025"
echo $matches['month'];  // "01"
echo $matches['day'];    // "27"

Незахватывающие группы

php
<?php
// (?:pattern) — группировка без захвата
$str = "http://example.com";

// С захватом — протокол попадёт в $matches
preg_match('/(https?)://(.+)/', $str, $matches);
// [0 => "http://example.com", 1 => "http", 2 => "example.com"]

// Без захвата протокола
preg_match('/(?:https?)://(.+)/', $str, $matches);
// [0 => "http://example.com", 1 => "example.com"]

Обратные ссылки

php
<?php
// \1, \2 — ссылки на захваченные группы
$str = "hello hello world world";

// Найти повторяющиеся слова
preg_match('/\b(\w+)\s+\1\b/', $str, $matches);
echo $matches[0];  // "hello hello"
echo $matches[1];  // "hello"

// Замена с группами
$str = "Иванов Иван";
$result = preg_replace('/(\w+)\s+(\w+)/', '$2 $1', $str);
echo $result;  // "Иван Иванов"

12. Модификаторы

Основные модификаторы

┌──────────┬────────────────────────────────────────────────────┐
│ Символ   │ Значение                                           │
├──────────┼────────────────────────────────────────────────────┤
│ i        │ Регистронезависимый поиск                          │
│ m        │ Многострочный режим (^ и $ для каждой строки)      │
│ s        │ Точка . включает \n                                │
│ u        │ UTF-8 режим (ОБЯЗАТЕЛЕН для кириллицы!)            │
│ x        │ Расширенный режим (игнорирует пробелы, комментарии)│
└──────────┴────────────────────────────────────────────────────┘
php
<?php
// i — без учёта регистра
preg_match('/hello/i', "HELLO World");  // Найдёт

// m — многострочный режим
$str = "Line1\nLine2\nLine3";
preg_match_all('/^\w+/m', $str, $matches);
// ["Line1", "Line2", "Line3"]

// s — точка включает перенос
$str = "Hello\nWorld";
preg_match('/Hello.World/s', $str);  // Найдёт

// u — UTF-8 (ОБЯЗАТЕЛЬНО для кириллицы!)
preg_match('/\w+/u', "Привет");  // Работает корректно

// x — расширенный режим (с комментариями)
$pattern = '/
    ^                   # Начало строки
    [\w\.\-]+           # Имя пользователя
    @                   # Символ @
    [\w\.\-]+           # Домен
    \.                  # Точка
    \w{2,}              # Зона (минимум 2 символа)
    $                   # Конец строки
/x';

13. preg_match и preg_match_all

preg_match — первое совпадение

php
<?php
$str = "My phone: +7 (999) 123-45-67";

// Простая проверка
if (preg_match('/\d{3}/', $str)) {
    echo "Найдены цифры";
}

// С захватом результата
if (preg_match('/\+7\s*\((\d{3})\)\s*(\d{3})-(\d{2})-(\d{2})/', $str, $matches)) {
    print_r($matches);
    /*
    [
        0 => "+7 (999) 123-45-67",
        1 => "999",
        2 => "123",
        3 => "45",
        4 => "67"
    ]
    */
}

// С флагом PREG_OFFSET_CAPTURE — позиции совпадений
preg_match('/\d+/', "Test123", $matches, PREG_OFFSET_CAPTURE);
// [0 => ["123", 4]] — значение и позиция

preg_match_all — все совпадения

php
<?php
$str = "Email: test@example.com, another@test.org";

// Найти все email
$pattern = '/[\w\.-]+@[\w\.-]+\.\w+/';
preg_match_all($pattern, $str, $matches);

print_r($matches[0]);
// ["test@example.com", "another@test.org"]

// С группами
$str = "Цены: 100 руб., 250 руб., 1500 руб.";
preg_match_all('/(\d+)\s*руб/', $str, $matches);

print_r($matches[0]);  // ["100 руб", "250 руб", "1500 руб"]
print_r($matches[1]);  // ["100", "250", "1500"]

// PREG_SET_ORDER — группировка по совпадениям
preg_match_all('/(\d+)\s*руб/', $str, $matches, PREG_SET_ORDER);
/*
[
    [0 => "100 руб", 1 => "100"],
    [0 => "250 руб", 1 => "250"],
    [0 => "1500 руб", 1 => "1500"]
]
*/

14. preg_replace — замена

Простая замена

php
<?php
$str = "Hello World";

// Замена по паттерну
echo preg_replace('/World/', 'PHP', $str);  // "Hello PHP"

// С модификаторами
echo preg_replace('/world/i', 'PHP', $str);  // "Hello PHP"

// Массив паттернов
$patterns = ['/\bHello\b/', '/\bWorld\b/'];
$replacements = ['Привет', 'Мир'];
echo preg_replace($patterns, $replacements, $str);  // "Привет Мир"

Замена с группами

php
<?php
// Использование $1, $2... для групп
$str = "Иванов Иван Сергеевич";
$result = preg_replace('/(\w+)\s+(\w+)\s+(\w+)/u', '$2 $3 $1', $str);
echo $result;  // "Иван Сергеевич Иванов"

// Форматирование телефона
$phone = "79991234567";
$formatted = preg_replace('/(\d)(\d{3})(\d{3})(\d{2})(\d{2})/', '+$1 ($2) $3-$4-$5', $phone);
echo $formatted;  // "+7 (999) 123-45-67"

// Удаление HTML-тегов (альтернатива strip_tags)
$html = "<p>Hello <b>World</b></p>";
echo preg_replace('/<[^>]+>/', '', $html);  // "Hello World"

preg_replace_callback — замена функцией

php
<?php
$str = "Цены: 100, 250, 1500";

// Увеличить все числа на 10%
$result = preg_replace_callback(
    '/\d+/',
    fn($matches) => $matches[0] * 1.1,
    $str
);
echo $result;  // "Цены: 110, 275, 1650"

// Форматирование дат
$str = "Даты: 2025-01-27, 2024-12-31";
$result = preg_replace_callback(
    '/(\d{4})-(\d{2})-(\d{2})/',
    fn($m) => "{$m[3]}.{$m[2]}.{$m[1]}",
    $str
);
echo $result;  // "Даты: 27.01.2025, 31.12.2024"

// Подсветка кода
$code = "Переменные: \$name, \$age, \$city";
$highlighted = preg_replace_callback(
    '/\$\w+/',
    fn($m) => "<span class='variable'>{$m[0]}</span>",
    $code
);

15. preg_split и preg_grep

preg_split — разбиение по паттерну

php
<?php
$str = "Hello,  World;  PHP";

// По разделителям (один или несколько)
$words = preg_split('/[\s,;]+/', $str);
// ["Hello", "World", "PHP"]

// По CamelCase
$str = "camelCaseString";
$parts = preg_split('/(?=[A-Z])/', $str);
// ["camel", "Case", "String"]

// С захватом разделителей
$str = "Hello, World; PHP";
$parts = preg_split('/([\s,;]+)/', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
// ["Hello", ", ", "World", "; ", "PHP"]

// Лимит
$parts = preg_split('/\s+/', "a b c d e", 3);
// ["a", "b", "c d e"]

preg_grep — фильтрация массива

php
<?php
$files = ["image.jpg", "document.pdf", "photo.png", "text.txt", "icon.gif"];

// Только изображения
$images = preg_grep('/\.(jpg|png|gif)$/', $files);
// ["image.jpg", "photo.png", "icon.gif"]

// Инверсия — всё кроме изображений
$notImages = preg_grep('/\.(jpg|png|gif)$/', $files, PREG_GREP_INVERT);
// ["document.pdf", "text.txt"]

// Фильтрация email
$strings = ["test@example.com", "not-email", "another@test.org", "123"];
$emails = preg_grep('/^[\w\.-]+@[\w\.-]+\.\w+$/', $strings);
// ["test@example.com", "another@test.org"]

16. Практические примеры регулярок

Валидация

php
<?php
class Validator {
    // Email
    public static function email(string $email): bool {
        return (bool) preg_match(
            '/^[\w\.\-]+@[\w\.\-]+\.[a-z]{2,}$/i',
            $email
        );
    }
    
    // Телефон (российский)
    public static function phone(string $phone): bool {
        $cleaned = preg_replace('/\D/', '', $phone);
        return (bool) preg_match('/^[78]?\d{10}$/', $cleaned);
    }
    
    // URL
    public static function url(string $url): bool {
        return (bool) preg_match(
            '#^https?://[\w\-\.]+(:\d+)?(/[\w\-\./]*)?(\?[\w=&\-]*)?$#i',
            $url
        );
    }
    
    // Дата (YYYY-MM-DD)
    public static function date(string $date): bool {
        if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m)) {
            return false;
        }
        return checkdate((int)$m[2], (int)$m[3], (int)$m[1]);
    }
    
    // Пароль (минимум 8 символов, буквы и цифры)
    public static function password(string $password): bool {
        return strlen($password) >= 8
            && preg_match('/[a-z]/i', $password)
            && preg_match('/\d/', $password);
    }
    
    // Только буквы (включая кириллицу)
    public static function alpha(string $str): bool {
        return (bool) preg_match('/^[\p{L}]+$/u', $str);
    }
    
    // Буквы и цифры
    public static function alphaNum(string $str): bool {
        return (bool) preg_match('/^[\p{L}\d]+$/u', $str);
    }
}

// Использование
var_dump(Validator::email("test@example.com"));  // true
var_dump(Validator::phone("+7 (999) 123-45-67"));  // true
var_dump(Validator::url("https://example.com/path?query=1"));  // true
var_dump(Validator::date("2025-02-30"));  // false (30 февраля не существует)

Парсинг данных

php
<?php
// Извлечение всех URL из текста
function extractUrls(string $text): array {
    preg_match_all(
        '#https?://[\w\-\.]+(?:/[\w\-\./]*)?(?:\?[\w=&\-]*)?#i',
        $text,
        $matches
    );
    return $matches[0];
}

$text = "Visit https://example.com or http://test.org/page?id=1";
print_r(extractUrls($text));
// ["https://example.com", "http://test.org/page?id=1"]

// Извлечение хештегов
function extractHashtags(string $text): array {
    preg_match_all('/#([\w\p{L}]+)/u', $text, $matches);
    return $matches[1];
}

$text = "Привет #мир! Это #PHP и #программирование";
print_r(extractHashtags($text));
// ["мир", "PHP", "программирование"]

// Парсинг CSV с кавычками
function parseCSVLine(string $line): array {
    preg_match_all('/"([^"]*)"|([^,]+)/', $line, $matches);
    $result = [];
    foreach ($matches[0] as $i => $match) {
        $result[] = $matches[1][$i] !== '' ? $matches[1][$i] : $matches[2][$i];
    }
    return $result;
}

$line = 'Иван,"Москва, Россия",25';
print_r(parseCSVLine($line));
// ["Иван", "Москва, Россия", "25"]

Трансформация текста

php
<?php
// Slug из строки
function slugify(string $str): string {
    $translit = [
        'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd',
        'е' => 'e', 'ё' => 'e', 'ж' => 'zh', 'з' => 'z', 'и' => 'i',
        'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n',
        'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't',
        'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'ts', 'ч' => 'ch',
        'ш' => 'sh', 'щ' => 'sch', 'ъ' => '', 'ы' => 'y', 'ь' => '',
        'э' => 'e', 'ю' => 'yu', 'я' => 'ya'
    ];
    
    $str = mb_strtolower($str);
    $str = strtr($str, $translit);
    $str = preg_replace('/[^a-z0-9]+/', '-', $str);
    $str = trim($str, '-');
    
    return $str;
}

echo slugify("Привет, Мир! Это PHP");  // "privet-mir-eto-php"

// CamelCase в snake_case
function toSnakeCase(string $str): string {
    return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $str));
}

echo toSnakeCase("camelCaseString");  // "camel_case_string"

// snake_case в CamelCase
function toCamelCase(string $str): string {
    return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $str))));
}

echo toCamelCase("snake_case_string");  // "snakeCaseString"

// Выделение слов в тексте (подсветка поиска)
function highlightWords(string $text, array $words): string {
    $pattern = '/\b(' . implode('|', array_map('preg_quote', $words)) . ')\b/iu';
    return preg_replace($pattern, '<mark>$1</mark>', $text);
}

$text = "PHP - это язык программирования для веб-разработки";
echo highlightWords($text, ["PHP", "язык"]);
// "<mark>PHP</mark> - это <mark>язык</mark> программирования..."

17. Упражнения

Упражнение 1: Строковые функции (15 минут)

php
<?php
$str = "  Hello, World! This is PHP.  ";

// 1. Убери пробелы по краям
// 2. Преобразуй в верхний регистр
// 3. Замени "World" на "Universe"
// 4. Подсчитай количество слов
// 5. Разбей на массив слов
// 6. Получи первые 5 символов

Упражнение 2: Форматирование данных (20 минут)

php
<?php
// 1. Напиши функцию форматирования телефона
// formatPhone("79991234567") → "+7 (999) 123-45-67"

// 2. Напиши функцию форматирования размера файла
// formatFileSize(1536) → "1.5 KB"
// formatFileSize(1048576) → "1 MB"

// 3. Напиши функцию обрезки текста с многоточием
// truncate("Hello World PHP", 10) → "Hello W..."
// truncate("Hi", 10) → "Hi"

Упражнение 3: Регулярные выражения (25 минут)

php
<?php
// 1. Напиши regex для проверки российского паспорта (XX XX XXXXXX)
// 2. Извлеки все числа из строки "Цена: 100 руб., скидка 15%, итого: 85 руб."
// 3. Найди все слова, начинающиеся с заглавной буквы
// 4. Замени все множественные пробелы на один
// 5. Проверь, является ли строка валидным IPv4 адресом

Упражнение 4: Практические задачи (30 минут)

php
<?php
$html = '<a href="https://example.com">Example</a> и <a href="https://test.org">Test</a>';

// 1. Извлеки все ссылки (URL и текст) из HTML
// Результат: [["url" => "https://example.com", "text" => "Example"], ...]

// 2. Напиши функцию очистки HTML от опасных тегов и атрибутов
// Оставить только: p, a (только href), strong, em

// 3. Напиши функцию автоматического создания ссылок из URL в тексте
// "Visit https://example.com for more" → "Visit <a href="...">https://example.com</a> for more"

18. Вопросы для самопроверки

  1. Чем отличаются одинарные и двойные кавычки?

  2. Почему для кириллицы нужно использовать mb_* функции?

  3. Как безопасно вывести пользовательский ввод в HTML?

  4. Чем отличается strpos() от str_contains()?

  5. Что делает модификатор u в регулярках?

  6. В чём разница между жадными и ленивыми квантификаторами?

  7. Как получить все совпадения, а не только первое?

  8. Что такое группы захвата и обратные ссылки?


19. Частые ошибки

Ошибка 1: Проверка strpos через ==

php
<?php
$str = "Hello World";

// ❌ Неправильно (0 == false)
if (strpos($str, "Hello")) {
    echo "Найдено";  // Не выполнится!
}

// ✅ Правильно
if (strpos($str, "Hello") !== false) {
    echo "Найдено";
}

// ✅ PHP 8+
if (str_contains($str, "Hello")) {
    echo "Найдено";
}

Ошибка 2: strlen для UTF-8

php
<?php
$str = "Привет";

// ❌ Неправильно — считает байты
echo strlen($str);     // 12 (не 6!)

// ✅ Правильно
echo mb_strlen($str);  // 6

Ошибка 3: Забыт модификатор u

php
<?php
$str = "Привет мир";

// ❌ Неправильно — \w не работает с кириллицей
preg_match('/\w+/', $str, $matches);
// $matches[0] может быть пустым или мусором

// ✅ Правильно
preg_match('/\w+/u', $str, $matches);
// $matches[0] = "Привет"

// Или используй \p{L} для букв любого языка
preg_match('/[\p{L}]+/u', $str, $matches);

Ошибка 4: XSS-уязвимость

php
<?php
$userInput = $_GET['name'];

// ❌ ОПАСНО!
echo "Привет, $userInput!";

// ✅ Безопасно
echo "Привет, " . htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') . "!";

Ошибка 5: Неэкранированные спецсимволы в regex

php
<?php
$search = "C++";

// ❌ Ошибка — + это квантификатор
preg_match("/$search/", "Learn C++");

// ✅ Экранировать пользовательский ввод
preg_match('/' . preg_quote($search, '/') . '/', "Learn C++");

20. Шпаргалка по регулярным выражениям

┌─────────────────────────────────────────────────────────────────┐
│                    ШПАРГАЛКА REGEX                              │
├─────────────────────────────────────────────────────────────────┤
│ МЕТАСИМВОЛЫ                                                     │
│   .     Любой символ          ^     Начало строки              │
│   $     Конец строки          |     ИЛИ                        │
│   \     Экранирование         ()    Группа захвата             │
│   []    Класс символов        [^]   Отрицание класса           │
├─────────────────────────────────────────────────────────────────┤
│ КЛАССЫ СИМВОЛОВ                                                 │
│   \d    Цифра [0-9]           \D    Не цифра                   │
│   \w    Слово [a-zA-Z0-9_]    \W    Не слово                   │
│   \s    Пробел                \S    Не пробел                  │
│   \b    Граница слова         \p{L} Буква (Unicode)            │
├─────────────────────────────────────────────────────────────────┤
│ КВАНТИФИКАТОРЫ                                                  │
│   *     0 или более           +     1 или более                │
│   ?     0 или 1               {n}   Ровно n                    │
│   {n,}  n или более           {n,m} От n до m                  │
│   *? +? Ленивые версии                                         │
├─────────────────────────────────────────────────────────────────┤
│ МОДИФИКАТОРЫ                                                    │
│   i     Регистронезависимый   m     Многострочный              │
│   s     Точка включает \n     u     UTF-8 (ОБЯЗАТЕЛЕН!)        │
│   x     Расширенный режим                                      │
├─────────────────────────────────────────────────────────────────┤
│ ФУНКЦИИ PHP                                                     │
│   preg_match()          Первое совпадение                      │
│   preg_match_all()      Все совпадения                         │
│   preg_replace()        Замена                                 │
│   preg_replace_callback() Замена функцией                      │
│   preg_split()          Разбиение                              │
│   preg_grep()           Фильтрация массива                     │
│   preg_quote()          Экранирование спецсимволов             │
└─────────────────────────────────────────────────────────────────┘

Резюме главы

┌────────────────────────────────────────────────────────────────┐
│                      ЗАПОМНИ ГЛАВНОЕ                           │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  СТРОКИ                                                        │
│  • 'одинарные' — без интерполяции                             │
│  • "двойные" — с интерполяцией $var и спецсимволами           │
│  • Для UTF-8 используй mb_* функции                           │
│  • htmlspecialchars() — защита от XSS                         │
│                                                                │
│  ОСНОВНЫЕ ФУНКЦИИ                                              │
│  • strlen/mb_strlen — длина                                   │
│  • strpos/mb_strpos — поиск позиции                           │
│  • str_contains/starts_with/ends_with — проверки (PHP 8+)     │
│  • str_replace — замена подстроки                             │
│  • explode/implode — разбиение/объединение                    │
│  • sprintf — форматирование                                   │
│  • trim — обрезка пробелов                                    │
│                                                                │
│  РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ                                          │
│  • /паттерн/модификаторы — синтаксис                          │
│  • u — ОБЯЗАТЕЛЕН для кириллицы!                              │
│  • preg_match — первое совпадение                             │
│  • preg_match_all — все совпадения                            │
│  • preg_replace — замена                                      │
│  • preg_replace_callback — замена функцией                    │
│  • preg_quote — экранирование пользовательского ввода         │
│                                                                │
│  БЕЗОПАСНОСТЬ                                                  │
│  • Всегда экранируй вывод: htmlspecialchars()                 │
│  • Экранируй regex: preg_quote()                              │
│  • Валидируй ввод перед использованием                        │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Следующая глава: Глава 1.6: Работа с файлами — чтение, запись, загрузка файлов от пользователя, безопасность

Выпущено под лицензией MIT