• Stars
    star
    810
  • Rank 56,092 (Top 2 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 7 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Associate views with Eloquent models in Laravel

Eloquent Viewable

Packagist run-tests StyleCI Codecov branch Total Downloads license

This Laravel >= 6.0 package allows you to associate views with Eloquent models.

Once installed you can do stuff like this:

// Return total views count
views($post)->count();

// Return total views count that have been made since 20 February 2017
views($post)->period(Period::since('2017-02-20'))->count();

// Return total views count that have been made between 2014 and 2016
views($post)->period(Period::create('2014', '2016'))->count();

// Return total unique views count (based on visitor cookie)
views($post)->unique()->count();

// Record a view
views($post)->record();

// Record a view with a cooldown
views($post)->cooldown(now()->addHours(2))->record();

Overview

Sometimes you don't want to pull in a third-party service like Google Analytics to track your application's page views. Then this package comes in handy. Eloquent Viewable allows you to easiliy associate views with Eloquent models. It's designed with simplicity in mind.

This package stores each view record individually in the database. The advantage of this is that it allows us to make very specific counts. For example, if we want to know how many people has viewed a specific post between January 10 and February 17 in 2018, we can do the following: views($post)->period(Period::create('10-01-2018', '17-02-2018'))->count();. The disadvantage of this is that your database can grow rapidly in size depending on the amount of visitors your application has.

Features

Here are some of the main features:

  • Associate views with Eloquent models
  • Get total views count
  • Get views count of a specific period
  • Get unique views count
  • Get views count of a viewable type (Eloquent model class)
  • Order viewables by views
  • Set a cooldown between views
  • Elegant cache wrapper built-in
  • Ignore views from crawlers, ignored IP addresses or requests with DNT header

Documentation

In this documentation, you will find some helpful information about the use of this Laravel package.

Table of contents

  1. Getting Started
  2. Usage
  3. Optimizing
  4. Extending

Getting Started

Requirements

This package requires PHP 7.4+ and Laravel 6+.

Support for Lumen is not maintained!

Version information

Version Illuminate Status PHP Version
^6.0 6.x.x - 8.x.x Active support >= 7.4.0
^5.0 6.x.x - 8.x.x Only bug fixes >= 7.2.0
^4.0 5.5.x - 5.8.x End of life >= 7.1.0
^3.0 5.5.x - 5.8.x End of life >= 7.1.0
^2.0 5.5.x - 5.7.x End of life >= 7.0.0
^1.0 5.5.x - 5.6.x End of life >= 7.0.0

Installation

First, you need to install the package via Composer:

composer require cyrildewit/eloquent-viewable

Secondly, you can publish the migrations with:

php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="migrations"

Finally, you need to run the migrate command:

php artisan migrate

You can optionally publish the config file with:

php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="config"

Register service provider manually

If you prefer to register packages manually, you can add the following provider to your application's providers list.

// config/app.php

'providers' => [
    // ...
    CyrildeWit\EloquentViewable\EloquentViewableServiceProvider::class,
];

Usage

Preparing your model

To associate views with a model, the model must implement the following interface and trait:

  • Interface: CyrildeWit\EloquentViewable\Contracts\Viewable
  • Trait: CyrildeWit\EloquentViewable\InteractsWithViews

Example:

use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\InteractsWithViews;
use CyrildeWit\EloquentViewable\Contracts\Viewable;

class Post extends Model implements Viewable
{
    use InteractsWithViews;

    // ...
}

Recording views

To make a view record, you can call the record method on the fluent Views instance.

views($post)->record();

The best place where you should record a visitors's view would be inside your controller. For example:

// PostController.php
public function show(Post $post)
{
    views($post)->record();

    return view('post.show', compact('post'));
}

Note: This package filters out crawlers by default. Be aware of this when testing, because Postman is for example also a crawler.

Setting a cooldown

You may use the cooldown method on the Views instance to add a cooldown between view records. When you set a cooldown, you need to specify the number of minutes.

views($post)
    ->cooldown($minutes)
    ->record();

Instead of passing the number of minutes as an integer, you can also pass a DateTimeInterface instance.

$expiresAt = now()->addHours(3);

views($post)
    ->cooldown($expiresAt)
    ->record();

How it works

When recording a view with a session delay, this package will also save a snapshot of the view in the visitor's session with an expiration datetime. Whenever the visitor views the item again, this package will checks his session and decide if the view should be saved in the database or not.

Retrieving views counts

Get total views count

views($post)->count();

Get views count of a specific period

use CyrildeWit\EloquentViewable\Support\Period;

// Example: get views count from 2017 upto 2018
views($post)
    ->period(Period::create('2017', '2018'))
    ->count();

The Period class that comes with this package provides many handy features. The API of the Period class looks as follows:

Between two datetimes
$startDateTime = Carbon::createFromDate(2017, 4, 12);
$endDateTime = '2017-06-12';

Period::create($startDateTime, $endDateTime);
Since a datetime
Period::since(Carbon::create(2017));
Upto a datetime
Period::upto(Carbon::createFromDate(2018, 6, 1));
Since past

Uses Carbon::today() as start datetime minus the given unit.

Period::pastDays(int $days);
Period::pastWeeks(int $weeks);
Period::pastMonths(int $months);
Period::pastYears(int $years);
Since sub

Uses Carbon::now() as start datetime minus the given unit.

Period::subSeconds(int $seconds);
Period::subMinutes(int $minutes);
Period::subHours(int $hours);
Period::subDays(int $days);
Period::subWeeks(int $weeks);
Period::subMonths(int $months);
Period::subYears(int $years);

Get total unique views count

If you only want to retrieve the unique views count, you can simply add the unique method to the chain.

views($post)
    ->unique()
    ->count();

Order models by views count

The Viewable trait adds two scopes to your model: orderByViews and orderByUniqueViews.

Order by views count

Post::orderByViews()->get(); // descending
Post::orderByViews('asc')->get(); // ascending

Order by unique views count

Post::orderByUniqueViews()->get(); // descending
Post::orderByUniqueViews('asc')->get(); // ascending

Order by views count within the specified period

Post::orderByViews('asc', Period::pastDays(3))->get();  // descending
Post::orderByViews('desc', Period::pastDays(3))->get(); // ascending

And of course, it's also possible with the unique views variant:

Post::orderByUniqueViews('asc', Period::pastDays(3))->get();  // descending
Post::orderByUniqueViews('desc', Period::pastDays(3))->get(); // ascending

Order by views count within the specified collection

Post::orderByViews('asc', null, 'custom-collection')->get();  // descending
Post::orderByViews('desc', null, 'custom-collection')->get(); // ascending

Post::orderByUniqueViews('asc', null, 'custom-collection')->get();  // descending
Post::orderByUniqueViews('desc', null, 'custom-collection')->get(); // ascending

Get views count of viewable type

If you want to know how many views a specific viewable type has, you need to pass an empty Eloquent model to the views() helper like so:

views(new Post())->count();

You can also pass a fully qualified class name. The package will then resolve an instance from the application container.

views(Post::class)->count();
views('App\Post')->count();

View collections

If you have different types of views for the same viewable type, you may want to store them in their own collection.

views($post)
    ->collection('customCollection')
    ->record();

To retrieve the views count in a specific collection, you can reuse the same collection() method.

views($post)
    ->collection('customCollection')
    ->count();

Remove views on delete

To automatically delete all views of an viewable Eloquent model on delete, you can enable it by setting the removeViewsOnDelete property to true in your model definition.

protected $removeViewsOnDelete = true;

Caching view counts

Caching the views count can be challenging in some scenarios. The period can be for example dynamic which makes caching not possible. That's why you can make use of the in-built caching functionality.

To cache the views count, simply add the remember() method to the chain. The default lifetime is forever.

Examples:

views($post)->remember()->count();
views($post)->period(Period::create('2018-01-24', '2018-05-22'))->remember()->count();
views($post)->period(Period::upto('2018-11-10'))->unique()->remember()->count();
views($post)->period(Period::pastMonths(2))->remember()->count();
views($post)->period(Period::subHours(6))->remember()->count();
// Cache for 3600 seconds
views($post)->remember(3600)->count();

// Cache until the defined DateTime
views($post)->remember(now()->addWeeks(2))->count();

// Cache forever
views($post)->remember()->count();

Optimizing

Benchmarks

Database indexes

The default views table migration file has already two indexes for viewable_id and viewable_type.

If you have enough storage available, you can add another index for the visitor column. Depending on the amount of views, this may speed up your queries in some cases.

Caching

Caching views counts can have a big impact on the performance of your application. You can read the documentation about caching the views count here

Using the remember() method will only cache view counts made by the count() method. The orderByViews and orderByUnique query scopes aren't using these values because they only add something to the query builder. To optimize these queries, you can add an extra column or multiple columns to your viewable database table with these counts.

Example: we want to order our blog posts by unique views count. The first thing that may come to your mind is to use the orderByUniqueViews query scope.

$posts = Post::latest()->orderByUniqueViews()->paginate(20);

This query is quite slow when you have a lot of views stored. To speed things up, you can add for example a unique_views_count column to your posts table. We will have to update this column periodically with the unique views count. This can easily be achieved using a schedued Laravel command.

There may be a faster way to do this, but such command can be like:

$posts = Post::all();

foreach($posts as $post) {
    $post->unique_views_count = views($post)->unique()->count();
}

To be updated! Laravel has a nice chunk and cursor feature what may come in handy.

Extending

If you want to extend or replace one of the core classes with your own implementations, you can override them:

  • CyrildeWit\EloquentViewable\Views
  • CyrildeWit\EloquentViewable\View
  • CyrildeWit\EloquentViewable\Visitor
  • CyrildeWit\EloquentViewable\CrawlerDetectAdapter

Note: Don't forget that all custom classes must implement their original interfaces

Custom information about visitor

The Visitor class is responsible for providing the Views builder information about the current visitor. The following information is provided:

  • a unique identifier (stored in a cookie)
  • ip address
  • check for Do No Track header
  • check for crawler

The default Visitor class gets its information from the request. Therefore, you may experience some issues when using the Views builder via a RESTful API. To solve this, you will need to provide your own data about the visitor.

You can override the Visitor class globally or locally.

Create your own Visitor class

Create you own Visitor class in your Laravel application and implement the CyrildeWit\EloquentViewable\Contracts\Visitor interface. Create the required methods by the interface.

Alternatively, you can extend the default Visitor class that comes with this package.

Globally

Simply bind your custom Visitor implementation to the CyrildeWit\EloquentViewable\Contracts\Visitor contract.

$this->app->bind(
    \CyrildeWit\EloquentViewable\Contracts\Visitor::class,
    \App\Services\Views\Visitor::class
);

Locally

You can also set the visitor instance using the useVisitor setter method on the Views builder.

use App\Services\Views\Visitor;

views($post)
    ->useVisitor(new Visitor()) // or app(Visitor::class)
    ->record();

Using your own Views Eloquent model

Bind your custom Views implementation to the \CyrildeWit\EloquentViewable\Contracts\Views.

Change the following code snippet and place it in the register method in a service provider (for example AppServiceProvider).

$this->app->bind(
    \CyrildeWit\EloquentViewable\Contracts\Views::class,
    \App\Services\Views\Views::class
);

Using your own View Eloquent model

Bind your custom View implementation to the \CyrildeWit\EloquentViewable\Contracts\View.

Change the following code snippet and place it in the register method in a service provider (for example AppServiceProvider).

$this->app->bind(
    \CyrildeWit\EloquentViewable\Contracts\View::class,
    \App\Models\View::class
);

Using a custom crawler detector

Bind your custom CrawlerDetector implementation to the \CyrildeWit\EloquentViewable\Contracts\CrawlerDetector.

Change the following code snippet and place it in the register method in a service provider (for example AppServiceProvider).

$this->app->singleton(
    \CyrildeWit\EloquentViewable\Contracts\CrawlerDetector::class,
    \App\Services\Views\CustomCrawlerDetectorAdapter::class
);

Adding macros to the Views class

use CyrildeWit\EloquentViewable\Views;

Views::macro('countAndRemember', function () {
    return $this->remember()->count();
});

Now you're able to use this shorthand like this:

views($post)->countAndRemember();

Views::forViewable($post)->countAndRemember();

Upgrading

Please see UPGRADING for detailed upgrade guide.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Credits

See also the list of contributors who participated in this project.

Helpful Resources:

Alternatives

Feel free to add more alternatives!

License

This project is licensed under the MIT License - see the LICENSE file for details.

More Repositories

1

repository-pattern-laravel

My way of implementing the repository pattern in Laravel 5.*
PHP
12
star
2

laravel-flow

🚧 Under Development 🚧 | Bring the power of flows (wizards) to your Laravel application
PHP
5
star
3

php-maps-urls

Build URL's for the Google Maps URL API in PHP
PHP
5
star
4

laravel-accommodations

[WIP] Open-source booking platform for accommodations
PHP
3
star
5

laravel-silent-authentication

Silent authentication methods for Laravel
PHP
3
star
6

laravel-with-wordpress

Research to find an appropriate way of integrating WordPress into a Laravel installation
PHP
3
star
7

laravel-breweries-app

PHP
2
star
8

dnh-leer-platform

Leer Platform - Scriptie informatica 2020 (De nieuwe Havo)
PHP
2
star
9

laravel-flow-demo-app

Laravel Flow demo application
PHP
2
star
10

hva-skc

HvA HBO-ICT Studiekeuzecheck
Python
1
star
11

laravel-maps-urls

Laravel wrapper for the PHP Google Maps URLs package
PHP
1
star
12

eloquent-viewable-demo-app

Eloquent Viewable demo application
PHP
1
star
13

dnh-eindopdracht-html-css-js

Eindopdracht HTML, CSS & JavaScript (De nieue Havo)
HTML
1
star
14

cyrildewit

1
star
15

gulp-examples

Just a list of gulpfile.js examples
JavaScript
1
star
16

maximize

Maximize is a boilerplate for creating new web development projects.
JavaScript
1
star
17

perceptor

My personal CSS boilerplate/framework written in Sass
CSS
1
star
18

java-game-assignment-pong

Replication of Pong in Java
Java
1
star
19

dnh-classrooms-plus

Classrooms+ (De nieuwe Havo)
JavaScript
1
star
20

Fusion

Fusion is a Flexible, Responsive CSS framework for Mobile-Friendly Development.
1
star
21

fhict-competence-documents

All my competence documents since semester 3
1
star
22

cyrildewit.nl

My personal website
1
star
23

my-codewars-code

My Codewars Solutions
JavaScript
1
star
24

cyrildewit.github.io

CSS
1
star
25

fhict-2021nj

C#
1
star
26

fhict-2021vj-personal-goals-coach

Personal Goals Coach application built with Spring Boot
Java
1
star
27

fhict-2021nj-project-trash-application

Monorepo with the web application and trashcan software of Project TRASH
PHP
1
star
28

normalize-sass

A configurable normalize.css for Sass
CSS
1
star
29

my-freecodecamp-code

My FreeCodeCamp projects code
HTML
1
star
30

dnh-profielwerkstuk

Sorteeralgoritmen - Profielwerkstuk 2020 Havo (De nieuwe Havo)
Python
1
star