fkdev Adminv1.0.0

Belgeler · DataTables

Tablolar, PHP ile.

DataTables bir tabloya arama, sıralama ve sayfalama ekler. Küçük veride tarayıcıda, büyük veride PHP ile sunucuda çalışır.

01CDN ile kurulum

HTML
<link rel="stylesheet" href="https://cdn.datatables.net/2.1.8/css/dataTables.dataTables.min.css" integrity="sha384-bK2L0Q3Sn2fB8YZTIjKejpTUJXd8xNtKn4RcIm7KM3EX3orMlOI31hwtNH0iyky/" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs" crossorigin="anonymous" defer></script>
<script src="https://cdn.datatables.net/2.1.8/js/dataTables.min.js" integrity="sha384-MgwUq0TVErv5Lkj/jIAgQpC+iUIqwhwXxJMfrZQVAOhr++1MR02yXH8aXdPc3fk0" crossorigin="anonymous" defer></script>

DataTables 2 hâlâ jQuery’ye ihtiyaç duyar; sıralama önemlidir: önce jQuery, sonra DataTables.

02Tablo HTML’i

HTML
<section class="ad-panel ad-dt">
  <table id="siparisler">
    <thead><tr><th>Sipariş</th><th>Müşteri</th><th>Tarih</th><th>Tutar</th><th>Durum</th></tr></thead>
    <tbody>
      <tr>
        <td><strong>#FK-10480</strong></td>
        <td>Ayşe Kaya</td>
        <td data-order="2026-10-24T09:42">24.10.2026 09:42</td>
        <td data-order="2340">₺2.340</td>
        <td data-search="Bekliyor"><span class="fk-badge fk-badge-warning">Bekliyor</span></td>
      </tr>
    </tbody>
  </table>
</section>
ÖznitelikNe işe yarar?
data-orderSıralamada görünen metin yerine bu değer kullanılır (tarih, para)
data-searchAramada rozetin HTML’i yerine düz metin kullanılır
.ad-dtDataTables’ı fkdev temasına bürür (arama hapı, sayfalama, satırlar)

03Başlatma ve Türkçe

JavaScriptassets/admin.js (özet)
const dt = new DataTable('#siparisler', {
  pageLength: 10,
  lengthMenu: [10, 25, 50],
  order: [[2, 'desc']],
  columnDefs: [{ orderable: false, searchable: false, targets: -1 }],
  language: {
    decimal: ',', thousands: '.',
    info: '_TOTAL_ siparişten _START_–_END_ arası',
    infoEmpty: 'Gösterilecek sipariş yok',
    infoFiltered: '(toplam _MAX_ sipariş içinde)',
    lengthMenu: 'Sayfada _MENU_ kayıt',
    search: '',
    searchPlaceholder: 'Sipariş no, müşteri, şehir…',
    zeroRecords: 'Aramana uyan sipariş bulunamadı',
    paginate: { first: '«', last: '»', next: '›', previous: '‹' }
  }
});

document.querySelectorAll('input[name="durum"]').forEach((r) => {
  r.addEventListener('change', () => {
    dt.column(5).search(r.value ? '^' + r.value + '$' : '', true, false).draw();
  });
});

04Sunucu taraflı: PHP + PDO

Binlerce satırda tüm tabloyu sayfaya basmak yerine DataTables her sayfa için PHP’ye istek atar; PHP yalnızca o 10 satırı döndürür.

JavaScript
new DataTable('#siparisler', {
  serverSide: true,
  processing: true,
  ajax: { url: '/api/siparisler.php', type: 'GET' },
  order: [[2, 'desc']],
  columns: [null, null, null, null, { className: 'dt-type-numeric' }, null]
});
PHPornekler/php/api/siparisler.php
<?php
declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';

require_login(['admin', 'editor']);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');

$columns = [
    0 => 'o.code',
    1 => 'c.name',
    2 => 'o.created_at',
    3 => 'c.city',
    4 => 'o.total',
    5 => 'o.status',
];

$draw   = (int) ($_GET['draw'] ?? 0);
$start  = max(0, (int) ($_GET['start'] ?? 0));
$length = (int) ($_GET['length'] ?? 10);
$length = $length < 1 || $length > 100 ? 10 : $length;
$search = trim((string) ($_GET['search']['value'] ?? ''));

$orderCol = (int) ($_GET['order'][0]['column'] ?? 2);
$orderDir = strtolower((string) ($_GET['order'][0]['dir'] ?? 'desc')) === 'asc' ? 'ASC' : 'DESC';
$orderBy  = $columns[$orderCol] ?? 'o.created_at';

$from   = ' FROM orders o JOIN customers c ON c.id = o.customer_id ';
$where  = '';
$params = [];

if ($search !== '') {
    $where = ' WHERE (o.code LIKE :q1 OR c.name LIKE :q2 OR c.city LIKE :q3 OR o.status LIKE :q4) ';
    $like = '%' . addcslashes($search, '%_\\') . '%';
    foreach ([':q1', ':q2', ':q3', ':q4'] as $k) {
        $params[$k] = $like;
    }
}

$status = (string) ($_GET['columns'][5]['search']['value'] ?? '');
$allowed = ['Bekliyor', 'Hazırlanıyor', 'Kargoda', 'Teslim edildi', 'İade'];
$status = trim($status, '^$');
if (in_array($status, $allowed, true)) {
    $where .= ($where ? ' AND ' : ' WHERE ') . ' o.status = :status ';
    $params[':status'] = $status;
}

$total = (int) db()->query('SELECT COUNT(*) FROM orders')->fetchColumn();

$stmt = db()->prepare('SELECT COUNT(*)' . $from . $where);
$stmt->execute($params);
$filtered = (int) $stmt->fetchColumn();

$sql = 'SELECT o.code, c.name, o.created_at, c.city, o.total, o.status'
     . $from . $where
     . " ORDER BY $orderBy $orderDir LIMIT :start, :length";
$stmt = db()->prepare($sql);
foreach ($params as $k => $v) {
    $stmt->bindValue($k, $v);
}
$stmt->bindValue(':start', $start, PDO::PARAM_INT);
$stmt->bindValue(':length', $length, PDO::PARAM_INT);
$stmt->execute();

$data = [];
foreach ($stmt as $row) {
    $data[] = [
        e($row['code']),
        e($row['name']),
        e(date('d.m.Y H:i', strtotime($row['created_at']))),
        e($row['city']),
        number_format((float) $row['total'], 2, ',', '.') . ' ₺',
        e($row['status']),
    ];
}

echo json_encode([
    'draw'            => $draw,
    'recordsTotal'    => $total,
    'recordsFiltered' => $filtered,
    'data'            => $data,
], JSON_UNESCAPED_UNICODE);
İstek parametresiAnlamı
drawİstek sayacı; aynen geri döndürülür
start / lengthKaçıncı satırdan, kaç satır
search[value]Genel arama kutusu
order[0][column] / [dir]Sıralanan sütun ve yön
columns[i][search][value]Sütun filtresi (durum segmenti)
JSONYanıt biçimi
{
  "draw": 3,
  "recordsTotal": 12840,
  "recordsFiltered": 214,
  "data": [["#FK-10480", "Ayşe Kaya", "24.10.2026 09:42", "İstanbul", "2.340,00 ₺", "Bekliyor"]]
}

05Güvenlik notları

  • Sütun adını asla istekten alma. order[0][column] bir sayıdır; onu kendi $columns dizinde karşılığa çevir (beyaz liste).
  • Yönü sabitle: yalnızca ASC ya da DESC.
  • Limiti sınırla: length en fazla 100; yoksa biri length=1000000 ile sunucunu yorar.
  • Hazırlanmış sorgu: arama değeri her zaman parametre olarak bağlanır; LIKE içindeki % ve _ kaçırılır.
  • Çıktıyı kaçır: hücreye giden her metin e() (htmlspecialchars) ile kaçırılır; yoksa müşteri adına yazılan bir betik admin panelinde çalışır.
  • Yetki: uç nokta require_login(['admin', 'editor']) ile korunur.

06Performans

SQL
CREATE INDEX idx_orders_created ON orders (created_at);
CREATE INDEX idx_orders_status ON orders (status, created_at);

Sıraladığın ve filtrelediğin her sütunun indeksi olsun. LIKE '%kelime%' indeks kullanamaz; çok büyük tablolarda FULLTEXT indeks ve MATCH … AGAINST kullan.

07Satır işlemleri (silme)

JavaScript
document.querySelector('#siparisler').addEventListener('click', async (e) => {
  const btn = e.target.closest('[data-sil]');
  if (!btn) return;
  if (!(await FK.confirm({ title: 'Sipariş silinsin mi?', ok: 'Sil', danger: true }))) return;
  const res = await fetch('/api/siparis-sil.php', {
    method: 'POST',
    headers: { 'X-CSRF-Token': document.querySelector('meta[name="csrf"]').content },
    body: new URLSearchParams({ id: btn.dataset.sil })
  });
  if (res.ok) { dt.row(btn.closest('tr')).remove().draw(false); FK.toast('Silindi', 'success'); }
  else FK.toast('Silinemedi', 'danger');
});