Карточки в PHP: подробное руководство для веб-разработчика
Карточки PHP: подходы к формированию веб-элементов
Основное решение: карточки через PDO и цикл foreach
Цель: получение записей из базы данных MySQL и вывод каждой записи в виде карточки.
Шаги:
- Подключение к базе с помощью PDO.
- Подготовка и выполнение запроса SELECT.
- Извлечение всех строк в массив.
- Проход по массиву циклом foreach и формирование HTML для каждой карточки.
Пример кода:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->query('SELECT id, title, content, image FROM cards');
$cards = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($cards as $card) {
echo '<div class="card">';
echo '<img src="' . htmlspecialchars($card['image']) . '" alt="...">';
echo '<h3>' . htmlspecialchars($card['title']) . '</h3>';
echo '<p>' . nl2br(htmlspecialchars($card['content'])) . '</p>';
echo '</div>';
}
Cards php card (карточки php)
Пояснение:
PDO предоставляет единый интерфейс для работы с разными СУБД. Метод query() выполняет запрос, fetchAll() извлекает все строки в ассоциативный массив. Цикл foreach перебирает каждую запись. Функция htmlspecialchars() защищает от XSS-атак. nl2br() преобразует переводы строк в теги <br>. Для изображений используется атрибут alt с описанием.
Типичные ошибки и их решения:
- Ошибка подключения: PDOException. Решение: обернуть код в try-catch, использовать настройки error mode.
- XSS-уязвимости: если не использовать htmlspecialchars(), злоумышленник может внедрить скрипты. Решение: всегда экранировать вывод.
- Отсутствие обработки пустого результата: если запрос не вернул строк, карточки не отобразятся. Решение: проверять count($cards) и выводить сообщение.
Как создать карточки с помощью процедурного mysqli?
Цель: использовать традиционный MySQLi при работе с устаревшими проектами.
Пример кода:
$conn = mysqli_connect('localhost', 'user', 'pass', 'test');
if (!$conn) { die('Connection failed'); }
$result = mysqli_query($conn, 'SELECT * FROM cards');
while ($row = mysqli_fetch_assoc($result)) {
echo '<div class="card">';
echo '<h3>' . htmlspecialchars($row['title']) . '</h3>';
echo '<p>' . htmlspecialchars($row['content']) . '</p>';
echo '</div>';
}
mysqli_close($conn);
Пояснение: mysqli_connect() открывает соединение, mysqli_query() выполняет запрос. Цикл while перебирает строки. В конце соединение закрывается. В отличие от PDO, здесь нет автоматической подстановки параметров, поэтому для защищённых запросов нужны prepared statements.
Типичные проблемы:
- SQL-инъекции при использовании конкатенации. Решение: использовать prepared statements (mysqli_prepare, mysqli_stmt_bind_param).
- Утечка ресурсов при незакрытом соединении. Решение: закрывать соединение после завершения работы.
Случаи использования:
- Поддержка легаси-кода.
- Проекты, где PDO недоступен.
Как вывести карточки из статического JSON-файла?
Цель: прототипирование без базы данных, хранение данных в JSON.
Пример кода:
$json = file_get_contents('cards.json');
$cards = json_decode($json, true);
foreach ($cards as $card) {
echo '<div class="card">';
echo '<h3>' . htmlspecialchars($card['name']) . '</h3>';
echo '<p>' . htmlspecialchars($card['description']) . '</p>';
echo '</div>';
}
Пояснение: file_get_contents() читает файл, json_decode() преобразует в массив. Важно проверять успешность декодирования.
Ошибки и решения:
- Файл не найден: file_get_contents() возвращает false. Решение: проверять на !== false.
- Некорректный JSON: json_decode() возвращает null. Решение: использовать json_last_error() для диагностики.
Случаи использования:
- Макеты, прототипы.
- Небольшие объёмы данных.
Как организовать карточки с помощью шаблонизатора Twig?
Цель: отделить логику от представления, переиспользовать шаблоны.
Установка через Composer:
composer require twig/twig
Пример кода (index.php):
require 'vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader);
echo $twig->render('cards.html.twig', ['cards' => $cards]);
Шаблон templates/cards.html.twig:
{% for card in cards %}
<div class="card">
<h3>{{ card.title|e }}</h3>
<p>{{ card.content|e|nl2br }}</p>
</div>
{% endfor %}
Пояснение: Twig автоматически экранирует вывод через |e. Фильтр nl2br аналогичен функции PHP. Шаблон сохраняется в отдельном файле.
Проблемы и решения:
- Ошибка загрузки шаблона: не найден файл. Решение: проверить путь в FilesystemLoader.
- Не экранированный вывод: если забыть |e, возможен XSS. Решение: использовать автоэкранирование (по умолчанию включено для HTML).
Случаи использования:
- Сложные проекты с множеством представлений.
- Команда разработчиков, где дизайнеры работают с шаблонами.
Как подгружать карточки асинхронно без перезагрузки страницы?
Цель: улучшить пользовательский опыт, загружать данные динамически.
PHP endpoint (api/cards.php):
header('Content-Type: application/json');
$pdo = new PDO(...);
$stmt = $pdo->query('SELECT * FROM cards');
$cards = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($cards);
Клиентский JavaScript (index.html):
fetch('api/cards.php')
.then(response => response.json())
.then(cards => {
const container = document.getElementById('cards');
cards.forEach(card => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `${card.title}
${card.content}
`;
container.appendChild(div);
});
});
Пояснение: PHP возвращает JSON. JavaScript обрабатывает его и динамически создаёт карточки. Важно экранировать данные на клиенте (использовать innerText или создать элементы).
Ошибки и решения:
- CORS: если API на другом домене, браузер блокирует запрос. Решение: установить заголовки Access-Control-Allow-Origin.
- XSS через innerHTML: если данные содержат теги, возможна инъекция. Решение: использовать document.createTextNode() или sanitize.
Случаи использования:
- Одностраничные приложения (SPA).
- Бесконечная прокрутка или ленивая загрузка.
Расширенные примеры создания карточек
Пример 1: Пагинация и поиск с использованием PDO
Код (index.php):
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 6;
$offset = ($page - 1) * $perPage;
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$where = '';
$params = [];
if ($search) {
$where = 'WHERE title LIKE :search';
$params[':search'] = '%' . $search . '%';
}
// Общее количество для пагинации
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM cards $where");
$countStmt->execute($params);
$total = $countStmt->fetchColumn();
$totalPages = ceil($total / $perPage);
// Карточки для текущей страницы
$stmt = $pdo->prepare("SELECT * FROM cards $where LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
foreach ($params as $key => $val) {
$stmt->bindValue($key, $val);
}
$stmt->execute();
$cards = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($cards as $card) {
echo '<div class="card">';
echo '<h3>' . htmlspecialchars($card['title']) . '</h3>';
echo '<p>' . htmlspecialchars($card['content']) . '</p>';
echo '</div>';
}
// Вывод ссылок пагинации
for ($i = 1; $i <= $totalPages; $i++) {
$link = "?page=$i&search=" . urlencode($search);
echo "<a href='$link'>$i</a> ";
}
Результат (пример вывода):
<div class="card"><h3>Карточка 1</h3><p>Описание 1</p></div> <div class="card"><h3>Карточка 2</h3><p>Описание 2</p></div> <a href='?page=1&search='>1</a> <a href='?page=2&search='>2</a>
Пояснение: Используются подготовленные запросы с именованными параметрами. Пагинация вычисляется через LIMIT и OFFSET. Поиск реализован с помощью LIKE. Важно экранировать URL в ссылках.
Пример 2: Кэширование готового HTML карточек
Цель: снизить нагрузку на базу данных и ускорить загрузку.
Код с использованием кэша:
$cacheFile = 'cache/cards.html';
$cacheTime = 3600; // 1 час
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
echo file_get_contents($cacheFile);
exit;
}
ob_start();
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$cards = $pdo->query('SELECT * FROM cards')->fetchAll(PDO::FETCH_ASSOC);
foreach ($cards as $card) {
echo '<div class="card">';
echo '<h3>' . htmlspecialchars($card['title']) . '</h3>';
echo '<p>' . htmlspecialchars($card['content']) . '</p>';
echo '</div>';
}
$html = ob_get_clean();
file_put_contents($cacheFile, $html);
echo $html;
Результат:
Сгенерированный HTML выводится мгновенно при повторном запросе в течение часа. Файл cache/cards.html обновляется только после истечения времени или при ручной очистке.
Пояснение: ob_start() и ob_get_clean() захватывают вывод. file_put_contents() сохраняет в файл. При следующем запросе, если кэш актуален, файл читается напрямую.
Пример 3: ООП с абстрактным классом Card
Цель: переиспользовать код для разных типов карточек (товары, пользователи, статьи).
Код:
abstract class Card {
protected $data;
public function __construct(array $data) {
$this->data = $data;
}
abstract public function render(): string;
}
class ProductCard extends Card {
public function render(): string {
return sprintf(
'<div class="card product"><h3>%s</h3><p>Цена: %s руб.</p></div>',
htmlspecialchars($this->data['name']),
htmlspecialchars($this->data['price'])
);
}
}
class UserCard extends Card {
public function render(): string {
return sprintf(
'<div class="card user"><h3>%s</h3><p>Email: %s</p></div>',
htmlspecialchars($this->data['username']),
htmlspecialchars($this->data['email'])
);
}
}
// Использование
$products = [['name' => 'Ноутбук', 'price' => 50000], ['name' => 'Мышь', 'price' => 1500]];
$users = [['username' => 'ivan', 'email' => 'ivan@ex.ru']];
foreach ($products as $p) {
$card = new ProductCard($p);
echo $card->render();
}
foreach ($users as $u) {
$card = new UserCard($u);
echo $card->render();
}
Результат:
<div class="card product"><h3>Ноутбук</h3><p>Цена: 50000 руб.</p></div> <div class="card product"><h3>Мышь</h3><p>Цена: 1500 руб.</p></div> <div class="card user"><h3>ivan</h3><p>Email: ivan@ex.ru</p></div>
Пояснение: Абстрактный класс определяет интерфейс. Каждый конкретный класс реализует свою верстку. Такой подход упрощает добавление новых типов карточек.
Пример 4: Бесконечная прокрутка (infinite scroll) с PHP API
Цель: загружать дополнительные карточки по мере прокрутки страницы.
PHP endpoint (api/load.php):
$page = (int)($_GET['page'] ?? 1);
$perPage = 4;
$offset = ($page - 1) * $perPage;
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM cards LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$cards = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($cards);
Клиентская часть (JavaScript):
let page = 1;
let loading = false;
window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100 && !loading) {
loading = true;
page++;
fetch(`api/load.php?page=${page}`)
.then(response => response.json())
.then(cards => {
cards.forEach(card => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `${card.title}
${card.content}
`;
document.getElementById('cards').appendChild(div);
});
loading = false;
});
}
});
Результат:
При прокрутке вниз автоматически подгружаются следующие 4 карточки, добавляясь в конец списка. Страница не перезагружается.
Пояснение: Клиент отслеживает положение скролла и отправляет запрос с номером страницы. PHP возвращает следующую порцию данных в JSON. Важно установить флаг loading для предотвращения одновременных запросов.