PHP & Web Development Blogs

Search Results For: push
Showing 1 to 3 of 3 blog articles.
5421 views · 1 years ago

![Introduction to Gitlab CI for PHP developers](https://images.ctfassets.net/vzl5fkwyme3u/5EUoVwcn2inEG3LsNJFAYp/14e5c704d91665c0de6ffd506a283ec3/AdobeStock_90389954.png?w=1000)

As a developer, you've probably at least heard something about [CI - Continuous integration](https://en.wikipedia.org/wiki/Continuous_integration). And if you haven't - you better fix it ASAP, because that's something awesome to have on your skill list and can get extremely helpful in your everyday work. This post will focus on CI for PHP devs, and specifically, on CI implementation from [Gitlab](https://docs.gitlab.com/ee/ci/README.html). I will suppose you know the basics of [Git](https://git-scm.com/), [PHP](https://php.net/), [PHPUnit](https://phpunit.de/), [Docker](https://www.docker.com/) and unix shell. Intended audience - intermediate PHP devs.

Adding something to your workflow must serve a purpose. In this case the goal is to automate routine tasks and achieve better quality control. Even a basic PHP project IMO needs the following:

* [linter](https://en.wikipedia.org/wiki/Lint_(software)) checks (cannot merge changes that are invalid on the syntax level)

* Code style checks

* Unit and integration tests

All of those can be just run eventually, of course. But I prefer an automated CI approach even in my personal projects because it leads to a higher level of discipline, you simply can't avoid following a set of rules that you've developed. Also, it reduces a risk of releasing a bug or regression, thus improving quality.

Gitlab is as generous as giving you their CI for free, even for your private repos. At this point it is starting to look as advertising, therefore a quick comparison table for [Gitlab](https://about.gitlab.com/pricing/), Github, [Bitbucket](https://bitbucket.org/product/pricing). AFAIK, Github does not have a built-in solution, instead it is easily integrated with third parties, of which [Travis CI](https://github.com/marketplace/travis-ci/plan/MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW43MA==#pricing-and-setup) seems to be the most popular - I will therefore mention Travis here.

### Public repositories (OSS projects). All 3 providers have a free offer for the open-source community!

| Provider | Limits |

|---|---|

| Gitlab | 2,000 CI pipeline minutes per group per month, shared runners |

| Travis | Apparently unlimited |

| Bitbucket| 50 min/month, max 5 users, File storage <= 1Gb/month |

### Private repositories

| Provider | Price | Limits |

|---|---|---|

| Gitlab | Free | 2,000 CI pipeline minutes per group per month, shared runners |

| Travis | $69/month | Unlimited builds, 1 job at a time |

| Bitbucket| Free | 50 min/month, max 5 users, File storage <= 1Gb/month |

## Getting started

I made a small project based on Laravel framework and called it "ci-showcase". I work in Linux environment, and the commands I use in the examples, are for linux shell. They should be pretty much the same on Mac and nearly the same on Windows though.

```sh

composer create-project laravel/laravel ci-showcase

```

Next, I went to gitlab website and created a new public project: https://gitlab.com/crocodile2u/ci-showcase. Cloned the repo and copied all files and folders from the newly created project - the the new git repo. In the root folder, I placed a `.gitignore` file:

```

.idea

vendor

.env

```

Then the `.env` file:

```

APP_ENV=development

```

Then I generated the application encryption key: `php artisan key:generate`, and then I wanted to verify that the primary setup works as expected: `./vendor/bin/phpunit`, which produced the output `OK (2 tests, 2 assertions)`. Nice, time to commit this: `git commit && git push`

[At this point](https://gitlab.com/crocodile2u/ci-showcase/tree/step-1), we don't yet have any CI, let's do something about it!

### Adding .gitlab-ci.yml

Everyone going to implement CI with Gitlab, is strongly encouraged to bookmark this page: https://docs.gitlab.com/ee/ci/README.html. I will simply provide a short introduction course here plus a bit of boilerplate code to get you started easier.

First QA check that we're going to add is PHP syntax check. PHP has a built-in linter, which you can invoke like this: `php -l my-file.php`. This is what we're going to use. Because the `php -l` command doesn't support multiple files as arguments, I've written a small wrapper shell script and saved it to `ci/linter.sh`:

```sh

#!/bin/sh

files=`sh ci/get-changed-php-files.sh | xargs`

last_status=0

status=0

# Loop through changed PHP files and run php -l on each

for f in "$files" ; do message=`php -l $f` last_status="$?" if [ "$last_status" -ne "0" ]; then # Anything fails -> the whole thing fails echo "PHP Linter is not happy about $f: $message" status="$last_status" fi

done

if [ "$status" -ne "0" ]; then echo "PHP syntax validation failed!"

fi

exit $status

```

Most of the time, you don't actually want to check each and every PHP file that you have. Instead, it's better to check only those files that have been changed. The Gitlab pipeline runs on every push to the repository, and there is a way to know which PHP files have been changed. Here's a simple script, meet `ci/get-changed-php-files.sh`:

```sh

#!/bin/sh

# What's happening here?

#

# 1. We get names and statuses of files that differ in current branch from their state in origin/master.

# These come in form (multiline)

# 2. The output from git diff is filtered by unix grep utility, we only need files with names ending in .php

# 3. One more filter: filter *out* (grep -v) all lines starting with R or D.

# D means "deleted", R means "renamed"

# 4. The filtered status-name list is passed on to awk command, which is instructed to take only the 2nd part

# of every line, thus just the filename

git diff --name-status origin/master | grep '\.php$' | grep -v "^[RD]" | awk '{ print $2 }'

```

These scripts can easily be tested in your local environment ( at least if you have a Linux machine, that is ;-) ).

Now, as we have our first check, we'll finally create our `.gitlab-ci.yml`. This is where your pipeline is declared using [YAML notation](https://yaml.org/):

```yml

# we're using this beautiful tool for our pipeline: https://github.com/jakzal/phpqa

image: jakzal/phpqa:alpine

# For this sample pipeline, we'll only have 1 stage, in real-world you would like to also add at least "deploy"

stages: - QA

linter:

stage: QA

# this is the main part: what is actually executed

script: - sh ci/get-changed-php-files.sh | xargs sh ci/linter.sh

```

The first line is `image: jakzal/phpqa:alpine` and it's telling Gitlab that we want to run our pipeline using a PHP-QA utility by [jakzal](https://github.com/jakzal). It is a docker image containing PHP and a huge variety of QA-tools. We declare one stage - QA, and this stage by now has just a single job named `linter`. Every job can have it's own docker image, but we don't need that for the purpose of this tutorial. Our project reaches [Step 2](https://gitlab.com/crocodile2u/ci-showcase/tree/step-2). Once I had pushed these changes, I immediately went to the [project's CI/CD page](https://gitlab.com/crocodile2u/ci-showcase/pipelines). Aaaand.... the pipeline was already running! I clicked on the `linter` job and saw the following happy green output:

```

Running with gitlab-runner 11.9.0-rc2 (227934c0) on docker-auto-scale ed2dce3a

Using Docker executor with image jakzal/phpqa:alpine ...

Pulling docker image jakzal/phpqa:alpine ...

Using docker image sha256:12bab06185e59387a4bf9f6054e0de9e0d5394ef6400718332c272be8956218f for jakzal/phpqa:alpine ...

Running on runner-ed2dce3a-project-11318734-concurrent-0 via runner-ed2dce3a-srm-1552606379-07370f92...

Initialized empty Git repository in /builds/crocodile2u/ci-showcase/.git/

Fetching changes...

Created fresh repository.

From https://gitlab.com/crocodile2u/ci-showcase * [new branch] master -> origin/master * [new branch] step-1 -> origin/step-1 * [new branch] step-2 -> origin/step-2

Checking out 1651a4e3 as step-2...

Skipping Git submodules setup

$ sh ci/get-changed-php-files.sh | xargs sh ci/linter.sh

Job succeeded

```

It means that our pipeline was successfully created and run!

### PHP Code Sniffer.

[PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer) is a tool for keeping app of your PHP files in one uniform code style. It has a hell of customizations and settings, but here we will only perform simple check for compatibilty with [PSR-2](https://www.php-fig.org/psr/psr-2/) standard. A good practice is to create a configuration XML file in your project. I will put it in the root folder. Code sniffer can use a few file names, of which I prefer `phpcs.xml`:

```xml

<?xml version="1.0"?>

/resources

```

I also will append another section to `.gitlab-ci.yml`:

```yml

code-style: stage: QA script: # Variable $files will contain the list of PHP files that have changes - files=`sh ci/get-changed-php-files.sh` # If this list is not empty, we execute the phpcs command on all of them - if [ ! -z "$files" ]; then echo $files | xargs phpcs; fi

```

Again, we check only those PHP files that differ from master branch, and pass their names to `phpcs` utility. That's it, [Step 3](https://gitlab.com/crocodile2u/ci-showcase/tree/step-3) is finished! If you go to see the pipeline now, you will notice that `linter` and `code-style` jobs run in parallel.

## Adding PHPUnit

Unit and integration tests are essential for a successful and maintaiable modern software project. In PHP world, [PHPUnit](https://phpunit.de/) is de facto standard for these purposes. The PHPQA docker image already has PHPUnit, but that's not enough. Our project is based on [Laravel](https://laravel.com/), which means it depends on a bunch of third-party libraries, Laravel itself being one of them. Those are installed into `vendor` folder with [composer](https://getcomposer.org/). You might have noticed that our `.gitignore` file has `vendor` folder as one of it entries, which means that it is not managed by the Version Control System. Some prefer their dependencies to be part of their Git repository, I prefer to have only the `composer.json` declarations in Git. Makes the repo much much smaller than the other way round, also makes it easy to avoid bloating your production builds with libraries only needed for development.

Composer is also included into PHPQA docker image, and we can enrich our `.gitlab-ci.yml`:

```yml

test: stage: QA cache: key: dependencies-including-dev paths: - vendor/ script: - composer install - ./vendor/bin/phpunit

```

PHPUnit requires some configuration, but in the very beginning we used `composer create-project` to create our project boilerplate. **laravel/laravel** package has a lot of things included in it, and `phpunit.xml` is also one of them. All I had to do was to add another line to it:

```xml

```

APP_KEY enironment variable is essential for Laravel to run, so I generated a key with `php artisan key:generate`.

`git commit` & `git push`, and we have all three jobs on the **QA** stage!

## Checking that our checks work

In [this branch](https://gitlab.com/crocodile2u/ci-showcase/tree/failing-checks) I intentionally added changes that should fail all three job in our pipeline, take a look at [git diff](https://gitlab.com/crocodile2u/ci-showcase/compare/step-4...failing-checks). And we have this out from the pipeline stages:

**Linter**:

```

$ ci/linter.sh

PHP Linter is not happy about app/User.php:

Parse error: syntax error, unexpected 'syntax' (T_STRING), expecting function (T_FUNCTION) or const (T_CONST) in app/User.php on line 11

Errors parsing app/User.php

PHP syntax validation failed!

ERROR: Job failed: exit code 255

```

**Code-style**:

```

$ if [ ! -z "$files" ]; then echo $files | xargs phpcs; fi

FILE: ...ilds/crocodile2u/ci-showcase/app/Http/Controllers/Controller.php

----------------------------------------------------------------------

FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE

---------------------------------------------------------------------- 13 | WARNING | Line exceeds 120 characters; contains 129 characters

----------------------------------------------------------------------

Time: 39ms; Memory: 6MB

ERROR: Job failed: exit code 123

```

**test**:

```

$ ./vendor/bin/phpunit

PHPUnit 7.5.6 by Sebastian Bergmann and contributors.

F. 2 / 2 (100%)

Time: 102 ms, Memory: 14.00 MB

There was 1 failure:

1) Tests\Unit\ExampleTest::testBasicTest

This test is now failing

Failed asserting that false is true.

/builds/crocodile2u/ci-showcase/tests/Unit/ExampleTest.php:17

FAILURES!

Tests: 2, Assertions: 2, Failures: 1.

ERROR: Job failed: exit code 1

```

Congratulations, our pipeline is running, and we now have much less chance of messing up the result of our work.

## Conclusion

Now you know how to set up a basic QA pipeline for your PHP project. There's still a lot to learn. Pipeline is a powerful tool. For instance, it can make deployments to different environments for you. Or it can build docker images, store artifacts and more! Sounds cool? Then spend 5 minutes of your time and leave a comment, you can also tell me if there is a pipeline topic you would like to be covered in next posts.

3386 views · 1 years ago

![Implement Web Push Notification in PHP using W3C provided Notification API](https://images.ctfassets.net/vzl5fkwyme3u/ERmW7y6S781gcWIiFjTRn/de1c1aeb3c79093210828760fc373ab2/AdobeStock_152541972.png?w=1000)

Hi Guys,

I am sharing you the simple steps by which you can broadcast the web push notifications to your subscriber. In this tutorial we are making a subscriber form and saving information using Ajax and PHP and then through a server side code returning response to current logged in user and showing notification to that user.

**Following are the steps to build this system**

#### 1. Create a database, I am creating db with name 'web_notifications'

Creating subscribers and notifications tables using following sql statements

```

CREATE TABLE IF NOT EXISTS `subscribers` (

`id` int(11) NOT NULL,

`name` varchar(255) NOT NULL,

`email` varchar(255) NOT NULL,

`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `subscribers`

ADD PRIMARY KEY (`id`);

ALTER TABLE `subscribers`

MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

CREATE TABLE IF NOT EXISTS `notifications` (

`id` int(11) NOT NULL,

`to_user` int(11) NOT NULL,

`title` varchar(255) NOT NULL,

`body` varchar(255) NOT NULL,

`url` varchar(255) NOT NULL,

`is_sent` int(11) NOT NULL DEFAULT '0',

`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `notifications`

ADD PRIMARY KEY (`id`);

ALTER TABLE `notifications`

MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

```

#### 2. Now create a db_connect.php file with following code

```

<?php

session_start();

$servername = "localhost";

$username = "root";

$password = "";

$dbname = "web_notifications";

/ / Create connection

$conn = new mysqli($servername, $username, $password, $dbname);

/ / Check connection

if ($conn->connect_error) {

die("Connection failed: " . $conn->connect_error);

}

?>

```

#### 3. Create a cookies.js file to read and write browser cookies

```

function WriteCookie(key,content) {

var now = new Date();

now.setMonth( now.getMonth() + 1 );

document.cookie = key+"=" + escape(content) + ";";

document.cookie = "expires=" + now.toUTCString() + ";"

}

function ReadCookie(key) {

var allcookies = document.cookie;

cookiearray = allcookies.split(';');

var CookieData=Array();

for(var i=0; i<cookiearray.length; i++) {

k = cookiearray[i].split('=')[0];

v = cookiearray[i].split('=')[1];

CookieData[k]=v;

}

return CookieData[key];

}

```

#### 4. Create a ajax file to read and mark is_sent if any notification foun to be sent in database for that user. create file with name 'fetch_notifications.php' with following content

```

<?php require 'db_connect.php';

$sql = "SELECT id,title,body,url FROM notifications where to_user='"[email protected]$_GET['user_id']."' and is_sent='0' ";

$result = $conn->query($sql);

$data=array();

if ($result->num_rows > 0) {

while($row = $result->fetch_assoc()) {

$data[]=$row;

/ /update this record as is_sent

$upd = "update notifications set is_sent='1' where id='".$row['id']."' ";

$conn->query($upd);

}

}

if(count($data)>0)

{

$response=array("status"=>1,"notification"=>$data);

}

else

{

$response=array("status"=>0,"error"=>"No new notification!");

}

echo json_encode($response);

$conn->close();

?>

```

#### 5. Now code index.php to show subscriber form and on submit insert record into the subscriber table

```

<?php require 'db_connect.php'; ?>

<!DOCTYPE html>

<html>

<head>

<title>Web Push Notification Demo</title>

<script src="./cookies.js" type="text/javascript"></script>

<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">

<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">

<?php

if(isset($_POST['subscribe_form']))

{

$_SESSION['is_login']=0;

$username=$conn->real_escape_string($_POST['username']);

$useremail=$conn->real_escape_string($_POST['useremail']);

$sql = "INSERT INTO subscribers set name='".$username."',email='".$useremail."' ";

if ($conn->query($sql) === TRUE) {

$_SESSION['is_login']=1;

$_SESSION['Uid']= $conn->insert_id;

$_SESSION['Uname']= $username;

?>

<script type="text/javascript">

WriteCookie("Uid","<?php echo $_SESSION['Uid']; ?>");

</script>

<?php

$msg="<p style='color:green'>You have subscribe for push notification succesfully :)</p>";

} else {

$msg="<p style='color:red'>Error in subscribing for notifications</p>";

}

}

?>

<div class="container">

<?php

if(isset($msg) && $msg!='')

{

?>

<br>

<div class="alert alert-info">

<?php echo $msg; ?>

</div>

<?php

}

if(isset($_SESSION['is_login']) && $_SESSION['is_login']==1)

{

?>

<h2>Welcome <?php echo $_SESSION['Uname']; ?></h2>

<script type="text/javascript">

setInterval(function(){

check_notification();

}, 10000);

function check_notification()

{

var Uid=ReadCookie("Uid");

if(Uid!==undefined)

{

$.ajax({url: "fetch_notifications.php?user_id="+Uid, success: function(result){

var response=JSON.parse(result);

if(response.status==1)

{

response=response.notifications;

for (var i = response.length - 1; i >= 0; i--) {

var url = response[i]['url'];

var noti = new Notification(response[i]['title'], {

icon: 'logo.png',/ /this should be your logo in root folder

body: response[i]['body'],

});

noti.onclick = function () {

window.open(url);

noti.close();

};

};

}

else{

console.log(response.error);

}

}

});

}

}

</script>

<?php

}

else

{

?>

<h2 class="text-center">Subscribe for Notifications</h2>

<div class="row justify-content-center">

<div class="col-12 col-md-8 col-lg-6 pb-5">

<div class="card border-primary rounded-0">

<div class="card-header p-0">

<div class="bg-info text-white text-center py-2">

<h3><i class="fa fa-envelope"></i> Information</h3>

<p class="m-0">provide your information</p>

</div>

</div>

<div class="card-body p-3">

<form method="post">

<!--Body-->

<div class="form-group">

<div class="input-group mb-2">

<div class="input-group-prepend">

<div class="input-group-text"><i class="fa fa-user text-info"></i></div>

</div>

<input type="text" class="form-control" id="username" name="username" placeholder="Input Your Name Here" required>

</div>

</div>

<div class="form-group">

<div class="input-group mb-2">

<div class="input-group-prepend">

<div class="input-group-text"><i class="fa fa-envelope text-info"></i></div>

</div>

<input type="text" class="form-control" id="useremail" name="useremail" pattern="[^@\s][email protected][^@\s]+\.[^@\s]+" title="Invalid email address" placeholder="[email protected]" required>

</div>

</div>

<div class="text-center">

<input type="submit" value="Subscribe" name="subscribe_form" class="btn btn-info btn-block rounded-0 py-2">

</div>

</form>

</div>

</div>

</div>

</div>

<?php }?>

</div>

</head>

<body>

</body>

</html>

<?php

$conn->close();

?>

```

The frontend of your subscription page (index.php) should look like this:

![Subscribing Form to User](https://www.w3school.info/blogimages/front_web_push_page.png)

Now we are ready to receive notification in frontend, but we still need to create an admin page from where we can send notification to subscriber(s).

#### 6. Create a table for admin user

```

CREATE TABLE IF NOT EXISTS `admin` (

`id` int(11) NOT NULL,

`username` varchar(255) NOT NULL,

`password` varchar(255) NOT NULL,

`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `admin`

ADD PRIMARY KEY (`id`);

ALTER TABLE `admin`

MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

INSERT INTO `web_notifications`.`admin` (`id`, `username`, `password`, `createdAt`) VALUES (NULL, 'admin', MD5('123456'), CURRENT_TIMESTAMP);

```

Following is the code for admin.php to add the notifications to subscriber(s) account also i have inserted following login credentials for admin in admin table:

username:admin

password:123456

#### 7. Now put following code in admin.php

```

<?php require 'db_connect.php'; ?>

<!DOCTYPE html>

<html>

<head>

<title>ADMIN PAGE</title>

<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">

<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">

<?php

if(isset($_POST['login']))

{

$_SESSION['admin_login']=0;

$username=$conn->real_escape_string($_POST['username']);

$password=$conn->real_escape_string($_POST['password']);

$sql = "SELECT * FROM admin where username='".$username."' and password='".md5($password)."' ";

$result = $conn->query($sql);

if ($result->num_rows > 0) {

$_SESSION['admin_login']=1;

$msg="<p style='color:green'>Admin Logged-in Successfully :)</p>";

}

else {

$msg="<p style='color:red'>INVALID CREDENTIALS FOR ADMIN</p>";

}

}

if(isset($_POST['add_notification']))

{

$title=$conn->real_escape_string($_POST['title']);

$body=$conn->real_escape_string($_POST['body']);

$url=$conn->real_escape_string($_POST['url']);

$users=$_POST['users'];

foreach ($users as $user_id) {

$ins = "insert into notifications set to_user='".$user_id."' , title='".$title."', url='".$url."', body='".$body."' ";

$conn->query($ins);

}

$msg="<p style='color:green'>Notification(s) added to subscribers account.</p>";

}

?>

<div class="container">

<?php

if(isset($msg) && $msg!='')

{

?>

<br>

<div class="alert alert-info">

<?php echo $msg; ?>

</div>

<?php

}

if(isset($_SESSION['admin_login']) && $_SESSION['admin_login']==1)

{

?>

<h2>Welcome Admin, Send notification to Subscriber(s)</h2>

<form method="post">

<div class="form-group">

<label for="sel1">Select Subscriber(s):</label>

<select multiple="multiple" required="required" class="form-control" id="users" name="users[]">

<?php

$sql = "SELECT id,name FROM subscribers";

$result = $conn->query($sql);

$data=array();

if ($result->num_rows > 0) {

while($row = $result->fetch_assoc()) {

echo "<option value='".$row['id']."'>".$row['name']."</option>";

}

}

?>

</select>

</div>

<div class="form-group">

<label for="email">Title</label>

<input type="text" required class="form-control" placeholder="notification title here" name="title" id="title">

</div>

<div class="form-group">

<label for="email">Message</label>

<textarea required class="form-control" placeholder="notification message here" name="body" id="body"></textarea>

</div>

<div class="form-group">

<label for="email">Url</label>

<input type="url" required class="form-control" placeholder="notification landing/click url here" name="url" id="url">

</div>

<input type="submit" class="btn btn-primary btn-block" name="add_notification" value="Submit" />

</form>

<?php

}

else

{

?>

<h2 class="text-center">ADMINISTRATOR</h2>

<div class="row justify-content-center">

<div class="col-12 col-md-8 col-lg-6 pb-5">

<div class="card border-primary rounded-0">

<div class="card-header p-0">

<div class="bg-info text-white text-center py-2">

<h3><i class="fa fa-envelope"></i> LOGIN</h3>

<p class="m-0">provide admin login credentials</p>

</div>

</div>

<div class="card-body p-3">

<form method="post">

<!--Body-->

<div class="form-group">

<div class="input-group mb-2">

<div class="input-group-prepend">

<div class="input-group-text"><i class="fa fa-user text-info"></i></div>

</div>

<input type="text" class="form-control" id="username" name="username" placeholder="Input username here" required>

</div>

</div>

<div class="form-group">

<div class="input-group mb-2">

<div class="input-group-prepend">

<div class="input-group-text"><i class="fa fa-key text-info"></i></div>

</div>

<input type="password" class="form-control" id="password" name="password" placeholder="your password here" required>

</div>

</div>

<div class="text-center">

<input type="submit" value="Login" name="login" class="btn btn-info btn-block rounded-0 py-2">

</div>

</form>

</div>

</div>

</div>

</div>

<?php }?>

</div>

</head>

<body>

</body>

</html>

<?php

$conn->close();

?>

```

The admin page will ask login credentials first then it will look like following screenshot:

![Admin Send Notifiv=cation to subscribers](https://www.w3school.info/blogimages/admin_web_push_page.png)

Now in your project if you open index.php you have a frontend where user will register themselves to receive notifications, and admin.php is your backend where you can send notifications to users or subscribers

If you face any problem in setup this small project please just let me know in the comments below, or by messaging me.

3617 views · 1 years ago

![Working With Thin Controller And Fat Model Concept In Laravel](https://images.ctfassets.net/vzl5fkwyme3u/18L41PfcrcYYkM0qAsCous/7caca26b8cfb5a643d8cb16b14ae5eae/AdobeStock_147870533.jpeg?w=1000)

Models and controllers are one of the most essential programming handlers in the Laravel MVC framework, and both are used vastly for different functional operations. Models in Laravel are created inside the app folder and are mostly used to interact with the database using Eloquent ORM, while the controllers are located inside the directory App/Http/Controllers.

As a programmer, you should have the knowledge how to keep the balance in between the programming usage of Models and controllers. As which one should be more utilized for allowing functional tasks in applications deployed on any [PHP MySQL hosting](https://www.cloudways.com/en/php-cloud-hosting.php).

## What is the Concept of Thin Controller and FAT Models

The concept of the thin controller and fat model is that we do less work in our controllers and more work in our models. Like we use our controllers to validate our data and then pass it to the models. While in models, we define our actual functional logic and main coding operations of the desired application. This code structuring process is also a very basic concept of MVC and also the differentiating factor from the conventional complex programming which we mistakenly ignore sometimes.

## Why FAT Controllers Are Bad For Handling Code

Controllers are always meant to be defined short and concise, and it should only be used for receiving requests and return responses to it. Anything else further should be programmed in Models, which is actually made for main functional operations.

Placing functional logic in controllers can be bad for many reasons for your applications deployed on any _hosting for PHP_. As it not only makes code structure long but also makes it complex sometimes. Further placing code in Controllers is also not recommended because if same functionality is needed somewhere else in route, then pulling out the whole code from their becomes difficult and so its reusability in the application.

Though Laravel is an MVC framework while developing on laravel, we sometimes ignore this and write mostly all our code including the extending of App\Model and all our functional logic in controller route methods. What we can do here is we can create a sub model of our parent model. For example, our parent model is User then we can create another sub model of username in CustomerModel if you are using the same User model for all types of users. In this model, we will write all the logic related to user type Customer.

So now let's take an example of my existing blog creating comment system with laravel and vuejs. In that article, you can see I have made so much mess in my controller methods. Mostly, I have written all my comments logic in my methods, so to shorten that let's clean them in this article. Inside app folder, I will create a new file with name CommentModel.php. Inside this file, I will write my whole logic for comment functions. This is my basic file:

```php

<?php

namespace App;

use App\Comment;

use App\CommentVote;

use App\CommentSpam;

use App\User;

use Auth;

class CommentModel

{

}

?>

```

Right now it contains no function but has the reference of all my models which I required for this model. Let's first add a function named **getallcomments** passing **$pageId** as a parameter inside it. The function will get all the comments for the given page:

```php

public function getAllComments($pageId)

{

$comments = Comment::where('page_id',$pageId)->get();

$commentsData = [];

foreach ($comments as $key) {

$user = User::find($key->users_id);

$name = $user->name;

$replies = $this->replies($key->id);

$photo = $user->first()->photo_url;

/ / dd($photo->photo_url);

$reply = 0;

$vote = 0;

$voteStatus = 0;

$spam = 0;

if(Auth::user()){

$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

if($voteByUser){

$vote = 1;

$voteStatus = $voteByUser->vote;

}

if($spamComment){

$spam = 1;

}

}

if(sizeof($replies) > 0){

$reply = 1;

}

if(!$spam){

array_push($commentsData,[

"name" => $name,

"photo_url" => (string)$photo,

"commentid" => $key->id,

"comment" => $key->comment,

"votes" => $key->votes,

"reply" => $reply,

"votedByUser" =>$vote,

"vote" =>$voteStatus,

"spam" => $spam,

"replies" => $replies,

"date" => $key->created_at->toDateTimeString()

]);

}

}

$collection = collect($commentsData);

return $collection->sortBy('votes');

}

```

Now I will create another function named **replies** which takes **$commentId** as a parameter. The function is more or less programmed in the same manner as the upper function get all comments.

```php

protected function replies($commentId)

{

$comments = Comment::where('reply_id',$commentId)->get();

$replies = [];

foreach ($comments as $key) {

$user = User::find($key->users_id);

$name = $user->name;

$photo = $user->first()->photo_url;

$vote = 0;

$voteStatus = 0;

$spam = 0;

if(Auth::user()){

$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

if($voteByUser){

$vote = 1;

$voteStatus = $voteByUser->vote;

}

if($spamComment){

$spam = 1;

}

}

if(!$spam){

array_push($replies,[

"name" => $name,

"photo_url" => $photo,

"commentid" => $key->id,

"comment" => $key->comment,

"votes" => $key->votes,

"votedByUser" => $vote,

"vote" => $voteStatus,

"spam" => $spam,

"date" => $key->created_at->toDateTimeString()

]);

}

}

$collection = collect($replies);

return $collection->sortBy('votes');

}

```

Now lets create a function **create comment **which passes **$array** as a parameter in it:

```php

public function createComment($arary)

{

$comment = Comment::create($array);

if($comment)

return [ "status" => "true","commentId" => $comment->id ];

else

return [ "status" => "false" ];

}

```

Similarly, Now I will create all the function for comment in my **CommentModel**, so that all the functions gets accumulated in one model.

```php

<?php

namespace App;

use App\Comment;

use App\CommentSpam;

use App\CommentVote;

use App\User;

use Auth;

class CommentModel

{

public function getAllComments($pageId)

{

$comments = Comment::where('page_id', $pageId)->get();

$commentsData = [];

foreach ($comments as $key) {

$user = User::find($key->users_id);

$name = $user->name;

$replies = $this->replies($key->id);

$photo = $user->first()->photo_url;

/ / dd($photo->photo_url);

$reply = 0;

$vote = 0;

$voteStatus = 0;

$spam = 0;

if (Auth::user()) {

$voteByUser = CommentVote::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

$spamComment = CommentSpam::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

if ($voteByUser) {

$vote = 1;

$voteStatus = $voteByUser->vote;

}

if ($spamComment) {

$spam = 1;

}

}

if (sizeof($replies) > 0) {

$reply = 1;

}

if (!$spam) {

array_push($commentsData, [

"name" => $name,

"photo_url" => (string) $photo,

"commentid" => $key->id,

"comment" => $key->comment,

"votes" => $key->votes,

"reply" => $reply,

"votedByUser" => $vote,

"vote" => $voteStatus,

"spam" => $spam,

"replies" => $replies,

"date" => $key->created_at->toDateTimeString(),

]);

}

}

$collection = collect($commentsData);

return $collection->sortBy('votes');

}

protected function replies($commentId)

{

$comments = Comment::where('reply_id', $commentId)->get();

$replies = [];

foreach ($comments as $key) {

$user = User::find($key->users_id);

$name = $user->name;

$photo = $user->first()->photo_url;

$vote = 0;

$voteStatus = 0;

$spam = 0;

if (Auth::user()) {

$voteByUser = CommentVote::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

$spamComment = CommentSpam::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

if ($voteByUser) {

$vote = 1;

$voteStatus = $voteByUser->vote;

}

if ($spamComment) {

$spam = 1;

}

}

if (!$spam) {

array_push($replies, [

"name" => $name,

"photo_url" => $photo,

"commentid" => $key->id,

"comment" => $key->comment,

"votes" => $key->votes,

"votedByUser" => $vote,

"vote" => $voteStatus,

"spam" => $spam,

"date" => $key->created_at->toDateTimeString(),

]);

}

}

$collection = collect($replies);

return $collection->sortBy('votes');

}

public function createComment($arary)

{

$comment = Comment::create($array);

if ($comment) {

return ["status" => "true", "commentId" => $comment->id];

} else {

return ["status" => "false"];

}

}

public function voteComment($commentId, $array)

{

$comments = Comment::find($commentId);

$data = [

"comment_id" => $commentId,

'vote' => $array->vote,

'user_id' => $array->users_id,

];

if ($array->vote == "up") {

$comment = $comments->first();

$vote = $comment->votes;

$vote++;

$comments->votes = $vote;

$comments->save();

}

if ($array->vote == "down") {

$comment = $comments->first();

$vote = $comment->votes;

$vote--;

$comments->votes = $vote;

$comments->save();

}

if (CommentVote::create($data)) {

return true;

}

}

public function spamComment($commentId, $array)

{

$comments = Comment::find($commentId);

$comment = $comments->first();

$spam = $comment->spam;

$spam++;

$comments->spam = $spam;

$comments->save();

$data = [

"comment_id" => $commentId,

'user_id' => $array->users_id,

];

if (CommentSpam::create($data)) {

return true;

}

}

}

?>

```

Now we have all our required methods in **CommentModel**. So now let's clean up **CommentController** which is currently bit complex and lengthy in code structure. As right now **CommentController **look like this:

```php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;

use App\Comment;

use App\CommentVote;

use App\CommentSpam;

use App\User;

use Auth;

class CommentController extends Controller

{

/ **

* Get Comments for pageId

*

* @return Comments

*/

public function index($pageId)

{

/ /

$comments = Comment::where('page_id',$pageId)->get();

$commentsData = [];

foreach ($comments as $key) {

$user = User::find($key->users_id);

$name = $user->name;

$replies = $this->replies($key->id);

$photo = $user->first()->photo_url;

/ / dd($photo->photo_url);

$reply = 0;

$vote = 0;

$voteStatus = 0;

$spam = 0;

if(Auth::user()){

$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

if($voteByUser){

$vote = 1;

$voteStatus = $voteByUser->vote;

}

if($spamComment){

$spam = 1;

}

}

if(sizeof($replies) > 0){

$reply = 1;

}

if(!$spam){

array_push($commentsData,[

"name" => $name,

"photo_url" => (string)$photo,

"commentid" => $key->id,

"comment" => $key->comment,

"votes" => $key->votes,

"reply" => $reply,

"votedByUser" =>$vote,

"vote" =>$voteStatus,

"spam" => $spam,

"replies" => $replies,

"date" => $key->created_at->toDateTimeString()

]);

}

}

$collection = collect($commentsData);

return $collection->sortBy('votes');

}

protected function replies($commentId)

{

$comments = Comment::where('reply_id',$commentId)->get();

$replies = [];

foreach ($comments as $key) {

$user = User::find($key->users_id);

$name = $user->name;

$photo = $user->first()->photo_url;

$vote = 0;

$voteStatus = 0;

$spam = 0;

if(Auth::user()){

$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

if($voteByUser){

$vote = 1;

$voteStatus = $voteByUser->vote;

}

if($spamComment){

$spam = 1;

}

}

if(!$spam){

array_push($replies,[

"name" => $name,

"photo_url" => $photo,

"commentid" => $key->id,

"comment" => $key->comment,

"votes" => $key->votes,

"votedByUser" => $vote,

"vote" => $voteStatus,

"spam" => $spam,

"date" => $key->created_at->toDateTimeString()

]);

}

}

$collection = collect($replies);

return $collection->sortBy('votes');

}

/ **

* Store a newly created resource in storage.

*

* @param \Illuminate\Http\Request $request

* @return \Illuminate\Http\Response

*/

public function store(Request $request)

{

$this->validate($request, [

'comment' => 'required',

'reply_id' => 'filled',

'page_id' => 'filled',

'users_id' => 'required',

]);

$comment = Comment::create($request->all());

/ / dd($comment);

if($comment)

return [ "status" => "true","commentId" => $comment->id ];

}

/ **

* Update the specified resource in storage.

*

* @param \Illuminate\Http\Request $request

* @param $commentId

* @param $type

* @return \Illuminate\Http\Response

*/

public function update(Request $request, $commentId,$type)

{

if($type == "vote"){

$this->validate($request, [

'vote' => 'required',

'users_id' => 'required',

]);

$comments = Comment::find($commentId);

$data = [

"comment_id" => $commentId,

'vote' => $request->vote,

'user_id' => $request->users_id,

];

if($request->vote == "up"){

$comment = $comments->first();

$vote = $comment->votes;

$vote++;

$comments->votes = $vote;

$comments->save();

}

if($request->vote == "down"){

$comment = $comments->first();

$vote = $comment->votes;

$vote--;

$comments->votes = $vote;

$comments->save();

}

if(CommentVote::create($data))

return "true";

}

if($type == "spam"){

$this->validate($request, [

'users_id' => 'required',

]);

$comments = Comment::find($commentId);

$comment = $comments->first();

$spam = $comment->spam;

$spam++;

$comments->spam = $spam;

$comments->save();

$data = [

"comment_id" => $commentId,

'user_id' => $request->users_id,

];

if(CommentSpam::create($data))

return "true";

}

}

/ **

* Remove the specified resource from storage.

*

* @param int $id

* @return \Illuminate\Http\Response

*/

public function destroy($id)

{

/ /

}

}?>

```

After cleaning up the controller it will look much simpler and easy to understand like this:

```php

<?php

namespace App\Http\Controllers;

use App\CommentModel;

use Illuminate\Http\Request;

class CommentController extends Controller

{

private $commentModel = null;

private function __construct()

{

$this->commentModel = new CommentModel();

}

/ **

* Get Comments for pageId

*

* @return Comments

*/

public function index($pageId)

{

return $this->commentModel->getAllComments($pageId);

}

/ **

* Store a newly created resource in storage.

*

* @param \Illuminate\Http\Request $request

* @return \Illuminate\Http\Response

*/

public function store(Request $request)

{

$this->validate($request, [

'comment' => 'required',

'reply_id' => 'filled',

'page_id' => 'filled',

'users_id' => 'required',

]);

return $this->commentModel->createComment($request->all());

}

/ **

* Update the specified resource in storage.

*

* @param \Illuminate\Http\Request $request

* @param $commentId

* @param $type

* @return \Illuminate\Http\Response

*/

public function update(Request $request, $commentId, $type)

{

if ($type == "vote") {

$this->validate($request, [

'vote' => 'required',

'users_id' => 'required',

]);

return $this->commentModel->voteComment($commentId, $request->all());

}

if ($type == "spam") {

$this->validate($request, [

'users_id' => 'required',

]);

return $this->commentModel->spamComment($commentId, $request->all());

}

}

}

?>

```

## Wrap Up!

So Isn't it looking much cleaner and simpler to understand now? This is what actually a thin controller and fat model looks like. We have all our logic related to Comment system programmed in our **CommentModel **and our controller is now just used to transfer data from the user to our model and returning the response which is coming from our model.

So this is how the structuring of the thin controller and fat model is made. Give your thoughts in the comments below.

    SPONSORS

    Faster PHP Cloud Hosting