-2

So I'm kinda new with php and I work on a project where I must use a Model View Controller structure. I've got an issue on my article page where people can post comments. When they post it, the script must reload the page with the newly posted comment but I've got this "Headers already sent" error popping up. I read pretty much all the existing topics on this issue (edit : including "how to fix headers already sent errors"), I tried a lot of things including setting "output_buffering = on" on my php.ini, my code is encoded on UTF-8 without BOM...

I found a solution to by-pass it, but I would have liked to know why I've got this issue and how to correct it a better way. The solution I found was to put a ob_start() and ob_get_contents() wrapping my template.

Here's my code for the article page :

<?php ob_start();
$title = htmlspecialchars($article['title']);
?>
<!-- Display de l'article -->
<article class="articleBox mx-auto border border-white m-3">
    <h2 class="display-3 mb-4"><?=htmlspecialchars(ucfirst($article['title']));?></h2>
    <div class="text-justify">
        <p><?=nl2br(htmlspecialchars($article['content']));?></p>
        <p class="text-right mt-5">Publié le <?=htmlspecialchars($article['date_posted']);?>, par
            <?=htmlspecialchars($article['author']);?></p>
        <?php if ($article['date_updated']) {
    echo '<p class="text-right">Mis à jour le ' . htmlspecialchars($article['date_updated']) . '</p>';
}?>
    </div>
</article>
<hr>
<!-- Display des commentaires -->
<section id="comments" class="d-flex flex-lg-row flex-column justify-content-around m-5">
    <div id="getComments" class="col-12 col-lg-6">
        <h3 class="h2">Commentaires</h3>
        <?php
if ($comments->rowCount() > 0) {
    while ($comment = $comments->fetch()) {?>
        <div class="commentBox m-4">
            <div>
                <p><strong><?=htmlspecialchars($comment['author'])?></strong>, le
                    <?=htmlspecialchars($comment['date_posted'])?> : </p>
            </div>
            <p><?=nl2br(htmlspecialchars($comment['comment']))?></p>
            <form method="POST" action="signalComment.php?id=<?=$comment['id']?>&amp;postId=<?=$article['id']?>">
                <?php if ($comment['signaled'] === 'yes') { 
                            echo '<p class="signaled small text-left text-lg-right">Ce commentaire a été signalé.</p>'; } 
                            else if($comment['signaled'] === 'ok') {
                             echo '<p class="signaled small text-left text-lg-right">Ce commentaire a été validé.</p>';} 
                            else { echo '<input class="signalButton" type="submit" value="Signaler">';}?>
            </form>
        </div>
        <?php
    }
} else {?>
        <h4 class="h3">Aucun commentaire</h4>
        <?php
    } ?>
    </div>
    <!-- Poster un commentaire -->
    <div id="postComment" class="col-12 col-lg-6 mt-5 mt-lg-0">
        <form class="box" method="POST" action="article.php?action=addComment&amp;id=<?=$article['id']?>">
            <h3 class="h2">Laisser un commentaire</h3>
            <p class="text-left"><input maxlength="15" class="input mt-3" type="text" name="author" id="author"
                    placeholder="votre nom" required></p>
            <textarea class="textarea m-4" name="comment" id="comment" cols="40" rows="5"
                placeholder="votre commentaire" required></textarea><br>
            <p><input type="submit" value="Envoyer"></p>
        </form>
    </div>
</section>
<?php
$content = ob_get_clean();
require 'template.php';

My code for the template (with the ob_start and ob_get_contents) :

<?php ob_start();?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <title>Jean Forteroche - <?=$title?></title>
    <link rel="icon" type="image/png" href="public/images/logoJF.png">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit = no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- CSS Animate -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css">
    <!-- CSS Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
        integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <!-- CSS -->
    <link rel="stylesheet" href="public/css/style.css">
    <!-- GOOGLE FONTS -->
    <link href="https://fonts.googleapis.com/css?family=Dancing+Script&display=swap" rel="stylesheet">
</head>
<body>
    <header>
        <?php include 'header.php';?>
    </header>
    <main class="text-center mb-5">
        <?=$content;?>
    </main>
    <footer>
        <?php include 'footer.php';?>
    </footer>

    <!-- JS Bootstrap -->
    <script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
        integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous">
    </script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
        integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous">
    </script>
</body>
</html>
<?php ob_get_contents();

My function on my frontend controller :

public function addComment($postId, $author, $comment)
{
    try {
        if (isset($_POST['author']) && !empty($_POST['author']) && isset($_POST['comment']) && !empty($_POST['comment']))
        {
            $commentManager = new CommentManager;    
            $input = $commentManager->postComment($postId, $author, $comment);
            header('Location: article.php?id='.$postId.'#comments');
        }
        else
            throw new Exception('emptyInputs');
    } catch(Exception $e) {
        $this->error($e);
    }
}

And here is the message error, saying the issue comes from the line 24, where I call my variable $content referring to my article page. When I try putting all my html on the same page, it says the error comes from the line where i call my variable $article['content']

Warning
: Cannot modify header information - headers already sent by (output started at /opt/lampp/htdocs/tests/projet4/view/frontend/template.php:24) in
/opt/lampp/htdocs/tests/projet4/controller/frontend/frontend.php
on line
123

Thanks for your help !

tereško
  • 58,060
  • 25
  • 98
  • 150
Charles
  • 1
  • 3
  • Does this answer your question? [How to fix "Headers already sent" error in PHP](https://stackoverflow.com/questions/8028957/how-to-fix-headers-already-sent-error-in-php) – SteveK Apr 16 '20 at 08:43
  • Well this is one of the topics I read many times, but I haven't found the solution to my problem in there, no. But i'm not sure to understand all it says either :/ – Charles Apr 16 '20 at 08:50
  • How is this all being invoked? From the error message it appears to be starting from the template file, which starts outputting HTML right away, and then from somewhere within your template file you're calling the `addComment` function, which attempts to output a header; hence the error. – deceze Apr 16 '20 at 08:55
  • I've got a router asking the views to my controller depending on the url. the error message refers to my $content, meaning my article page. Here i left the solution i found to by pass it. I've got other functions calling redirection in my controller for when i submit a form for exemple, that works perfectly. – Charles Apr 16 '20 at 09:03
  • 1
    So the order of invocation is *router → template → controller*…? Then it's the wrong order. The controller must be invoked to handle the request and process it, then the view (template) must be output. – deceze Apr 16 '20 at 09:06
  • thanks for that insight, i never thought it could come from my router. I answered the topic with the solution, but yeah my router called my router page, then called the addComment function if a comment was posted. I just had to put it the other way. Thanks man! – Charles Apr 16 '20 at 09:19

2 Answers2

1

While it's not 100% clear from your examples, this error most often occurs to the beginner when they attempt to output a part of the page before they do a header() call.

As soon as you have echo'd anything out, the headers are sent, so you need to ensure that any calls to header() happen before any of the page generation code, whether that is a call to echo or simply HTML outside of your <?php ?> tags.

A common gotcha here is where some whitespace has inadvertently been included, typically before the <?php at the top of a file, or after a closing ?>.

As you are using MVC methodology, good practice would dictate that only your template/view files should contain HTML, and the first characters of any PHP scripts should be <?php, and you shouldn't close with ?> at the end of a file to ensure that any trailing whitespace doesn't get included in your page views.

SteJ
  • 1,491
  • 11
  • 13
  • thanks for your answer. As I said, I already checked all of that, html is present only in my views folder and all my php scripts begin with – Charles Apr 16 '20 at 09:06
  • Submitting a form is a *request* and doesn't happen during a *response* -- the problem is when you try to send a header in the middle of a *response*, as the header needs to be sent *at the start* of the response. You will need to track down what is sending *response text* (ie HTML) before your call to `header()` - judging from the comments on your question this is because you are trying to parse the template/view before your controller. – SteJ Apr 16 '20 at 09:35
  • The problem with making your question clearer is you already have quite a bit of code to read through and it still doesn't show the complete execution chain from receiving a request through to sending the response; this leaves us "guessing" at what might come in between. Consider removing actual code (such as your html) and replacing it with shorter examples and.or pseudocode -- all we need to know is where your HTML is output, not actually what it says. – SteJ Apr 16 '20 at 09:38
0

Okay, i've found the problem.

My router was written like that for the article page :

else if (strpos($frontend->url, 'article.php'))
        {
                $frontend->article(); 
            // Si l'utilisateur poste un commentaire sur un article
            if (isset($_GET['action']) && $_GET['action'] == 'addComment')
                $frontend->addComment($_GET['id'], $_POST['author'], $_POST['comment']);
        }

It loaded my article page and then, if a comment was submitted, it called my controller function AddComment where there's the redirection. I put my If statement before loading the article page and it worked correctly !

Thanks for you time, and thanks deceze who made me ring a bell. Hope it can help others, I didn't find a lot of exemples with MVC structure.

Charles
  • 1
  • 3