title: View counter - how to only count up once per user? - PHP / Laravel 4
body: <p>I’m planning a view count coded in PHP for a blog. The purpose of the counter is to indicate how often a blog post is read, like StackOverflow shows 'viewed n times' on question pages.</p>
  
  <p>First I wanted to store the counter data in files but through research I changed my mind to use MySQL with several InnoDB tables as these perform faster than HDD lookups, are less laborious in the long run and ship with useful functions natively.</p>
  
  <p>The problem I’m stuck at is <strong>how to only count up once per user?</strong></p>
  
  <p>Restricting IP’s is not a solution, because of dynamic IP’s and networks mostly having a single IP sharing the connection with all connected devices (e.g. school, office, public access points).</p>
  
  <p><strong>EDIT:</strong></p>
  
  <p>Inspired by <a href="https://stackoverflow.com/a/17515371/2493918">fideloper’s answer</a> I’m noodling over a session based solution.</p>
  
  <p>I’m using a separate database table for sessions already, so I’ll incorporate this into the solution as suggested.</p>
  
  <p>This is what I have got so far (using <a href="http://laravel.com/" rel="nofollow noreferrer">Laravel 4</a> with <a href="http://laravel.com/docs/eloquent" rel="nofollow noreferrer">Eloquent ORM</a>):</p>
  
  <p><strong>SQL Tables Setup:</strong></p>
  
  <pre><code>-- 
  -- Sessions Table
  -- 
  CREATE TABLE sessions (
      id VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
      payload TEXT NOT NULL COLLATE 'utf8_unicode_ci',
      last_activity INT(11) NOT NULL,
  
      UNIQUE INDEX sessions_id_unique (id)
  ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;
  
  -- 
  -- Views Table
  -- 
  CREATE TABLE post_views (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      post_id INT(11) UNSIGNED NOT NULL,
      sess_id VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
      viewed_at TIMESTAMP NULL DEFAULT NULL,
  
      PRIMARY KEY (id)
  ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;
  
  -- 
  -- Posts Table
  -- 
  CREATE TABLE posts (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      user_id INT(11) UNSIGNED NOT NULL,
      title VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
      excerpt TEXT NOT NULL COLLATE 'utf8_unicode_ci',
      slug VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
      content LONGTEXT NOT NULL COLLATE 'utf8_unicode_ci',
      ...
  
      views INT(10) UNSIGNED NOT NULL DEFAULT '0',
  
      PRIMARY KEY (id)
  ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;
  </code></pre>
  
  <p><br />
  <strong>PHP Views Counter Class:</strong></p>
  
  <pre><code>&lt;?php namespace Services\Posts;
  /**
   * Blog Post Views Counter
   *
   * Using Laravel 4 with Eloquent ORM
   */
  
  class ViewsCounter {
  
      public $post_id;
  
      /**
       * Sets the post id
       * 
       * @param  int $id
       * @return void
       */
      public function setPostID($id)
      {
          $this-&gt;post_id = $id;
      }
  
      /**
       * Handles the view count
       * 
       * @return bool  True or false on failure
       */
      public function countView()
      {
          // Get the post data from the db
          try
          {
              $post = \Post::findOrFail($this-&gt;post_id);
          }
          catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e)
          {
              return false;
          }
  
          // Get the current session id
          $sess_id = session_id();
  
          // Check if this view was already counted by 
          // looking up the session id in the views table
          $post_views = \PostViews::where('sess_id', '=', $sess_id)
                              -&gt;where('post_id', '=', $this-&gt;post_id)
                              -&gt;remember(10)
                              -&gt;first();
  
          // The sess_id was not found so count the view
          if (empty($post_views))
          {
              $post_views-&gt;post_id = $this-&gt;post_id;
              $post_views-&gt;sess_id = $sess_id;
              $post_views-&gt;viewed_at = date('Y-m-d H:i:s');
              $post_views-&gt;save();
  
              // A cronjob is run every 24 hours to count the views per 
              // post and update the count values in the posts table.
              // The post_views table will be truncated when done.
          }
  
          return true;
      }
  
  }
  </code></pre>
  
  <p>Suggestions appreciated.</p>