v2.5.2
Giriş yap

Yorum Cevaplandırma

coder
323 defa görüntülendi

Merhaba arkadaşlar.

Bir makalemiz var.
Ona yorum yapılıyor. Ama yorum sisteminde kişiler, bir yorum altında bir birine cevap verebiliyor.
Aşağıdaki resimde olduğu gibi.

Şimdi bunu yapabilmem için şunlar olması gerekiyor.

makaleler tablosu
makale_id | makale_icerik

uyeler tablosu
uye_id | uye_adi

yorumlar tablosu
yorum_id | makale_id | uye_id | yorum_icerik

Şimdi takıldığım nokta şu.

Bir yorum altında kişileri nasıl birbirine cevap verdirebilirim.
Tablo yapısı nasıl olmalı?

Cevap yaz
Cevaplar (12)
ebykdrms
291 gün önce

Evet, orada da eksik yazmışım.

ana_yorum Düzeltmeler doğru.

 // Bu kısmı
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum_id'] ?>">Cevapla</a>
 
 // Bu şekilde değiştirdim.
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum']['yorum_id'] ?>">Cevapla</a>

cevap_yorum burada da cevapla kısmı üsttekiyle aynı olmalı ki hep ana yoruma bağlı kalsın.

 // Bu kısmı
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum_id'] ?>">Cevapla</a>
 
 // Bu şekilde değiştirdim.
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $altYorum['yorum_id'] ?>">Cevapla</a>
 // yerine şöyle olmalı 
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum']['yorum_id'] ?>">Cevapla</a>
coder
290 gün önce

ebykdrms teşekkür ederim.

coder
293 gün önce

@ebykdrms aşağıdaki hatayı alıyorum hocam.

showVar($duzenlenmisYorumlar);

GÜNCELLEME
Aşağıdaki alanları gösterdiğim şekilde değiştirdiğimde hatalar kayboldu.
Herhangi bir yanlışlık var mı?

ana_yorum

 // Bu kısmı
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum_id'] ?>">Cevapla</a>
 
 // Bu şekilde değiştirdim.
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum']['yorum_id'] ?>">Cevapla</a>

cevap_yorum

 // Bu kısmı
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum_id'] ?>">Cevapla</a>
 
 // Bu şekilde değiştirdim.
 <a href="#" class="mt-2" data-ust-yorum-id="<?= $altYorum['yorum_id'] ?>">Cevapla</a>
ebykdrms
293 gün önce

İlk fonksiyonda eksik yazmışım gibi görünüyor.
Bu yüzden hiyerarşi düzgün oluşamamış.
else içindeki 'yorum_id' kısmını 'ust_yorum_id' olarak düzelttim:

$yorumlar = tumYorumlar($mak_id);
$duzenlenmisYorumlar = [];
foreach($yorumlar as $yorum) {
    // Baktığımız yorumun üst yorumu yoksa, ana yorum olarak eklensin.
    if($yorum['ust_yorum_id'] === 0) {
      $duzenlenmisYorumlar[ 'yorum_'.$yorum['yorum_id'] ] = ['yorum'=>$yorum, 'alt_yorumlar'=>[]];
    }
    // Baktığımız yorumun üst yorumu varsa, o yorumun alt yorumu olarak eklensin
    else {
      $duzenlenmisYorumlar[ 'yorum_'.$yorum['ust_yorum_id'] ]['alt_yorumlar'][] = $yorum;
    }
}

Ayrıca ikinci foreach içinde de patlama ihtimalini engellemek gerek.

<?php foreach($duzenlenmisYorumlar as $key=>$data) { ?>
    <?php if(!isset($data['yorum'])) continue; ?>
    <div class="ana_yorum" data-key="<?= $key ?>">
        <div><?= $data['yorum']['yorum_adi'] ?></div>
        <div><?= $data['yorum']['yorum_icerik'] ?></div>
        <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum_id'] ?>">Cevapla</a>
        <?php foreach($data['alt_yorumlar'] as $altYorum) { ?>
            <div class="cevap_yorum">
                <div><?= $altYorum['yorum_adi'] ?></div>
                <div><?= $altYorum['yorum_icerik'] ?></div>
                <a href="#" class="mt-2" data-ust-yorum-id="<?= $data['yorum_id'] ?>">Cevapla</a>
            </div>
        <?php } ?>
    </div>
<?php } ?>

Not: Bu sistem, yazılan bir ana yorum altına cevaplar eklenmesini sağlar.
Ama yoruma verilen cevaba verilen cevap şeklinde düzen oluşturmaz.
Bu nedenle verilen cevaplar her zaman ust_yorum_id'ye bakılarak yapılmalı.
O yüzden belki değiştirip kullanabilmeniz için <a> taglarınıza, üst yorum id'lerini bir attribute olarak ekledim.

Eğer çalışmazsa, showVar() fonksiyonu ile yeniden $duzenlenmisYorumlar dizisine bakabiliriz.

coder
293 gün önce

@ebykdrms dediğiniz gibi yaptım. Aşağıdaki şekilde bir çıktı alıyorum.

showVar($yorumlar);

showVar($duzenlenmisYorumlar);

ebykdrms
293 gün önce

Bu hata, 24.satırda array'in "yorum" diye bir key'ine ulaşılmaya çalışıldığını ama öyle bir key olmadığını söylüyor.
$yorumlar değişkeni içinde nasıl bir veri geliyor?
$duzenlenmisYorumlar değişkeni içinde nasıl bir veri oluşuyor?
Bunları görüp ona göre bir yaklaşım üretebiliriz.
Bu değişkenlerin değerlerini paylaşabilir misiniz?

function showVar($var) {
    echo '<pre>';
    var_dump($var);
    echo '</pre>';
}

Bu şekilde bir fonksiyon oluşturup, bu fonksiyon ile değişkenlerin değerini alabilirsiniz:

$yorumlar = tumYorumlar($mak_id);
$duzenlenmisYorumlar = [];
foreach($yorumlar as $yorum) {
    // Baktığımız yorumun üst yorumu yoksa, ana yorum olarak eklensin.
    if($yorum['ust_yorum_id'] === 0) {
      $duzenlenmisYorumlar[ 'yorum_'.$yorum['yorum_id'] ] = ['yorum'=>$yorum, 'alt_yorumlar'=>[]];
    }
    // Baktığımız yorumun üst yorumu varsa, o yorumun alt yorumu olarak eklensin
    else {
      $duzenlenmisYorumlar[ 'yorum_'.$yorum['yorum_id'] ]['alt_yorumlar'][] = $yorum;
    }
}
showVar($yorumlar);
showVar($duzenlenmisYorumlar);
coder
296 gün önce

@fatihkurtl teşekkür ederim.

@ebykdrms hocam sizin vermiş olduğunuz kodlarıda denemek istedim. Ancak şöyle bir hata alıyorum. Çözemedim?

ebykdrms
296 gün önce

Üst Not: @fatihkurtl benden önce cevap vermiş. Benim cevabım biraz daha farklı oldu. Her yiğidin yoğurt yiyişi farklı tabi. :) Aşağıda benim kendimce cevabım var:

~ Kişi makale oku linkine tıkladığında html sayfasını mı görecek?
~ Evet. Direkt html sayfasını görmeli. PHP'ye hiç iş ilişmemeli. Veritabanını meşgul etmemeli. Çünkü makaleler sürekli güncellenen, anlık işleme girmesi gereken içerik değildirler. Mesela bir e-ticaret sitesinde ürün detay sayfaları da böyledir. Buralarda nadiren yapılan güncellemeler olur. Bu durumda da yeni bir cache dosyası oluşturulur. Ama mesela kullanıcının sepeti, anlık olarak işlem görmesi gereken, hassas bir yerdir. Mutlaka veritabanından kontrolü gerekir.

~ HTML sayfası PHP sayfasına include mu edilecek?
~ Hayır. HTML sayfası direkt bir dosya olarak oluşturulmalı ve mesela FTP'yle sunucudaki dosyalarınıza baktığınızda html uzantılı bir dosya olarak görülebilmeli. PHP olmasa bile kendi başına çalışabilir bir dosya olmalı. Böyle bir çıktıyı nasıl üretebileceğinizi şimdilik geçiyorum ama aslında zor değil. Sayfayı oluşturdunuz diyelim. Bu sayfaya yönlendirme işlemi PHP .htaccess dosyası ile sağlanabilir. Dosyanın adı aslında url'deki adıdır. Mesela yapay-zekanin-yukselisi.html adlı bir dosya. .htaccess dosyasında uygun bir yönlendirmeyle kullanıcının bu sayfaya http://siteadi.com/makale/yapay-zekanin-yukselisi url'siyle ulaşması sağlanmalı.
Örneğin bu dosyaları yönetim panelinden düzenledikten sonra /makaleler adlı bir klasöre PHP yardımıyla kaydettiniz. Şu an /makaleler/yapay-zekanin-yukselisi.html diye bir dosyanız var. Bu dosyaya, http://siteadi.com/makale/yapay-zekanin-yukselisi linki ile ulaşılabilsin istiyorsunuz.
Sitenizin kök klasöründeki .htaccess dosyasında şu şekilde yönlendirme sağlayabilirsiniz:

RewriteEngine On
RewriteRule ^makale/(.+)/?$ /makaleler/$1.html [L,R=301]

Bu sayede kullanıcı http://siteadi.com/makale/yapay-zekanin-yukselisi url'sini çağırırsa sunucu tarafından direkt /makaleler/yapay-zekanin-yukselisi.html dosyasına yönlendirilir.
Tabi CodeIgniter, Laravel gibi framework'ler kullanıyorsanız bu cache mekanizmasının framework'ler içinde kendi çözümleri var.

~ Her makalenin html dosyası olmalı mı?
~ Evet. Her makalenin bir html dosyası olmalı. Tabi binlerce makale olduğunda bu sistemi de optimize etmek gerekir. Bu çok daha detaylı bir konu.

Elbette küçük çaplı projelerde bu kadar optimizasyona gerek yok. Anlık kullanıcı sayısının çok artabileceği, sunucunun isteklere yetişemeyebileceği projeler için tüm bunlar yapılmalı ama zaten anlık 10-15 kullanıcıdan yukarı pek çıkmayan bir proje için bu kadar uğraşmak gerekmez.

Aldığınız hata: foreach() argument must be of type array|object, bool given in ... on line 70
Yani 70. satırda foreach, array veya object değeri ararken bool değerle karşılaşmış.
Muhtemelen 70. satır dediği yer sizin <?php foreach($ust_yorumlar as $ust_yorum): ?> satırınıza denk geliyordur.
Buna göre sanırım 67. satırınıza denk gelen $ust_yorumlar = yorumlar($mak_id, $ust_yorum_id); satırınızda $ust_yorumlar değişkeninize false değeri atanıyor olabilir.

Ama genel olarak kurgunuzda bir sorun var. foreach döngüsü içinde yorumlar() fonksiyonunu çağırmamalısınız. Bu fonksiyon veritabanına istek atıyor. Bu durumda dönünün her adımında veritabanı isteği atmış oluyorsunuz.
Çözüm olarak, makaleye ait tüm yorumları tek seferde çekmelisiniz ve PHP ile bir obje oluşturarak bu yorumların hiyerarşisini düzenlemelisiniz.
Yani mesela "SELECT * FROM yorumlar INNER JOIN uyeler ON uyeler.uye_id = yorumlar.uye_id WHERE makale_id = :mak_id"; şeklinde tüm yorumları tek seferde veren tek bir sorgu.
Bu sorgunun sonucu $yorumlar değişkenine aldınız varsayalım. Sonrasında mesela $duzenlenmisYorumlar adlı boş bir dizi oluşturup, $yorumlar dizinizde dolaşarak gerekli hiyerarşide yorumlarınızı yeni diziye aktarabilirsiniz.

<?php
$yorumlar = tumYorumlar($mak_id);
$duzenlenmisYorumlar = [];
foreach($yorumlar as $yorum) {
    // Baktığımız yorumun üst yorumu yoksa, ana yorum olarak eklensin.
    if($yorum['ust_yorum_id'] === 0) {
      $duzenlenmisYorumlar[ 'yorum_'.$yorum['yorum_id'] ] = ['yorum'=>$yorum, 'alt_yorumlar'=>[]];
    }
    // Baktığımız yorumun üst yorumu varsa, o yorumun alt yorumu olarak eklensin
    else {
      $duzenlenmisYorumlar[ 'yorum_'.$yorum['yorum_id'] ]['alt_yorumlar'][] = $yorum;
    }
}
?>

Artık foreach ile dolaşıp yorumları dom'a basabilirsiniz.

<?php foreach($duzenlenmisYorumlar as $key=>$data) { ?>
    <div class="ana_yorum" data-key="<?= $key ?>">
        <div><?= $data['yorum']['yorum_adi'] ?></div>
        <div><?= $data['yorum']['yorum_icerik'] ?></div>
        <a href="#" class="mt-2">Cevapla</a>
        <?php foreach($data['alt_yorumlar'] as $altYorum) { ?>
            <div class="cevap_yorum">
                <div><?= $altYorum['yorum_adi'] ?></div>
                <div><?= $altYorum['yorum_icerik'] ?></div>
                <a href="#" class="mt-2">Cevapla</a>
            </div>
        <?php } ?>
    </div>
<?php } ?>

Uzun zamandır PHP kullanmıyorum. Yukarıdaki kodları test etmeden yazdım. Yani hatalı olabilir. Ama kurgusal olarak ne yapmaya çalıştığımı aşağı yukarı belli ediyordur.

Not: PHP'de string ifade belirtirken çift tırnak " ve tek tırnak ' farklı amaçlarla kullanılır.
Eğer string ifade içinde değişken kullanmayacaksanız (Örn: "Benim adım $isim. Seninki ne?") tek tırnak kullanmalısınız. Çünkü PHP, çift tırnak içinde bir değişken olabileceğini varsayar. Ama tek tırnak içinde değişken aramaz. Çok çok küçük bir performans maliyeti olsa da, büyük çaplı projeler için milisaniyeler bile önemli olduğundan, alışkanlık kazanmak iyi olur.

Not: Yanlış anlamayın, bu notları size özel yazmıyorum. Ben cevaplarımda, herkesin okuyabileceğini düşünerek böyle detaylar paylaşıyorum. :)

fatihkurtl
296 gün önce

rica ederim, sorularına gelecek olursak;

öncelikle her makale için ayrı html sayfası olmaz bir tane tane detay sayfan olur detay.html gibi oraya
makale oku linkine tıklandığında kullanıcı yönlendirilir hangi makaleye tıkladığını da makale oku linkine tıklandığında backend'e o makalenin id'sini
göndererek sorgulayabilirsin veya eğer unique'se name ile de olur ki bu seo için daha iyi bile olur.
Yani özet olarak bir tane detay sayfan olacak içeriği kullanicinin hangi makaleyi oku linkine tıklayıp tıklamadığına göre değişecek.

detay html sayfasından bahsediyorsan onu bir yere include etmene gerek yok, makale detay sayfalarını markdown formatında yazabilir daha sonra detay sayfasında bu sekilde gosterebilirsin makale yazarken biraz uğraştırır ancak detay sayfan esnek ve daha sekillendirilebilir olur,

hataya gelecek olursak php bilmiyorum ama anlağım kadar yardımcı olmaya çalışayım, bir foreach döngüsü yalnızca dizileri ve nesneleri döndürür ama sen bool tipinde bir veri döndürmeye çalışmışsın, yani döngün yorumlari listeliyor, listeliyor liste bitiyor ama foreach döngün hala listemeleye çalışıyor yorum olmadğı için false sonuncu
dönüyor yani bool tipinde bir veri ve senin foreach döngün bunu da döndürüp listelemeye çalışıyor hata bu yüzden oluyor anladığım kadarıyla bunun önüne geçmek için bir koşul yazabilirsin mesela eğer yanıt_id varsa o yanıtları foreach ile listelersin ya da döndürecek olduğun değerin bir dize olup olmadığını koşul koyarak örnek olarak;

$yorumlar = yorumlar($mak_id);
if (is_array($yorumlar)) {
    foreach($yorumlar as $yorum) {
      
    }
}

bundan çok emin değilim ancak denemeye değer, başarılar...

coder
297 gün önce

@fatihkurtl, @ebykdrms ikinize de ayrı ayrı teşekkür ederim arkadaşlar.


@ebykdrms güzel ve beyinde fikirler uyandıracak bilgiler verdiğiniz için teşekkür ederim. Yazmışken sormak istiyorum.
Makaleyi html olarak yazdırıp bunu üyelere nasıl göstereceğiz?

  • Kişi makele oku linkine tıkladığında html sayfasını mı görecek?
  • Html sayfası php sayfasına include mi edilecek?
  • Her makalenin html dosyası olmalı mı?

GÜNCELLEME

Hocam listeleme işleminde bir sorun yaşıyorum.
Resimdeki hatayı alıyorum. Alt cevabı olmayan yorumda hata veriyor
Yardımcı olabilir misiniz?

Yorumlar Tablosu

Hata

db.php

<?php
function yorumlar($makale_id, $ust_yorum_id = null){
    global $db;

    $sorgu = "SELECT * FROM yorumlar INNER JOIN uyeler ON uyeler.uye_id = yorumlar.uye_id WHERE makale_id = :mak_id AND ust_yorum_id = :ust_yor_id";
    $query = $db->prepare($sorgu);
    
    if($ust_yorum_id != null){
        $query->execute([":mak_id" => $makale_id, ":ust_yor_id" => $ust_yorum_id]);
    }else{
        $query->execute([":mak_id" => $makale_id, ":ust_yor_id" => 0]);
    }
    
    $yaz = $query->fetchAll(PDO::FETCH_ASSOC);
    
    if($query->rowCount() > 0){
        return $yaz;
    }else{
        return false;
    }
}

read.php

<?php
  include_once 'inc/db.php';
  $mak_id = $_GET["id"];
  $yaz = makale($mak_id);
  $yorumlar = yorumlar($mak_id);
?>

<div class="yorumlar">

  <!-- Ana Yorum Başlangıç -->
  <?php foreach($yorumlar as $yorum): ?>
    <div class="ana_yorum">
      
      <?php
        echo $yorum["uye_adi"];
        echo $yorum["yorum_icerik"];
      ?>
      <a href="#" class="mt-2">Cevapla</a>
            
        <!-- Cevap Yorum Başlangıç -->
        <?php
            $ust_yorum_id = $yorum["yorum_id"];
            if($ust_yorum_id != 0):
            $ust_yorumlar = yorumlar($mak_id, $ust_yorum_id);
          ?>

          <?php foreach($ust_yorumlar as $ust_yorum): ?>
            <div class="cevap_yorum">
              <?php
                echo $ust_yorum["uye_adi"];
                echo $ust_yorum["yorum_icerik"];
              ?>
              <a href="#" class="mt-2">Cevapla</a>
            </div>
          <?php endforeach; ?>
          <?php endif; ?>
        <!-- Cevap Yorum Bitiş -->
    
    </div>
    <?php endforeach; ?>
    <!-- Ana Yorum Bitiş -->

</div>
ebykdrms
297 gün önce

Tamamen örnek bir senaryo üzerinden yazıyorum:

1) yorum_id | makale_id | uye_id | yorum_icerik | ust_yorum_id
diye bir sütun daha eklenebilir.

Ahmet bir yorum yazdı. yorum_id=1, ust_yorum_id=null
Mehmet, Ahmet'e cevap yazdı. yorum_id=2, ust_yorum_id=1
Ahmet, Mehmet'e cevap yazdı. yorum_id=3, ust_yorum_id=2
Hüseyin de Mehmet'e cevap yazdı. yorum_id=4, ust_yorum_id=2
Mehmet, Hüseyin'e cevap yazdı. yorum_id=5, ust_yorum_id=4

2) Sayfaya sadece makale basılır ve makale en hızlı şekilde kullanıcıya iletilir.
Sayfa yüklendikten sonra yorumları almak için sunucuya makale_id ile birlikte GET isteği atılır.

Not: Yorumlar sayfaya direkt PHP ile basılmaz.
Kullanıcı bu makaleyi görüntülemek istediğinde asıl amacı makaleyi görmektir.
Makale kullanıcıya en hızlı şekilde gösterilmelidir.
O yüzden yorumların da veritabanından çekilmesi, hizaya sokulması vs. ek işlemler için kullanıcı bekletilmez.

Not: Mümkünse yorumlar, sayfa yüklendikten sonra hemen istenmez.
Kullanıcı belki de makalede yorumları okuyacak kadar kalmayacak.
Sunucuya ne kadar az yük bindirirsek o kadar rahat ederiz.
O yüzden, kullanıcı scroll yapıp makalenin sonuna gelip yorumların bulunacağı kısma yaklaşırsa o zaman yorumlar için istek atılır.
Böylece belki sadece 3-5 cümle okuyup sayfadan çıkacak kullanıcılar için gereksiz yere veritabanına iş bindirmemiş oluruz.

Not: Sayfanın kendisi bile php olmamalıdır.
Makale metni oluşturulduktan sonra bir html dosyası olarak kaydedilmelidir.
Kullanıcılar bu makaleyi görüntülemek istediğinde makale verisi her istek atan kullanıcı için veritabanından sorgulanmamalıdır.
Makale bir kez html dosyası olarak kaydedilip (cache'lenip) kullanıcılara doğrudan bu html dosyası iletilmelidir.

3) Sunucu, yorumları makale_id ile veritabanından çektikten sonra yorumlar üzerinde -güvenlik tedbirleri hariç- ek bir işlem yapmadan front-end'e json formatında iletir.

Not: Bu yorumların sıralanması, hiyerarşik olarak hangi yorumun hangi yoruma cevap olarak verildiği vs. gibi işlemler için sunucu yorulmamalıdır.
Bir işlemi sunucu tarafında yapmamız gerekiyorsa muhtemelen güvenlik gerekçesiyle olur. Ama yorumların sıralanması bir güvenlik sorunu değil.
Bu tür işlemleri kullanıcının tarayıcısına, yani kullanıcının bilgisayarının işlemcisine yıkarak sunucumuzu gereksiz iş yükünden koruruz.

4) Front-end'de JavaScript ile yorumlar belli bir hiyerarşi içinde organize edilir ve DOM'a basılır.

Son Not: Araya girdiğim bu notlar, sunucunun çok sayıda kullanıcıyı kaldırabilecek halde tutulması için. 3-5 kullanıcı için sunucu çok hızlı cevaplar verebilir ama 1000 kullanıcı aynı anda istek attığında her biri için veritabanını ayrı php'yi ayrı yorarsak kullanıcıların yanıt alma süresi çok uzar. Kullanıcılar belki hemen görebileceği bir makaleyi -iş sırasına alındığı için- 3-5 saniye beklemek zorunda kalırlar. Ama sayfa html olarak hazırda tutuluyorsa kullanıcılar istek atınca sunucu sadece html dosyasını iletip sıradaki işleme geçebilir. Ayrıca veritabanıyla iletişim minimumda tutulursa güvenlik açığı riski de azaltılmış olur.
Güvenlik demişken tabi bu aşamalarda bazı güvenlik adımları da araya sokulmalı. Çünkü kullanıcıdan alınan bir input'un ekrana basılması güvenlik açısından titiz davranılması gereken bir konu.

fatihkurtl
297 gün önce

Bu sekilde olabilir;

yorumlar tablosu
yorum_id | makale_id | uye_id | yorum_icerik | parent_id

Eğer bir yorum aynı zamanda başka bir yoruma cevapsa cevap oldugu yorumun id'sini parent_id olarak alabilir eğer herhangi bir yoruma cevap değilse yani sadece gönderiye atılan bir yorumsa parent_id boş kalabilir