Today, I’m excited to share a small but mighty React component called CopyToClipboard. It’s sleek, simple, and customizable, making it a perfect addition to your projects.
import React, { useState } from "react";
import { HiMiniClipboardDocument, HiMiniClipboardDocumentCheck } from "react-icons/hi2";
const CopyToClipboard = ({ textToCopy }) => {
const [copied, setCopied] = useState(false);
const copyToClipboard = () => {
navigator.clipboard
.writeText(textToCopy)
.then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1000);
})
.catch((err) => console.error("Failed to copy text:", err));
};
return (
<button
onClick={copyToClipboard}
className="p-2 rounded hover:bg-gray-100 transition"
>
{copied ? (
<HiMiniClipboardDocumentCheck className="text-green-500" />
) : (
<HiMiniClipboardDocument className="text-gray-500" />
)}
</button>
);
};
export default CopyToClipboard;
CopyToClipboard
component into your project:import CopyToClipboard from "./CopyToClipboard";
const App = () => {
const membershipId = "12345678";
return (
<div>
<h1>Your Membership ID</h1>
<p>{membershipId}</p>
<CopyToClipboard textToCopy={membershipId} />
</div>
);
};
export default App;
Why Use It?
CopyToClipboard
component saves you time while maintaining flexibility. Instead of rewriting logic every time you need this feature, simply drop the component into your project, pass the text as a prop, and you're done!This small addition can make a big difference in user experience. I hope this component makes your React development a little sweeter and your users a little happier.
Feel free to customize it and make it your own! Let me know how you’re using CopyToClipboard
in your projects.
If you have been using Laravel since 2013 and later, you may have been using it since the popular release of Laravel version 4.2 on June 1, 2014.
I've been using Laravel since 2012, during its early stages when it was establishing itself as a prominent framework, alongside other frameworks like CodeIgniter. I played around with CodeIgniter for a while and saw that people were loving Laravel, so I shifted to it. Since then, I have witnessed everything that has come to life from Laravel and have enjoyed developing robust Laravel apps and APIs.
In this article, I'm going to take you through a well-known bug in the framework that many people have encountered and reported for a fix. However, Laravel has soared in popularity, and support for version 4.2 has reached its end of life, leading many to stop using this older version of the framework.
Despite this, I've noticed that many businesses do not upgrade their projects to the latest Laravel version over time because it costs money and time, and sometimes the older version continues to work well for them under the hood.
If you have faced this issue, you might have explored the Laravel repository on GitHub and come across the issues that people have created for the bug, such as: "groupBy and paginate problem with Eager Loading" (#5063, #5072). If you look through these issues, you will see that Taylor Otwell, the author of Laravel, has asked people to submit a pull request (PR) for its fix. Unfortunately, no one in the community has managed to address it, as many may not have noticed those issues.
What is the issue, and how to re-create it?
$products = Product::query()
->enabled()
->with(['variants'])
->groupBy('product.id')
->paginate(10);
The above Laravel Eloquent query builder generates the following SQL query:
select * from `product` where `product`.`deleted_at` is null and `product`.`status` = ? group by `product`.`id`
select * from `product_variant` where `product_variant`.`deleted_at` is null and `product_variant`.`product_id` in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ....many more ....)
The problem is that the SQL generated for Eager Loading executes a query that retrieves all results from the product variants table instead of just the specific number of results needed for pagination of the selected products.
So how to solve it?
As I mentioned earlier in the article, Laravel v4.2 has already reached its end of life for support, so do not expect any fixes to be released for the framework.
So, if by any chance you have older projects running on Laravel v4.2 like I do, and need to work on them frequently to add features and fix bugs for clients, you can consider the solution below, which has worked perfectly for me.
<?php
namespace App\Models;
use Illuminate\Support\Facades\DB;
/**
* Class BaseModel
* @package App\Models
*/
class BaseModel extends \Eloquent
{
/**
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $perPage
* @param array $columns
*/
public function scopeCustomPaginate($query, $perPage = null, $columns = ['*'])
{
$perPage = $perPage ?: $this->getPerPage();
$eloquent = $query->getQuery();
$connection = $eloquent->getConnection();
$paginator = $eloquent->getConnection()->getPaginator();
$total = $connection->table(DB::raw("({$query->toSql()}) as subquery"))
->select(DB::raw('count(*) as count'))
->setBindings($query->getBindings())
->first()
->count;
$currentPage = $paginator->getCurrentPage($total);
$results = $query->forPage($currentPage, $perPage)->get($columns);
$stack = [];
$results->each(function ($model) use (&$stack) {
$stack[] = $model;
});
return $paginator->make($stack, $total, $perPage);
}
}
I've added a new class that will later be extended by my Product model.
<?php
namespace App\Models;
/**
* Class Product
* @package App\Models
*/
class Product extends BaseModel
{
....
}
It is not always necessary to create a class and extend it; you can also use a trait in the model. However, my structure has some generic functions in the base model that I need to call in all other models in the project, so I'm keeping them in the class.
Now, see the updated query below.
$products = Product::query()
->enabled()
->with(['variants'])
->groupBy('product.id')
->customPaginate(10)
;
The updated code utilizes the new customPaginate()
method that was introduced as a query scope in the BaseModel, allowing it to be used anywhere in the other models throughout the project.
So results Before vs After are below.
Before
select * from `product` where `product`.`deleted_at` is null and `product`.`status` = ? group by `product`.`id`
select * from `product_variant` where `product_variant`.`deleted_at` is null and `product_variant`.`product_id` in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, .... many more ...)
After
select * from `product` where `product`.`deleted_at` is null and `product`.`status` = ? group by `product`.`id` limit 10 offset 0
select * from `product_variant` where `product_variant`.`deleted_at` is null and `product_variant`.`product_id` in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
The query above does not actually require groupBy
, but I added it to demonstrate the bug.
Thank you for reaching the end of the article. I hope this helps with your project if you have faced this issue.
Happy Coding!
Prerequisites
Before we start, ensure you have PHP and Composer installed on your system. If not, you can download and install them from their respective official websites.
Step 1: Set Up the Project
mkdir php-image-upload
cd php-image-upload
composer init
Follow the prompts to set up your Composer project.
For this example, we'll use the vlucas/phpdotenv
library to manage environment variables:
composer require vlucas/phpdotenv
Step 2: Configure Environment Variables
Create a .env file in the project root to store environment variables:
UPLOAD_DIR=uploads
MAX_FILE_SIZE=2097152 # 2MB in bytes
ALLOWED_TYPES=image/jpeg,image/png,image/gif
Step 3: Create the Directory Structure
Set up the necessary directories and files:
mkdir -p public/uploads
touch public/index.php
touch public/upload.php
Step 4: Create a Helper Class for Handling Uploads
Create a UploadHandler.php file in the src directory:
<?php
namespace App;
class UploadHandler
{
protected $uploadDir;
protected $maxFileSize;
protected $allowedTypes;
public function __construct($uploadDir, $maxFileSize, $allowedTypes)
{
$this->uploadDir = $uploadDir;
$this->maxFileSize = $maxFileSize;
$this->allowedTypes = explode(',', $allowedTypes);
}
public function validate($file)
{
// Check if file was uploaded without errors
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new \Exception('File upload error.');
}
// Check file size
if ($file['size'] > $this->maxFileSize) {
throw new \Exception('File is too large.');
}
// Check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$fileType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($fileType, $this->allowedTypes)) {
throw new \Exception('Invalid file type.');
}
return true;
}
public function upload($file)
{
$this->validate($file);
$fileName = uniqid() . '-' . basename($file['name']);
$destination = $this->uploadDir . '/' . $fileName;
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new \Exception('Failed to move uploaded file.');
}
return $fileName;
}
}
Step 5: Set Up Environment Variables Loading
In the public/index.php file, set up environment variables loading:
<?php
require __DIR__ . '/../vendor/autoload.php';
use Dotenv\Dotenv;
use App\UploadHandler;
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
$uploadDir = $_ENV['UPLOAD_DIR'];
$maxFileSize = $_ENV['MAX_FILE_SIZE'];
$allowedTypes = $_ENV['ALLOWED_TYPES'];
$handler = new UploadHandler($uploadDir, $maxFileSize, $allowedTypes);
Step 6: Create the Upload Form
In the public/index.php file, add the HTML for the upload form:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<h1>Upload File</h1>
<?php if (!empty($_GET['success'])): ?>
<div>
File uploaded successfully!
<img src="uploads/<?php echo htmlspecialchars($_GET['file']); ?>" alt="Uploaded Image">
</div>
<?php endif; ?>
<?php if (!empty($_GET['error'])): ?>
<div>
Error: <?php echo htmlspecialchars($_GET['error']); ?>
</div>
<?php endif; ?>
<form action="upload.php" method="POST" enctype="multipart/form-data">
<label for="file">Choose a file:</label>
<input type="file" name="file" id="file" required>
<button type="submit">Upload</button>
</form>
</body>
</html>
Step 7: Handle the Upload Logic
In the public/upload.php file, handle the file upload logic:
<?php
require __DIR__ . '/../vendor/autoload.php';
use Dotenv\Dotenv;
use App\UploadHandler;
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
$uploadDir = $_ENV['UPLOAD_DIR'];
$maxFileSize = $_ENV['MAX_FILE_SIZE'];
$allowedTypes = $_ENV['ALLOWED_TYPES'];
$handler = new UploadHandler($uploadDir, $maxFileSize, $allowedTypes);
try {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$fileName = $handler->upload($_FILES['file']);
header('Location: index.php?success=1&file=' . urlencode($fileName));
exit;
}
} catch (Exception $e) {
header('Location: index.php?error=' . urlencode($e->getMessage()));
exit;
}
header('Location: index.php');
Step 8: Ensure Permissions
Ensure that the public/uploads
directory is writable by the web server:
chmod -R 775 public/uploads
Conclusion
By following this guide, you have set up a robust file upload system in pure PHP using Composer for dependency management. This setup includes environment variable management with phpdotenv, file validation, and proper error handling. Feel free to customize the validation, error messages, and other configurations to suit your specific requirements.
Also Read:
Image Upload in Laravel: A Comprehensive Guide
]]>
Part 1: Local Storage
Step 1: Create the Upload Controller
Generate a controller to manage the file upload logic:
php artisan make:controller UploadController
Step 2: Create the Form Request Class
Generate a Form Request class for validation:
php artisan make:request UploadFileRequest
Step 3: Define Validation Logic in the Form Request
Open app/Http/Requests/UploadFileRequest.php
and define the validation rules:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;
class UploadFileRequest extends FormRequest
{
public function authorize()
{
return true; // Adjust this if you have authorization logic
}
public function rules()
{
return [
'file' => [
'required',
File::image()
->dimensions(Rule::dimensions()->minWidth(100)->minHeight(100)->maxWidth(2000)->maxHeight(2000))
->max(2048),
],
];
}
public function messages()
{
return [
'file.required' => 'An image file is required.',
'file.image' => 'The file must be an image.',
'file.dimensions' => 'The image dimensions must be between 100x100 and 2000x2000 pixels.',
'file.max' => 'The image size must not exceed 2MB.',
];
}
}
Step 4: Define Routes
Open routes/web.php and define the routes for the upload form and handling the file upload:
<?php
use App\Http\Controllers\UploadController;
Route::get('/upload', [UploadController::class, 'showUploadForm'])->name('upload.form');
Route::post('/upload', [UploadController::class, 'uploadFile'])->name('upload.file');
Step 5: Create the Upload Form
Create a Blade template for the upload form in resources/views/upload.blade.php
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<h1>Upload File</h1>
@if ($errors->any())
<div>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (session('success'))
<div>
{{ session('success') }}
</div>
<img src="{{ session('path') }}" alt="Uploaded Image">
@endif
<form action="{{ route('upload.file') }}" method="POST" enctype="multipart/form-data">
@csrf
<label for="file">Choose a file:</label>
<input type="file" name="file" id="file">
<button type="submit">Upload</button>
</form>
</body>
</html>
Step 6: Implement the Controller Logic
Open app/Http/Controllers/UploadController.php
and implement the methods:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Storage;
class UploadController extends Controller
{
public function showUploadForm()
{
return view('upload');
}
public function uploadFile(UploadFileRequest $request)
{
if ($request->file('file')->isValid()) {
$file = $request->file('file');
$path = $file->store('uploads', 'public');
return back()->with('success', 'File uploaded successfully!')->with('path', Storage::url($path));
}
return back()->withErrors(['file' => 'File upload failed.']);
}
}
Step 7: Configure the Filesystem
Ensure your config/filesystems.php
is configured to use the public disk:
'disks' => [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],
// Other disks...
],
Run the following command to create a symbolic link from public/storage to storage/app/public
:
php artisan storage:link
Part 2: Amazon S3 Storage
To extend the functionality to support Amazon S3, follow these additional steps.
Step 1: Set Up AWS S3
Step 2: Configure Laravel to Use S3
Add your AWS credentials to your .env file:
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=your-region (e.g., us-east-1)
AWS_BUCKET=your-bucket-name
AWS_URL=https://your-bucket-name.s3.amazonaws.com
Update the config/filesystems.php
file to include your S3 configuration:
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'visibility' => 'public',
],
],
Step 3: Implement the Controller Logic for S3 Upload
Modify the UploadController to upload files to S3:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Storage;
class UploadController extends Controller
{
public function showUploadForm()
{
return view('upload');
}
public function uploadFile(UploadFileRequest $request)
{
if ($request->file('file')->isValid()) {
$file = $request->file('file');
$path = $file->store('uploads', 's3');
// Make the file publicly accessible
Storage::disk('s3')->setVisibility($path, 'public');
$url = Storage::disk('s3')->url($path);
return back()->with('success', 'File uploaded successfully!')->with('path', $url);
}
return back()->withErrors(['file' => 'File upload failed.']);
}
}
Step 4: Update the Upload Form
Update the form to allow file uploads without any changes, as the controller already handles the storage to S3:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<h1>Upload File</h1>
@if ($errors->any())
<div>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (session('success'))
<div>
{{ session('success') }}
</div>
<img src="{{ session('path') }}" alt="Uploaded Image">
@endif
<form action="{{ route('upload.file') }}" method="POST" enctype="multipart/form-data">
@csrf
<label for="file">Choose a file:</label>
<input type="file" name="file" id="file">
<button type="submit">Upload</button>
</form>
</body>
</html>
Conclusion
By following this comprehensive guide, you have set up a robust file upload system in Laravel that supports both local storage and Amazon S3. These setups ensure a scalable and flexible file upload solution for your Laravel applications. Feel free to customize the validation, error messages, and storage configurations as needed to suit your specific requirements.
Also Read:
Image Upload in PHP: A Step-by-Step Guide
]]>
Disclaimer: This article is designed for absolute beginners who are just starting their journey into PHP development. If you're already familiar with PHP, you might find this guide too basic.
What is PHP?
PHP stands for "Hypertext Preprocessor" (yes, it's a recursive acronym). It's a server-side scripting language designed specifically for web development. This means that PHP scripts are executed on the server, and the output is sent to the client's browser. PHP is known for its flexibility, ease of use, and integration with HTML, making it a favorite among web developers.
Why Learn PHP?
PHP is a powerful tool for making dynamic and interactive web pages quickly. It's a widely-used, free, and efficient alternative to competitors such as Microsoft's ASP. Here are a few reasons why you should learn PHP:
Setting Up Your Development Environment
Before you start coding, you'll need to set up your development environment. Here’s what you need:
Setting Up on Windows
Setting Up on Linux
sudo apt update
Install Apache:
sudo apt install apache2
sudo apt install mysql-server
Secure your MySQL installation:
sudo mysql_secure_installation
Install PHP:
sudo apt install php libapache2-mod-php php-mysql
sudo systemctl start apache2
Start MySQL:
sudo systemctl start mysql
/var/www/html
.sudo apt install phpmyadmin
- When prompted, select Apache and configure it.
- Open your web browser and go to http://localhost/phpmyadmin.
- Use phpMyAdmin to manage your MySQL databases.
Understanding the Basics
PHP Tags
PHP code is enclosed within <?php ... ?>
tags. Anything outside these tags is treated as regular HTML.
Example:
<!DOCTYPE html>
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<?php
echo "Hello, World!";
?>
</body>
</html>
In this example, PHP is embedded within HTML to dynamically generate content.
Echo Statement
The echo statement outputs text to the browser. It's a basic and commonly used function in PHP.
Example:
<?php
echo "This is a test.";
?>
This will display "This is a test." in the browser.
Comments
Comments are used to leave notes within your code. They are ignored by the PHP engine and are solely for the benefit of the programmer.
//
or #
./* ... */
.Example:
<?php
// This is a single-line comment
# This is also a single-line comment
/*
This is a multi-line comment
It can span multiple lines
*/
echo "Hello, World!"; // This is an inline comment
?>
Writing Your First PHP Script
Now that your environment is set up, let’s write a simple PHP script.
1. Create a New PHP File:
index.php
.C:\xampp\htdocs\index.php
for Windows or /var/www/html/index.php
for Linux).2. Write Some PHP Code:
<?php
echo "Hello, World!";
?>
This script uses the echo
function to output the text "Hello, World!" to the browser.
3. Run Your Script:
http://localhost/index.php
in the address bar and hit enter.Exploring PHP Variables
Variables in PHP are used to store data, such as numbers, strings, arrays, and more. They are declared with the $
symbol followed by the variable name.
Example:
<?php
$name = "John";
$age = 25;
echo "Name: " . $name . "<br>";
echo "Age: " . $age;
?>
In this example, we have two variables, $name
and $age
. The .
operator is used to concatenate strings.
Data Types
PHP supports several data types, including:
Working with Arrays
Arrays are used to store multiple values in a single variable. PHP supports both indexed and associative arrays.
Indexed Arrays
Indexed arrays use numeric indexes.
Example:
<?php
$fruits = array("Apple", "Banana", "Cherry");
echo "I like " . $fruits[0] . ", " . $fruits[1] . " and " . $fruits[2] . ".";
?>
Associative Arrays
Associative arrays use named keys.
Example:
<?php
$age = array("Peter" => "35", "Ben" => "37", "Joe" => "43");
echo "Peter is " . $age['Peter'] . " years old.";
?>
Using Loops
Loops are used to execute a block of code repeatedly. PHP supports several types of loops, including while, do...while, for, and foreach.
While Loop
The while loop executes a block of code as long as the specified condition is true.
Example:
<?php
$x = 1;
while($x <= 5) {
echo "The number is: $x <br>";
$x++;
}
?>
For Loop
The for loop is used when the number of iterations is known.
Example:
<?php
for ($x = 0; $x <= 10; $x++) {
echo "The number is: $x <br>";
}
?>
Functions in PHP
Functions are reusable blocks of code that perform a specific task. PHP has many built-in functions, and you can also create your own.
Built-in Functions
PHP has thousands of built-in functions. Here are a few examples:
strlen()
, str_replace()
, substr()
array_merge()
, array_push()
, array_pop()
abs()
, round()
, rand()
Example:
<?php
echo strlen("Hello World!"); // Outputs: 12
?>
User-Defined Functions
You can create your own functions using the function keyword.
Example:
<?php
function greet($name) {
return "Hello, " . $name . "!";
}
echo greet("John");
?>
This function takes a parameter $name and returns a greeting message.
Handling Forms with PHP
Forms are essential for interacting with users. PHP can collect form data and process it.
1. Create an HTML Form:
<form action="welcome.php" method="post">
Name: <input type="text" name="name"><br>
Email: <input type="text" name="email"><br>
<input type="submit">
</form>
2. Process Form Data:
<?php
$name = $_POST['name'];
$email = $_POST['email'];
echo "Welcome, $name!<br>";
echo "Your email address is: $email";
?>
In this example, the form data is sent to welcome.php using the POST method. The PHP script then processes and displays the data.
Connecting to MySQL Database
Connecting PHP to a MySQL database allows you to store and retrieve data dynamically.
Create a Database:
Open phpMyAdmin and create a new database named test_db
.
Create a Table:
In phpMyAdmin, create a table named users
with columns id
, name
, and email
.
Connect to the Database:
<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test_db";
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
echo "Connected successfully";
?>
Insert Data:
<?php
$sql = "INSERT INTO users (name, email) VALUES ('John Doe', '[email protected]')";
if ($conn->query($sql) === TRUE) {
echo "New record created successfully";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
$conn->close();
?>
Retrieve Data:
<?php
$sql = "SELECT id, name, email FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
// Output data of each row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - Name: " . $row["name"]. " - Email: " . $row["email"]. "<br>";
}
} else {
echo "0 results";
}
$conn->close();
?>
Moving Forward
Now that you’ve got the basics down, you can start exploring more advanced topics like sessions, cookies, file handling, object-oriented programming, and security best practices. PHP is a powerful language with a lot of built-in functions and a vibrant community, so there’s always something new to learn.
Conclusion
Getting started with PHP is an exciting journey into web development. With its ease of use and powerful capabilities, you'll be building dynamic and interactive websites in no time. Remember to practice regularly, explore PHP documentation, and engage with the community for support and inspiration.
Happy coding!
Also Read:
Image Upload in PHP: A Step-by-Step Guide
Flexibility of Invokable PHP Classes
An alternative way for PHP array_column
In this blog post, we'll explore an enhanced version of a recursive function designed to filter and trim data within a multidimensional array.
I came up with this function while I was working on a Mastercard Payment Gateway Services (MPGS) integration for a client's project.
Here, I needed this function to avoid empty array elements and trim the array elements.
Initially, I developed a basic version that handled recursive filtering. Later, it came to my mind that I needed to handle both recursive filtering and trimming whitespace from strings, so I extended it.
Prerequisite
You need to have a basic understanding of PHP to follow this article. This guide is aimed at mid-level PHP developers.
The Function
The original function handled arrays recursively, removing empty elements effectively. Here’s the initial version of the function:
public function filterDataRecursively($array = [])
{
foreach ($array as &$value) {
if (is_array($value)) {
$value = $this->filterDataRecursively($value);
}
}
return array_filter($array);
}
Limitations of the Initial Version:
This function only processes arrays and does not handle other types of elements within the array, such as strings.
To address this limitation, I extended the function to trim whitespace from string elements while maintaining the recursive filtering of arrays. Here’s the enhanced version:
public function filterDataRecursively($array = [])
{
$callback = function ($item) {
if (is_array($item)) {
return $this->filterDataRecursively($item);
} elseif (is_string($item)) {
return trim($item);
}
return $item;
};
$array = array_map($callback, $array);
return array_filter($array);
}
How It Works:
After applying the callback to all elements using array_map
, the function filters out any false
-y values (like null
, false
, empty strings, etc.) using array_filter
.
This enhanced version not only preserves the original functionality but also extends it by ensuring strings are clean and free of unnecessary whitespace, making your data cleaner and more reliable.
Tip
I would like to share a bonus tip with this article. While the PHP method we've discussed above is primarily designed for use within PHP classes, there's a neat trick to make it even handier: crafting it as a PHP helper function.
Here's how you can do it:
if (!function_exists('filter_trim_recursive_array')) {
function filter_trim_recursive_array($array = [])
{
$callback = function ($item) {
if (is_array($item)) {
return filter_trim_recursive_array($item);
} elseif (is_string($item)) {
return trim($item);
}
return $item;
};
$array = array_map($callback, $array);
return array_filter($array);
}
}
Package
Additionally, I'd like to mention that I've created an MPGS package on top of the Omnipay payment gateway integration library. You can find the package on my GitHub profile.
If you have any feedback or feature submissions for the library, feel free to submit a pull request following the coding standards outlined in the Omnipay library's guidelines.
Your contributions are highly appreciated!
Conclusion
By extending the original recursive array filtering function to include trimming of string elements, we’ve created a more robust and versatile tool.
This small but significant enhancement can save you time and effort in managing arrays and ensuring your data is as clean as possible.
Thank you for taking the time to read this post. I hope you find this enhanced PHP function useful in your projects. If you have any questions or suggestions, feel free to leave a comment here.
Happy coding!
]]>
Using a cheat sheet isn't limited to beginner or intermediate developers; it's valuable for developers at any level. It serves as a quick reference for Laravel's code syntax and ecosystem features, making it essential for professionals as well.
I've previously covered a few versions of the Laravel Cheat Sheet on this blog, but some of them are already unlisted because they no longer exist now.
We've updated to the new ones as they are updated by the original creators.
What is a Laravel Cheat Sheet?
A Laravel Cheat Sheet is like a quick guidebook that gives you easy access to important info about Laravel. It helps developers by showing examples about every convention of Laravel features like routes, controllers, views, models, commands etc. and common tasks that a Laravel framework can perform in your project.
What are some of the best available cheat sheets?
The Artisan Page, created by James Brooks, a Laravel core team member, serves as a comprehensive cheat sheet for all things related to Laravel Artisan Commands. Recently, the author announced full support for all first-party pages from the Laravel ecosystem.
You can visit the Artisan Page to review both earlier versions and the latest version of Laravel from their navigation menu.
The creator has open-sourced this project on GitHub, making it accessible to everyone, and can also be contributed to it if you have any features, ideas or fixes on it.
Laravel Cheat Sheet, This is another fantastic Laravel cheat sheet which covers everything about Laravel features. However, it does not have any navigation about the supported versions, you will need to have certain knowledge about what sort of things have changed in between the Laravel major version releases.
There is also a get started section, which offers you how to get started on different environments like Windows, Mac, Linux, and basic requirements.
As you go through this cheat sheet, remember to consider the version of Laravel you're using and any changes in parameters or features across different versions. They've included a note stating that it serves as a reference for common commands and features in Laravel.
Also Read: Laravel Cheat Sheet for Verson 4x, 5x
These cheat sheets were primarily created for older Laravel versions like 4.x and 5.x. However, they still cover generic Laravel features that remain relevant, and newer versions have improved upon them. Therefore, they are not considered outdated.
Thank you for reading the article all the way through. If you found it helpful, please consider sharing it with your developer circle.
Stay tuned for more upcoming articles!
]]>
As I was working on the report section of my project, I needed to fetch abandoned carts and display their values. The prices were stored in a table called product_prices, which contained various pricing conditions. If a price matched in this table, it was used. Otherwise, the query would fall back to the cart_item.unit_price column. Let's delve into it further below.
This got me thinking—I should share this handy solution with my community through a blog post, and here you are reading about it!
We'll be using three example database tables: cart, cart_item, and product_price. By the end of this guide, you'll understand how to handle null values effectively and provide fallbacks to another column in your Laravel applications.
Prerequisites
Before we begin, make sure you have a basic understanding of Laravel and Eloquent ORM.
Handling Null Values and Fallbacks in Laravel Eloquent Queries
As I mentioned earlier, I needed to fallback to the default price from the cart_item.unit_price when the product price was not found in the product_price table. The product_price table stored conditional prices, but I required the default price. Therefore, if the default price was not available, the fallback price would be displayed.
Now, let me illustrate this concept using the query below and explain it further.
To tackle this, let's break down the provided code snippet:
return CartItem::query()
->selectRaw(
DB::raw(
"SUM(cart_item.quantity) as total_abandoned,
COALESCE(
(SELECT
price
FROM
product_price
WHERE
product_price.variant_id = cart_item.variant_id
AND product_price.role_id IS NULL
AND product_price.is_special = 0
LIMIT 1),
cart_item.unit_price
) * SUM(cart_item.quantity) AS total_value,
cart_item.sku,
CONCAT_WS('', product.name, cart_item.name) as product_name,
product_variant.title as variant_name,
CONCAT_WS('||', IFNULL(cart_item.variant_id, 'x'), cart_item.sku) as group_column"
)
)
->join('cart', 'cart.id', '=', 'cart_item.cart_id')
->leftJoin('product', 'cart_item.product_id', '=', 'product.id')
->leftJoin('product_variant', 'cart_item.variant_id', '=', 'product_variant.id')
->where('cart.is_processed', 0)
->whereNotNull('cart_item.sku')
->groupBy('group_column')
->orderBy('total_abandoned', 'desc');
Here's what's happening:
selectRaw()
method.COALESCE()
function is used to handle null values and fallback to another column if necessary. In this case, we're checking if there's a custom price for the product in the product_price table. If not, we fallback to the unit price of the cart item.where()
method to filter the results. For example, we're only selecting cart items from carts that are not processed (cart.is_processed
is 0).group_column
) and ordering them by the total quantity of abandoned items in descending order.This code snippet efficiently handles null values and provides fallbacks, ensuring that our report on abandoned cart items is accurate and informative.
By understanding and implementing concepts like this in our Laravel projects, we can write robust and reliable code that meets our business requirements effectively.
Conclusion
You've now learned how to handle null values and provide fallbacks in Laravel Eloquent queries for the cart, cart_item, and product_price tables. Experiment with these techniques in your own Laravel applications to handle null values effectively and provide appropriate fallbacks when necessary. Happy querying!
]]>
Prerequisites
Before we begin, ensure that you have a basic understanding of HTML, CSS, and the respective framework or library you'll be using (JavaScript, jQuery, Vue.js, or Alpine.js).
HTML Structure
Let's start by defining the HTML structure of our webpage. We'll create a simple layout with a navigation menu and sections that we want to scroll to.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smooth Scrolling Examples</title>
<style>
/* Add your CSS styles here */
</style>
</head>
<body>
<nav>
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
<li><a href="#section3">Section 3</a></li>
</ul>
</nav>
<section id="section1">
<h2>Section 1</h2>
<!-- Content for section 1 -->
</section>
<section id="section2">
<h2>Section 2</h2>
<!-- Content for section 2 -->
</section>
<section id="section3">
<h2>Section 3</h2>
<!-- Content for section 3 -->
</section>
</body>
</html>
JavaScript Example (Vanilla)
Let's implement smooth scrolling using vanilla JavaScript. Add the following JavaScript code to your HTML file:
<script>
document.addEventListener('DOMContentLoaded', function() {
const links = document.querySelectorAll('nav a');
links.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
const offsetTop = targetElement.offsetTop;
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
});
}
});
});
});
</script>
jQuery Example
Let's implement smooth scrolling using jQuery. Add the following jQuery code to your HTML file:
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
$('nav a').click(function(e) {
e.preventDefault();
const targetId = $(this).attr('href').substring(1);
const targetOffset = $('#' + targetId).offset().top;
$('html, body').animate({
scrollTop: targetOffset
}, 1000);
});
});
</script>
Vue.js Example
Now, let's implement smooth scrolling using Vue.js. Add the following Vue.js code to your HTML file:
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
new Vue({
el: '#app',
methods: {
scrollTo(id) {
const targetOffset = document.getElementById(id).offsetTop;
window.scrollTo({
top: targetOffset,
behavior: 'smooth'
});
}
}
});
</script>
Alpine.js Example
Lastly, let's implement smooth scrolling using Alpine.js. Add the following Alpine.js code to your HTML file:
<script src="https://cdn.jsdelivr.net/npm/alpinejs@2"></script>
<script>
window.alpineInit = function() {
return {
scrollTo(id) {
const targetOffset = document.getElementById(id).offsetTop;
window.scrollTo({
top: targetOffset,
behavior: 'smooth'
});
}
};
}
</script>
Conclusion
You've now learned how to implement smooth scrolling to an element on a webpage using JavaScript, jQuery, Vue.js, and Alpine.js. Experiment with these examples and choose the method that best fits your project's requirements. Smooth scrolling enhances user experience and makes navigation more intuitive for your website visitors.
Happy coding!
Understanding Foreign Key Constraints:
Before diving into the implementation details, let's briefly review what foreign key constraints are and why they're essential. Foreign key constraints establish relationships between tables in a relational database, ensuring that data consistency is maintained across related records. By enforcing referential integrity, foreign key constraints prevent orphaned records and maintain the integrity of your data model.
Retrieving Foreign Key Constraints with Laravel Eloquent:
In Laravel, Eloquent provides a powerful and intuitive interface for interacting with the database. We can leverage Eloquent to retrieve information about foreign key constraints from the database schema effortlessly. Here's how to do it:
Step 1: Define the Model:
Start by creating a model for the information_schema.key_column_usage table. Since this table resides in the information_schema database, we need to specify the connection name for this model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class KeyColumnUsage extends Model
{
protected $table = 'information_schema.key_column_usage';
public $timestamps = false;
}
Step 2: Retrieve Foreign Key Constraints:
Once we've defined the model, we can use Eloquent to query the information_schema.key_column_usage table and retrieve foreign key constraints. Here's how to do it:
use App\Models\KeyColumnUsage;
$results = KeyColumnUsage::select('TABLE_NAME as table_name', 'COLUMN_NAME as column_name', 'CONSTRAINT_NAME as constraint_name', 'REFERENCED_TABLE_NAME as referenced_table_name', 'REFERENCED_COLUMN_NAME as referenced_column_name')
->where('REFERENCED_TABLE_SCHEMA', DB::getDatabaseName())
->whereNotNull('REFERENCED_TABLE_NAME')
->get();
This query selects relevant columns from the information_schema.key_column_usage table, filters by the current database schema, and retrieves rows where the REFERENCED_TABLE_NAME column is not null.
In a recent project, I faced a big problem with foreign key constraints when trying to implement force delete functionality. The issue arose because different database engines treated these constraints differently. To solve this problem, I came up with a new plan: I decided to get rid of all the foreign key constraints and keep them only as indexes.
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ForeignKeyToIndexCommand extends Command
{
/**
* @var string
*/
protected $signature = 'foreign-key-to-index';
/**
* @var string
*/
protected $description = 'Replace Database Foreign Key with Index Key.';
/**
* @return void
*/
public function handle()
{
$sql = "SELECT `TABLE_NAME` as `table_name`, `COLUMN_NAME` as `column_name`, `CONSTRAINT_NAME` as `constraint_name`, `REFERENCED_TABLE_NAME` as `referenced_table_name`, `REFERENCED_COLUMN_NAME` as `referenced_column_name` FROM `information_schema`.`key_column_usage` WHERE `REFERENCED_TABLE_SCHEMA` = DATABASE() AND `REFERENCED_TABLE_NAME` IS NOT NULL";
$results = DB::select($sql);
if (!sizeof($results)) {
return $this->info("* No Foreign Key Constraints Found. * \n");
}
foreach ($results as $k => $result) {
Schema::table($result->table_name, function (Blueprint $table) use ($result) {
if ($this->confirm("* Would you like to Drop Foreign Key [{$result->constraint_name}], and Add Index [y/n] *")) {
$table->dropForeign($result->constraint_name);
$this->info("* Dropped foreign key constraint: {$result->constraint_name} * \n");
}
$index_column = "{$result->table_name}_{$result->column_name}_index";
$this->info("* Querying INDEX Key {$index_column} * \n");
$index_stack = [];
$table_indexes = DB::select("SHOW INDEXES FROM $result->table_name");
foreach ($table_indexes as $index) {
if ($index->Key_name == $index_column) {
$index_stack[] = $index_column;
}
}
if (!sizeof($index_stack)) {
$table->index([$result->column_name]);
$this->info("* Added INDEX column {$index_column} * \n");
} else {
$this->info("* INDEX column {$index_column} already exists.* \n");
}
});
}
}
}
Now, updating the console command as Laravel ORM style.
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\KeyColumnUsage;
class ForeignKeyToIndexCommand extends Command
{
/**
* @var string
*/
protected $signature = 'foreign-key-to-index';
/**
* @var string
*/
protected $description = 'Replace Database Foreign Key with Index Key.';
/**
* @return void
*/
public function handle()
{
$results = KeyColumnUsage::select(
'TABLE_NAME as table_name',
'COLUMN_NAME as column_name',
'CONSTRAINT_NAME as constraint_name',
'REFERENCED_TABLE_NAME as referenced_table_name',
'REFERENCED_COLUMN_NAME as referenced_column_name'
)
->where('REFERENCED_TABLE_SCHEMA', DB::getDatabaseName())
->whereNotNull('REFERENCED_TABLE_NAME')
->get();
if (!$results->count()) {
return $this->info("* No Foreign Key Constraints Found. * \n");
}
foreach ($results as $k => $result) {
Schema::table($result->table_name, function (Blueprint $table) use ($result) {
if ($this->confirm("* Would you like to Drop Foreign Key [{$result->constraint_name}], and Add Index [y/n] *")) {
$table->dropForeign($result->constraint_name);
$this->info("* Dropped foreign key constraint: {$result->constraint_name} * \n");
}
$index_column = "{$result->table_name}_{$result->column_name}_index";
$this->info("* Querying INDEX Key {$index_column} * \n");
$index_stack = [];
$table_indexes = DB::select("SHOW INDEXES FROM $result->table_name");
foreach ($table_indexes as $index) {
if ($index->Key_name == $index_column) {
$index_stack[] = $index_column;
}
}
if (!sizeof($index_stack)) {
$table->index([$result->column_name]);
$this->info("* Added INDEX column {$index_column} * \n");
} else {
$this->info("* INDEX column {$index_column} already exists.* \n");
}
});
}
}
}
Conclusion:
By utilizing the power of Laravel's Eloquent ORM, we can seamlessly retrieve foreign key constraints from the database, empowering us to ensure data integrity and streamline schema management. Whether you're building a new application or maintaining an existing one, leveraging Eloquent for database interactions offers unparalleled flexibility and ease of use.
Wrapping Up:
In this guide, we've explored how to use Laravel's Eloquent ORM to retrieve foreign key constraints from the database schema. By following the steps outlined above, you can effortlessly access vital information about your database structure, enhancing data integrity and simplifying schema management in your Laravel applications.
]]>
This article assumes that you have completed a full-fledged PayPal integration using the library explained in the previous article.
Please Read: How to Integrate PayPal REST API for Online Payments
Let's begin by adding the route needed to access the web app with a POST request from the PayPal webhook service.
/**
* PayPal REST API Webhook
*/
Route::post('/checkout/webhook/paypal/rest', [
'as' => 'checkout.webhook.paypal.rest',
'name' => 'PayPal Rest Webhook',
'uses' => 'App\Controllers\Webhook\PayPalRestController@validate',
]);
Now, let's set up a controller that can accept the HTTP request from the PayPal webhook service. This controller will handle both the headers data and the payload.
<?php
namespace App\Controllers\Webhook;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Event;
/**
* Class PayPalRestController
* @package App\Controllers\Webhook
*/
class PayPalRestController extends Controller
{
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* @return \Illuminate\Http\Response
*/
public function validate()
{
$data = $this->request->all();
$headers = $this->request->headers->all();
$event_type = $data['event_type'] ?? null;
$verify_payload = [
'auth_algo' => $this->request->headers->get('PAYPAL-AUTH-ALGO'),
'cert_url' => $this->request->headers->get('PAYPAL-CERT-URL'),
'transmission_id' => $this->request->headers->get('PAYPAL-TRANSMISSION-ID'),
'transmission_sig' => $this->request->headers->get('PAYPAL-TRANSMISSION-SIG'),
'transmission_time' => $this->request->headers->get('PAYPAL-TRANSMISSION-TIME'),
'webhook_id' => 'REPLACE-WITH-YOUR-OWN',
'webhook_event' => $data,
];
$gateway = Omnipay::create('PayPalRest_Rest');
$gateway->setClientId('xxxxxxxxxxxxxx');
$gateway->setSecret('xxxxxxxxxxxxxx');
$gateway->setTestMode(true);
$response = $gateway->verifyWebhookSignature($verify_payload);
if ($response->isSuccessful()) {
// webhook verified by PayPal
// handle the webhook event as necessary
return \Response::make(null);
}
// verification failed.
}
}
It's important to verify the webhook request sent by PayPal to confirm that the PayPal data hasn't been altered and that the request itself originates from PayPal.
Once it's verified, you can proceed to handle the event types that you've subscribed to under the webhook links added to the PayPal dashboard, associated with the credentials of your PayPal apps.
If you are managing webhook records through the API, it's essential to use the corresponding webhook ID for verifying the webhook signatures. For manual handling, you should provide the webhook ID generated in the PayPal dashboard and pass it into the provided codebase above.
Closing
Thank you for reading the article all the way to the end. Please share it within your circle, and continue visiting our website for more articles in the future.
]]>Follow this link: PayPal Payment Gateway for Business to scroll through those articles.
Let's get started with the PayPal REST API, which we will cover in the article below.
PayPal is a globally recognized payment gateway service that is compatible for businesses of all sizes, whether small or large, to accept online payments. It is known for its security, reliability, and reputation as a trusted payment gateway, making it one of the most trusted in the world.
I recently released a new, framework-agnostic library for PHP to handle the PayPal REST API. You can view it on GitHub and explore the features it offers to meet your needs.
Furthermore, if you require any additional features for the package and have the ability to submit a pull request following the provided coding standards, your contributions are welcome.
# Prerequisites
A good understanding of PHP, some basic knowledge of the Laravel framework, familiarity with Composer, and proficiency in PHP, MySQL databases, as well as having a working environment ready to run the project are prerequisites.
# Composer
{
"require": {
"sudiptpa/paypal-rest": "~3.0"
}
}
To add the library to your project, use the Composer package manager.
Now, you can set up your routes for your application and create a controller where you'll place your application logic for initiating payments.
use Omnipay\Omnipay;
$gateway = Omnipay::create('PayPalRest_Rest');
$gateway->setClientId('xxxxxxxxxxx');
$gateway->setSecret('xxxxxxxxxxx');
$gateway->setTestMode('xxxxxxxxxxx');
You can initiate the library with the above code snippet, and you will now have access to the features within the library.
Now, let's outline the requirements for how the payment flow works:
# Create Order
Here, the process involves creating the order on the PayPal platform, not on the website itself. The assumption is that the order, awaiting payment, has already been established on the website. Now, it's time to direct the user to the PayPal hosted platform to provide their payment details.
Below is the code snippet offered by the library.
<?php
use Omnipay\Common\CreditCard;
...
$payload = [
'amount' => 20,
'transactionId' => '1001',
'transactionReference' => 'INV-1001',
'currency' => 'AUD',
'card' => new CreditCard([
'shippingFirstName' => 'First Name',
'shippingLastName' => 'Last Name',
'email' => 'Email Address',
'shippingPhone' => 'Phone Number',
'shippingAddress1' => 'Street Address',
'shippingAddress2' => 'House Number, Apt.',
'shippingCity' => 'City, Home Town',
'shippingState' => 'State, Province',
'shippingPostcode' => 'Postal code',
'shippingCountry' => 'AU',
]),
'shippingType' => 'SHIPPING',
'items' => [
[
'name' => 'Test Product 1',
'description' => 'A sample description',
'quantity' => 1,
'price' => 20,
'sku' => 'ITEM-CODE1',
'category' => 'PHYSICAL_GOODS',
'reference' => 'ITEM',
]
],
'cancelUrl' => 'https://example.com/cancel/url',
'returnUrl' => 'https://example.com/return/url',
];
$response = $gateway->purchase($payload)->send();
if ($response && $response->isSuccessful()) {
$order->update(['paypal_order_id' => $response->getTransactionReference()]);
if ($response->isRedirect()) {
$response->redirect();
}
// do something else
}
// handle the failure
You can send multiple items, but the item reference indicates whether it relates to shipping, tax, discount, handling, insurance, or the order line item. Please review the enum class to see the supported item references.
# Capture
After returning from PayPal, you need to complete the payment capture process for the order.
$response = $gateway->completePurchase([
'transactionReference' => $order->paypal_order_id,
])->send();
if ($response && $response->isSuccessful() && $response->isCaptured()) {
// handle the successful payment
}
if ($response && $response->isFailure()) {
// show error message
}
Furthermore, this library also enables sending tracking information to PayPal for the order using the capture ID it provides after a successful payment capture.
Integrating a webhook is a good practice to automatically validate whether the website order has been marked as paid or refunded. There may be times where the website fails to update the database in real-time when the payment capture occurs. In such cases, a webhook becomes crucial for managing the order status effectively.
Also Read:
Stripe Payment Gateway Integration with Laravel
PayPal Integration – Omnipay PayPal PHP Library
PayPal Instant Payment Notification (IPN) Handling
# Closing
Thank you for following the article up to the end. If you found it interesting, please consider sharing it with others who might also find it worthwhile.
In our next article, we'll explain about webhook verification and handling. We request you to keep visiting our website for more insightful articles like this one in the future.
]]>Why did I change the hosting provider?
I had been using my previous hosting provider from 2015 until August 2023. However, we have now switched to Vultr as our hosting provider.
The sole reason is that I have several other VPS servers with Vultr for my clients. With my previous hosting, I only had one server. I wanted to consolidate them all in one place for easier maintenance and periodic review.
In this blog, I will outline the steps we followed to migrate to a new server along with the new domain.
Moving your established blog to a new domain can be a strategic step to refresh your online presence or align better with your niche. However, executing a seamless migration requires a more advanced approach.
Disclaimer: By using the provided links for Vultr hosting, you grant us a small commission that helps sustain our blog, while also receiving credits to spin up your new server.
Backup
I took a complete backup of my images and database as the initial step with the previous hosting provider. I ensured their safety while proceeding with the following tasks.
Infrastructure
Being familiar with technology, I know how to set up VPS servers with Linux OS. I created a new server, set it up with the latest Ubuntu version, and configured Linux, Nginx, and PHP with necessary modules.
After sorting out the server and SSL setup, I tested everything by pointing them to a new IP using local host changes on my machine.
Vultr provides the option to manage DNS directly from their user dashboard, making it easy to set up simple DNS for the domain hosted on the server.Therefore, we've leveraged DNS management directly within the Vultr dashboard.
Also Read
An Ultimate Guide to Starting a Successful Blog
Get a Free SSL Certificate for Your Website
SSL
We utilize Cloudflare to safeguard our blog against DDoS attacks and benefit from its CDN and DNS services. They also offer free SSL certificates, which is great. While setting up and updating the SSL, I found that the SSL certificate I was using didn't work for the new testing.
So, we had to create new SSL keychains and set them up again.
Migration
Once the server setup was done and ready for migration, we cloned the code from the repository and moved assets, images, the database, and cron jobs.
Using Cloudflare was particularly helpful for DNS. We quickly changed the IP to point to the new server, smoothly transitioning my existing blog to the new domain within minutes.
SEO
Once the migration was complete, we set up a new property in Google Search Console. Additionally, we marked the old property to indicate that the site was moving to a new domain.
Furthermore, to ensure that my Google indexed pages remained consistent, we promptly submitted the new pages for indexing by Google.
To accomplish this, we updated the sitemap link, enabling Google to crawl the pages daily.
The website is up on the new server after the move. If you find any mistakes or have concerns, please let us know using the Contact Us page.
Conclusion
Thanks for checking out our migration process. Stay tuned for more blog posts in the future.
If you like what you read, feel free to share our articles with others.
]]>
Laravel, one of the most popular PHP frameworks, is renowned for its simplicity, elegance, and developer-friendly features. Among its array of powerful functionalities, "Invokable Controllers" stands out as an exceptional feature that can significantly enhance the organization and efficiency of your Laravel applications. In this blog post, we will explore the concept of invokable controllers in Laravel, understand their benefits, and learn how to leverage them to streamline your code and improve the overall development experience.
What are Invokable Controllers?
Invokable controllers, also known as "single-action controllers," are a type of controller in Laravel that can be treated as a callable entity. Instead of defining multiple methods for different actions, invokable controllers contain a single __invoke()
method. This unique feature allows the controller itself to be invoked as if it were a closure or a function, making it an elegant solution for handling single-action routes.
The Beauty of Simplicity:
One of the main advantages of using invokable controllers is their simplicity. By having a single __invoke()
method in the controller, you can streamline your code and avoid clutter caused by multiple action methods. This approach is particularly beneficial for routes that perform a specific action without the need for additional methods.
Creating an Invokable Controller:
Creating an invokable controller in Laravel is straightforward. Let's take a look at an example:
// app/Http/Controllers/MyController.php
namespace App\Http\Controllers;
class MyController
{
public function __invoke($id)
{
// Your controller logic using $id
// ...
}
}
In this example, we have defined an invokable controller named MyController
. The __invoke()
method is responsible for handling the logic of the controller and accepting a parameter $id
.
Routing with Invokable Controllers:
To use the invokable controller in your routes, simply reference the controller class as the route's action:
// routes/web.php or routes/api.php
use App\Http\Controllers\MyController;
Route::get('/example/{id}', MyController::class);
Now, when a request is made to /example/{id}
, Laravel will automatically invoke the __invoke()
method of the MyController
class, passing the {id}
as an argument.
Advantages of Invokable Controllers:
Code Organization: Invokable controllers help maintain a cleaner and more organized codebase, especially for single-action routes.
Reusability: With a single-action controller, you can easily reuse the controller logic across different routes, enhancing code modularity.
Readability: Using invokable controllers can make your code more expressive and self-documenting, as it highlights the single purpose of the controller.
Route Simplification: Invokable controllers eliminate the need for additional method definitions in the controller, leading to concise and straightforward route declarations.
Conclusion:
Invokable controllers are a valuable feature in Laravel that brings simplicity and elegance to your application's architecture. By embracing this approach, you can improve code organization, enhance readability, and create modular and maintainable applications. The power of invokable controllers lies in their ability to transform your code into concise, single-action entities, providing a seamless development experience.
Next time you encounter a single-action route in your Laravel project, consider employing an invokable controller to unlock its full potential. Embrace the simplicity and elegance of Laravel's invokable controllers to create cleaner, more maintainable, and efficient code.
Happy coding!
PHP, being a versatile and widely used programming language, offers various features to enhance code flexibility and organization. One such powerful feature is "Invokable PHP Classes" or "Callable Classes." These classes can be invoked as if they were functions, enabling developers to encapsulate behavior and use classes as callable entities. In this blog post, we will delve into the world of invokable PHP classes, exploring their potential applications, and showcasing examples of how they can be utilized to create clean and maintainable code.
1. Dependency Injection:
class MyService
{
public function __invoke($arg)
{
// Service logic using $arg
}
}
// Register the invokable class in the dependency injection container
$container->register('my_service', MyService::class);
// Resolve and execute the service
$service = $container->get('my_service');
$result = $service($someArgument);
2. Middleware:
class AuthenticationMiddleware
{
public function __invoke($request, $next)
{
// Perform authentication logic before passing to the next handler
// $request = ... modify the request if needed
$response = $next($request);
// Perform actions after the request has been handled (if needed)
return $response;
}
}
// Usage in a middleware stack
$middlewareStack = [
new AuthenticationMiddleware(),
// Other middleware classes
// ...
$finalRequestHandler,
];
foreach ($middlewareStack as $middleware) {
$finalRequestHandler = $middleware($finalRequestHandler);
}
$response = $finalRequestHandler($request);
3. Closures with State:
class Counter
{
private $count = 0;
public function __invoke()
{
return $this->count++;
}
}
$counter = new Counter();
echo $counter(); // Output: 0
echo $counter(); // Output: 1
echo $counter(); // Output: 2
4. Custom Function Wrappers:
class FunctionWrapper
{
public function __invoke($function, ...$args)
{
// Perform actions before calling the function
// ...
$result = $function(...$args);
// Perform actions after calling the function
// ...
return $result;
}
}
$wrapper = new FunctionWrapper();
$wrappedFunction = $wrapper(function ($a, $b) {return $a + $b;}, 3, 5);
echo $wrappedFunction; // Output: 8
5. API Routing and Controllers:
class UserController
{
public function __invoke($request, $response)
{
$userId = $request->getParam('id');
// Fetch user data using $userId
// ...
return $response->withJson($userData);
}
}
// Define API routes
$app->get('/users/{id}', UserController::class);
6. Command-Line Tasks:
class ImportDataTask
{
public function __invoke($options)
{
// Read options and perform the data import task
// ...
}
}
// Execute the command-line task
$importTask = new ImportDataTask();
$importTask(['file' => 'data.csv', 'format' => 'csv']);
7. Event Listeners:
class UserRegisteredListener
{
public function __invoke($event)
{
$user = $event->getUser();
// Perform actions when a user is registered
// ...
}
}
// Register the event listener
$dispatcher->addListener('user.registered', new UserRegisteredListener());
8. Custom Filters:
class TrimFilter
{
public function __invoke($value)
{
return trim($value);
}
}
$trimFilter = new TrimFilter();
$cleanedValue = $trimFilter(' Hello, world! ');
echo $cleanedValue; // Output: "Hello, world!"
Also Read: Simplifying Laravel Development with Invokable Controllers
Laravel
We share a lot of articles in this blog regarding the Laravel framework. Here, you may have questions about whether any of the Laravel features include invokable PHP classes. An example is provided below.
Yes, Laravel has features that support invokable classes. In fact, Laravel's support for invokable classes is one of its powerful features that can help simplify and organize your code.
In Laravel, you can define a controller as an invokable class, allowing you to use it as a single-action controller with just the __invoke()
method. This means you don't need to define multiple methods in your controller for each action; instead, the __invoke()
method handles all the logic.
Here's an example of an invokable controller in Laravel:
// app/Http/Controllers/MyController.php
namespace App\Http\Controllers;
class MyController
{
public function __invoke($id)
{
// Your controller logic using $id
// ...
}
}
You can then define a route to use this invokable controller in your web.php
or api.php
routes file:
// routes/web.php or routes/api.php
use App\Http\Controllers\MyController;
Route::get('/my-route/{id}', MyController::class);
When a request is made to /my-route/{id}
, Laravel will automatically resolve and invoke the __invoke()
method of the MyController
class, passing the {id}
as an argument.
Using invokable controllers in Laravel helps keep your code more concise and organized, especially for simple single-action routes. It's a powerful feature that promotes code reusability and makes your routes and controllers more manageable.
Conclusion
Invokable PHP classes offer a powerful and flexible way to structure code, encapsulate behavior, and create clean and maintainable applications. From dependency injection to middleware, custom filters to event listeners, and more, these classes enable developers to leverage the benefits of object-oriented programming while retaining the convenience of callable entities.
Incorporating invokable PHP classes into your development arsenal empowers you to build well-organized, modular, and easily maintainable codebases. By abstracting away implementation details and focusing on encapsulation, you can craft robust applications that can adapt and evolve over time.
Closing
As you embark on your PHP development journey, remember to explore the potential of invokable PHP classes and harness their power to create elegant and efficient solutions. Whether you're building web applications, APIs, or command-line tools, the use of invokable classes will undoubtedly streamline your development process and help you write code that is easy to read, understand, and extend.
Thank you for reading the article up to the end. If it was worth reading, please share this article on social media within your circle.
Happy coding!
]]>Here are 10 essential steps to follow when starting a blog:
With these steps, you'll be on your way to launching a successful blog in no time!
Define your Niche
Defining your niche is a crucial step in starting a successful blog. It involves identifying a specific topic, theme, or area of interest that sets your blog apart from others. By narrowing down your focus, you can position yourself as an expert in that particular field and attract a targeted audience. A well-defined niche helps you create content that resonates with your readers and establishes your credibility. It allows you to explore your passions and delve deeper into subjects that truly inspire you.
For example: In this blog, I write about Laravel and web development, drawing from my personal experience and finding it beneficial in my day-to-day work.
Select a Blogging Platform
Selecting the right blogging platform is crucial for your blog's success. Explore various platforms like WordPress, Blogger, and Squarespace to find one that aligns with your needs and technical expertise. Consider factors such as ease of use, customization options, and scalability to make an informed decision. The right platform will provide a user-friendly interface and a wide range of themes and plugins to enhance your blog's functionality. By choosing the ideal blogging platform, you set the stage for a seamless and efficient blogging experience.
If you're looking to build your own custom blog with a friendly developer, assess your requirements and feel free to reach out to me for assistance.
Design an attractive blog Layout
Crafting an appealing blog layout is key to a successful online presence. Aim for a clean and user-friendly design that facilitates effortless navigation. You can opt for pre-designed themes or enlist a web designer for a unique and customized layout. Remember, an eye-catching design sets the stage for an engaging reader experience.
Simplicity and user-friendly navigation are key factors that attract people to websites. Prioritizing a clean and intuitive user interface can enhance the overall user experience.
Get a perfect Domain name, and Hosting
When it comes to getting a perfect domain name and hosting for your blog, there are several reputable options available. Digital Ocean, Vultr, Kinsta, A2 Hosting, Bluehost, and HostGator are all well-regarded providers in the industry for reliable and efficient services.
Sign up for Digital Ocean using my referral link to get credits for your blog while also supporting me. Start your blogging journey with reliable cloud hosting services from Digital Ocean. Join today!
Digital Ocean and Vultr offer reliable and scalable cloud hosting solutions, ideal for bloggers seeking flexibility and control over their server environment. Kinsta specializes in managed WordPress hosting, providing a hassle-free experience with optimized performance and top-notch security.
A2 Hosting is known for its high-speed hosting and excellent customer support, catering to bloggers who prioritize speed and reliability. Bluehost is a popular choice for beginners, offering affordable shared hosting plans and user-friendly interfaces.
HostGator provides reliable hosting with a range of options suitable for blogs of all sizes.
Consider factors such as pricing, performance, support, and specific requirements of your blog when selecting the most suitable domain name and hosting provider. It's crucial to choose a provider that aligns with your needs and offers the features necessary to support the growth of your blog.
Read: Laravel Envoy to Deploy your Application to Hosting Server
Also Read: How to Deploy Laravel Application to VPS Server
Craft Compelling Content
To ensure a successful blog, focus on creating high-quality content that captivates and engages your audience. Your articles should be informative, entertaining, and highly relevant to your chosen niche. Consistency is key, so aim to post regularly and consistently to maintain your audience's interest and keep them coming back for more. By delivering valuable content on a consistent basis, you'll establish yourself as a reliable source and foster a dedicated following. Remember, the quality and frequency of your content play a crucial role in driving the success of your blog.
Implement SEO Techniques
Boost your blog's visibility and reach by implementing SEO techniques. Optimize your content with relevant keywords, build quality backlinks, and improve your website's loading speed. By effectively utilizing SEO strategies, you can enhance your blog's search engine rankings and attract organic traffic, expanding your audience and maximizing your online presence.
Engage with your Audience
Engage with your audience to build a strong and loyal community. Respond to comments, participate in discussions, and utilize social media for interaction. By fostering connections and actively engaging with your readers, you create a sense of community and build trust as a relatable blogger.
Build a Social Media Presence
Establish a solid presence on social media platforms such as Facebook, Instagram, Twitter, and LinkedIn to expand your blog's reach. Share your posts, interact with followers, and leverage analytics to optimize your strategies. By actively engaging on social media, you can grow your audience, foster connections, and establish your influence in your niche.
Explore Monetization Options
Google AdSense is a widely used advertising platform that enables you to monetize your blog by displaying relevant ads and earning revenue based on clicks or impressions. Another popular monetization method is affiliate marketing, where you promote products or services on your blog and earn a commission for each sale or referral made through your unique affiliate links. By leveraging these monetization strategies, you can generate income from your blog while providing valuable content to your audience.
You can find various ways to monetize your online presence.
Analyze and Track your Blog's Performance
Analyze your blog's performance with analytics tools like Google Analytics. Gain insights into your traffic, audience, and content engagement to make informed decisions for growth and success.
Stay up-to-date with Industry Trends
Stay in the know with industry trends to keep your blog relevant and engaging. By staying updated on the latest developments, you can provide valuable content that resonates with your audience and maintain a competitive edge in your niche.
In conclusion, by defining your niche, choosing the right platform, creating quality content, promoting effectively, monetizing strategically, tracking metrics, staying updated, and remaining committed, you can launch a successful blog in 2024. Engage your audience and achieve your blogging goals with confidence.
Embrace the journey, stay dedicated, and watch your blog soar to new heights. Best of luck on your path to blogging success!
Thank you for reading! If you found this article helpful, please consider sharing it with others. Let's inspire more bloggers to achieve success!
Happy Blogging!
dd()
, dump()
, dumpRawSql()
, ddRawSql()
, and toSql()
, to effectively debug complex queries and resolve database-related issues efficiently.
Debugging Query Results with dd()
:
$query = DB::table('users')
->join('orders', 'users.id', '=', 'orders.user_id')
->leftJoin('products', 'orders.product_id', '=', 'products.id')
->where('users.status', '=', 'active')
->where('orders.quantity', '>', 5)
->orderBy('orders.created_at', 'desc')
->select('users.name', 'orders.total', 'products.name as product_name');
$result = $query->get();
dd($result);
By applying dd()
to the query result, you can instantly inspect the query results and gain insights into the retrieved data and query structure.
Inspecting Raw SQL Statement with dumpRawSql()
:
$query = DB::table('orders')
->whereIn('status', ['pending', 'processing'])
->orWhere(function ($query) {
$query->where('total', '>', 1000)
->where('discount', '>', 0);
})
->orderBy('created_at', 'desc')
->limit(10);
$query->dumpRawSql();
By using dumpRawSql()
, you can examine the raw SQL statement generated by the query, including complex conditions and subqueries.
Inspecting SQL Statement and Bindings with dump()
:
$name = 'John Doe';
$query = DB::table('users')
->where('name', $name)
->where('age', '>', 25);
$query->dump();
Using dump()
, you can inspect the query builder instance, including the SQL statement and parameter bindings.
Inspecting Raw SQL Statement with ddRawSql()
:
$query = DB::table('posts')
->whereIn('category', ['Technology', 'Science'])
->where(function ($query) {
$query->where('status', '=', 'published')
->orWhere('featured', '=', true);
})
->orderBy('created_at', 'desc')
->limit(5);
$query->ddRawSql();
By using ddRawSql()
, you can examine the raw SQL statement generated by the query and analyze it more effectively.
Extracting SQL Statement with toSql()
:
$query = DB::table('posts')
->whereIn('category', ['Technology', 'Science'])
->where(function ($query) {
$query->where('status', '=', 'published')
->orWhere('featured', '=', true);
})
->orderBy('created_at', 'desc')
->limit(5);
$sql = $query->toSql();
dump($sql);
Using toSql()
, you can extract the SQL statement from the query and further analyze it without interrupting the code execution.
Conclusion: Advanced query debugging techniques empower Laravel 10 developers to effectively resolve complex database-related issues. By utilizing methods such as dd()
, dump()
, dumpRawSql()
, ddRawSql()
, and toSql()
, you gain efficient tools to inspect query results, raw SQL statements, parameter bindings, and extracted SQL statements. These methods provide valuable insights during development. It's important to note that while ddRawSql()
is helpful for debugging, it should not be used in production environments due to security risks. Incorporating these techniques into your Laravel 10 development workflow will enhance your ability to debug queries, ensuring smooth and optimized database interactions.
Thank you for reading the article up to the end, please share with your circle if you found it worth reading and helpful.
Happy Debugging!
Prerequisites: Before getting started with the article below, it is recommended to have a solid understanding of Laravel, PHP and web development concepts. Familiarity with PHP programming, web basics (HTML/CSS/JavaScript), MVC architecture, databases, and basic command-line knowledge will be beneficial for effectively working with the Laravel framework.
Also Read:
Laravel 5.4: Login with Username or Email
Laravel v5.5: Login, Register with Username or Email
Laravel 5.6 Login, Register, Activation with Username or Email Support
Laravel 5.7: Adding Support for Login with Username or Email
Building a Password Less Authentication System with Laravel Signed Routes
Let's get started!
Step 1: Database Preparation To enable login with both username and email, we need to make modifications to our database. Open your migration file and add a new column, username
, to the users
table. Ensure the username
column is unique and indexed.
Schema::table('users', function (Blueprint $table) {
$table->string('username')->unique()->after('email');
});
Step 2: User Model Configuration Next, update the User
model to include the username
field in the $fillable
array and implement a method to dynamically determine the login field.
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $fillable = [
'name', 'email', 'password', 'username',
];
public function getLoginField($loginValue)
{
return filter_var($loginValue, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
}
// ...
}
Step 3: Login Form Update your login form to include a single input field for both the username and email. Modify the login controller to handle the dynamic login field.
<form method="POST" action="{{ route('login') }}">
@csrf
<div>
<label for="login">Username or Email</label>
<input id="login" type="text" name="login" value="{{ old('login') }}" required autofocus>
</div>
<!-- Rest of the form fields -->
<div>
<button type="submit">Login</button>
</div>
</form>
Step 4: Login Controller Modify your login controller to utilize the dynamic login field based on user input. By overriding the username()
method, we can dynamically switch between the username and email fields during the authentication process.
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
use AuthenticatesUsers;
// ...
public function username()
{
$loginValue = $this->request->input('login');
$loginField = $this->guard()->user()->getLoginField($loginValue);
return $loginField;
}
// ...
}
Inside the AuthenticatesUsers
trait, there is a method called credentials()
that is responsible for retrieving the login credentials from the request. In order to support login with both username and email, we need to modify this method to dynamically determine the login field based on the provided input.
Here's an updated version of the credentials()
method:
/**
* Get the login credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
$loginField = $this->getLoginField($request->input($this->username()));
return [$loginField => $request->input($this->username()), 'password' => $request->input('password')];
}
You can easily override the credentials()
method in the User model to customize the login process further. By doing so, you can handle any specific logic or validations related to the login credentials.
To override the credentials()
method in the User model, follow these steps:
app/Models/User.php
).credentials()
method:/**
* Get the login credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function credentials(Request $request)
{
$loginField = $this->getLoginField($request->input($this->username()));
return [
$loginField => $request->input($this->username()),
'password' => $request->input('password'),
'active' => true, // Customize additional conditions if needed
];
}
Customize the method as per your requirements. You can add extra conditions to the credentials array, such as checking for an active user or applying any other business logic.
By overriding the credentials()
method in the User model, you have the flexibility to tailor the login process according to your application's needs.
Now, Laravel will use the overridden credentials()
method from the User model instead of the default implementation.
That's it! With these advanced code snippets and modifications, your login system will now support login with both username and email options. Users can choose their preferred login method, enhancing the overall user experience.
Conclusion: In this article, we explored how to implement login with both username and email options using Laravel 10. By allowing users to choose their preferred login method, we create a more personalized and user-friendly experience. Remember to follow the step-by-step guide and update the necessary code snippets in your application.
I hope this advanced tutorial has been helpful in expanding your understanding of user authentication with Laravel 10. If you have any questions or comments, please feel free to leave them below. Stay tuned for more exciting Laravel tutorials and guides in the future!
References:
Laravel Documentation
]]>
Let's get started!
Step 1: Set up a Laravel project First, create a new Laravel project using the Laravel Installer or Composer. Open a command prompt and run the following command:
composer create-project --prefer-dist laravel/laravel your-project-name
Step 2: Install required composer packages Next, install the required composer packages by running the following commands in the project's root directory:
composer require letencrypt/letsencrypt
composer require pkijs/pkijs
composer require jszip/jszip
Step 3: Create a route and controller Create a route in the routes/web.php
file to handle the SSL certificate generation. Open the file and add the following route definitions:
Route::get('/free-ssl-certificate', [SSLController::class, 'showCertificateForm']);
Route::post('/generate-ssl-certificate', [SSLController::class, 'generateCertificate'])->name('generate.certificate');
Route::get('/download-certificate/{file}', [SSLController::class, 'downloadCertificate'])->name('download.certificate');
Next, generate a controller by running the following command in the command prompt:
php artisan make:controller SSLController
Open the generated app/Http/Controllers/SSLController.php
file and replace its contents with the provided code.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use PKIjs\CertificationRequest;
use LetEncrypt\Client;
use JSZip\JSZip;
use Illuminate\Support\Facades\Config;
class SSLController extends Controller
{
public function showCertificateForm()
{
return view('ssl.certificate');
}
public function generateCertificate(Request $request)
{
$domain = $request->input('domain');
// Perform SSL certificate generation logic using Let's Encrypt, PKIJS, and JSZIP
// Example code to generate SSL certificate and create a zip file
// 1. Generate CSR (Certificate Signing Request) using PKIJS
$privateKey = Config::get('app.private_key');
$csr = $this->generateCSR($domain, $privateKey);
// 2. Use Let's Encrypt ACME client to generate the SSL certificate
$letsEncryptClient = new Client();
$certificate = $letsEncryptClient->generateCertificate($csr);
// 3. Create a zip file with the certificate files using JSZIP
$zip = new JSZip();
$zip->addFile('certificate.crt', $certificate->getCertificate());
$zip->addFile('private-key.key', $privateKey);
$zipContent = $zip->generate();
// 4. Store the zip file in the public directory
$zipFileName = 'ssl_certificate.zip';
file_put_contents(public_path($zipFileName), $zipContent);
// 5. Redirect to the download page
return redirect()->route('download.certificate', ['file' => $zipFileName]);
}
private function generateCSR($domain, $privateKey)
{
$pkijsCSR = new CertificationRequest();
// Set subject common name (domain)
$pkijsCSR->subject->typesAndValues[0] = new RelativeDistinguishedNames();
$pkijsCSR->subject->typesAndValues[0]->type = '2.5.4.3'; // Common Name OID
$pkijsCSR->subject->typesAndValues[0]->value->valueBlock->value = $domain;
// Sign the CSR with the private key
$pkijsCSR->sign(null, $privateKey, 'SHA-256');
// Return the CSR in PEM format
return $pkijsCSR->toSchema(true) . "\r\n";
}
public function downloadCertificate($file)
{
$filePath = public_path($file);
if (file_exists($filePath)) {
return response()->download($filePath)->deleteFileAfterSend();
}
abort(404);
}
}
Step 4: Create views Create a new directory called ssl
inside the resources/views
directory. Inside the ssl
directory, create a new file called certificate.blade.php
with the following content:
@extends('layouts.app')
@section('title', 'Free SSL Certificate')
@section('content')
<div class="container">
<h1>Free SSL Certificate Page</h1>
<p>Here you can generate your free SSL certificate.</p>
<form method="POST" action="{{ route('generate.certificate') }}">
@csrf
<div class="form-group">
<label for="domainInput">Enter your domain:</label>
<input type="text" class="form-control" id="domainInput" name="domain" placeholder="example.com" required>
</div>
<button type="submit" class="btn btn-primary">Generate SSL Certificate</button>
</form>
@isset($zipFileName)
<div class="mt-4">
<h4>Download your SSL certificate:</h4>
<a href="{{ route('download.certificate', ['file' => $zipFileName]) }}" class="btn btn-success">Download</a>
</div>
@endisset
</div>
@endsection
Step 5: Update the .env
file and configuration Open the .env
file in the root directory of your Laravel project and add the following line:
PRIVATE_KEY=your_private_key_here
Replace your_private_key_here
with the actual private key you want to use.
Open the config/app.php
file and add the following line to the providers
array:
'providers' => [
// Other providers...
JSZip\ServiceProvider::class,
],
Step 6: Update the welcome.blade.php
file (optional) If you want to add a link to the SSL certificate generation page in the default Laravel welcome page, you can update the resources/views/welcome.blade.php
file by adding the following code:
<div class="links">
<a href="{{ url('/free-ssl-certificate') }}">Generate Free SSL Certificate</a>
</div>
That's it! You now have a Laravel website with a free SSL certificate generation feature. Feel free to customize the views, routes, and controllers according to your requirements.
Remember to run php artisan serve
or use your preferred web server to access your Laravel application in the browser.
Conclusion
Congratulations! You have successfully implemented a simple Laravel website that allows users to generate free SSL certificates. Users can access the SSL certificate generation page, enter their domain name, and generate the certificate. The generated certificate is then bundled into a zip file that can be downloaded from the website.
By following this tutorial, you have learned how to integrate Let's Encrypt, PKIJS, and JSZIP into your Laravel project for SSL certificate generation. You can further enhance the website by adding additional features such as certificate expiration reminders or certificate management.
Remember to customize the views, routes, and controllers as per your project requirements. Happy coding!
]]>1. Date Formatting Made Easy
Formatting dates is a breeze with Carbon. You can easily format dates using the format()
method, providing you with complete control over the output. For example:
use Carbon\Carbon;
$date = Carbon::now();
$formattedDate = $date->format('Y-m-d H:i:s');
echo $formattedDate; // Output: 2023-06-29 14:30:00
2. Handling Timezones with Ease
Carbon simplifies working with different timezones. You can effortlessly switch between timezones using the timezone()
method. For example:
use Carbon\Carbon;
$date = Carbon::now();
$date->timezone('Asia/Kathmandu');
echo $date; // Output: 2023-06-29 14:30:00 (Asia/Kathmandu timezone)
3. Localization for Multilingual Applications
Localizing date and time outputs is crucial for multilingual applications. Carbon allows you to easily set the desired locale using the setLocale()
method. For example:
use Carbon\Carbon;
$date = Carbon::now();
Carbon::setLocale('en');
echo $date->isoFormat('LLLL'); // Output: Tuesday, June 29, 2023 2:30 PM
4. Working with Time Intervals
Carbon simplifies adding or subtracting time intervals from a date. You can easily manipulate dates by adding or subtracting minutes, hours, days, months, or years using the add
and sub
methods. Here's an example:
use Carbon\Carbon;
$date = Carbon::now();
$date->addMinutes(30);
$date->subHours(2);
$date->addMonth();
$date->addYears(2);
5. Calculating Date Differences
Carbon provides powerful methods to calculate the difference between two dates. You can determine the difference in minutes, hours, days, or even create custom intervals. Here's an example:
use Carbon\Carbon;
$date1 = Carbon::create(2023, 1, 1);
$date2 = Carbon::create(2023, 1, 2);
$diffInDays = $date1->diffInDays($date2);
$diffInHours = $date1->diffInHours($date2);
$customInterval = $date1->diff($date2)->format('%m months and %d days');
6. Advanced Date Manipulation with Carbon Period
Carbon's Period class allows you to work with time periods more intuitively. You can create a period using CarbonPeriod::create()
and iterate over the range of dates. Here's an example:
use Carbon\Carbon;
use Carbon\CarbonPeriod;
$start = Carbon::create(2023, 6, 1);
$end = Carbon::create(2023, 6, 30);
$period = CarbonPeriod::create($start, $end);
foreach ($period as $date) {
echo $date->format('Y-m-d') . "<br>";
}
7. Localization of Relative Time
Carbon's diffForHumans()
method generates human-readable time differences. By setting the locale, Carbon automatically localizes the output. For example:
use Carbon\Carbon;
$date = Carbon::now()->subHours(2);
Carbon::setLocale('en');
echo $date->diffForHumans(); // Output: 2 hours ago
8. Working with Unix Timestamps
Carbon seamlessly integrates with Unix timestamps, allowing you to convert them to Carbon instances and vice versa. For example:
use Carbon\Carbon;
$timestamp = 1625000000; // Example Unix timestamp
$carbonDate = Carbon::createFromTimestamp($timestamp);
echo $carbonDate->format('Y-m-d H:i:s'); // Output: 2021-06-30 12:53:20
$unixTimestamp = $carbonDate->timestamp;
echo $unixTimestamp; // Output: 1625000000
9. Leap Year Check
Carbon provides an isLeapYear()
method to determine if a given year is a leap year. For example:
use Carbon\Carbon;
$year = 2024;
if (Carbon::isLeapYear($year)) {
echo "The year {$year} is a leap year.";
} else {
echo "The year {$year} is not a leap year.";
}
10. Comparison with Null-Safe Methods
Carbon offers null-safe methods for comparing dates. You can use methods like equalTo()
, notEqualTo()
, greaterThan()
, and lessThan()
to compare dates and handle null values gracefully. Let's look at an example:
use Carbon\Carbon;
$date1 = Carbon::parse('2023-06-30');
$date2 = null;
// Using the equalTo() method to compare dates (including null check)
if (Carbon::equalTo($date1, $date2)) {
echo "The dates are equal.";
} else {
echo "The dates are not equal.";
}
In the above example, we have two dates: $date1
and $date2
, where $date2
is set to null
. By using the equalTo()
method, Carbon compares the dates while handling the null value gracefully. If both dates are equal, it will output "The dates are equal." Otherwise, it will output "The dates are not equal."
This null-safe comparison approach ensures that your code doesn't encounter errors or unexpected behavior when dealing with null values in date comparisons.
You can use similar null-safe methods like notEqualTo()
, greaterThan()
, greaterThanOrEqualTo()
, lessThan()
, and lessThanOrEqualTo()
to perform various comparisons while considering null values.
use Carbon\Carbon;
$date1 = Carbon::parse('2023-06-30');
$date2 = null;
// Using the greaterThan() method to compare dates (including null check)
if (Carbon::greaterThan($date1, $date2)) {
echo "Date 1 is greater than Date 2.";
} else {
echo "Date 1 is not greater than Date 2.";
}
In this example, we use the greaterThan()
method to compare $date1
and $date2
while handling the null value. If $date1
is greater than $date2
, it will output "Date 1 is greater than Date 2." Otherwise, it will output "Date 1 is not greater than Date 2."
By utilizing these null-safe comparison methods, you can ensure that your date comparisons are reliable and handle null values without causing unexpected errors or inconsistencies in your code.
11. Serialization and Unserialization
You can easily serialize and unserialize Carbon instances using PHP's serialize()
and unserialize()
functions, allowing you to store and retrieve Carbon objects effortlessly. Let's take a look at a short example that demonstrates how to serialize and unserialize Carbon objects:
use Carbon\Carbon;
$date = Carbon::now();
// Serialize the Carbon object
$serialized = serialize($date);
// Store the serialized data in a file or database
// Retrieve the serialized data
$serializedData = ''; // Assume we fetched the serialized data from storage
// Unserialize the data back into a Carbon object
$carbonDate = unserialize($serializedData);
echo $carbonDate->format('Y-m-d H:i:s');
In the example above, we start by creating a Carbon instance with the current date and time using Carbon::now()
. We then serialize the $date
object using the serialize()
function, which converts the object into a string representation that can be stored or transferred.
Once the serialized data is stored (e.g., in a file or database), we can retrieve it when needed. In this example, we assume that we fetched the serialized data into the variable $serializedData
. We then use the unserialize()
function to convert the serialized data back into a Carbon object.
Finally, we can use the unserialized $carbonDate
object to perform various operations. In this case, we format it using the format()
method to display the date and time in the desired format.
Serialization and unserialization are particularly useful when you need to persist Carbon objects in storage or transfer them between different parts of your application. By leveraging serialization, you can seamlessly store and retrieve complex date and time data, ensuring consistency and accuracy in your Laravel application.
12. Modifying Date and Time Precision
Carbon allows you to include microseconds in the date/time representation using the micro()
method. This provides finer-grained control over the precision of the time information.
use Carbon\Carbon;
$dateTime = Carbon::now();
$dateTimeWithMicroseconds = $dateTime->micro(123456);
echo $dateTimeWithMicroseconds->format('Y-m-d H:i:s.u');
In the example above, we create a Carbon instance representing the current date and time. By calling the micro()
method with a value on the $dateTime
object, we modify the precision of the time by including microseconds. The format()
method with the u
specifier is used to display the modified date and time with microseconds.
This feature is useful when high precision is required, such as in scientific calculations or when working with systems that rely on microsecond-level accuracy.
By leveraging Carbon's ability to modify the precision of date and time values, you can tailor the output to match the required level of precision in your Laravel application.
13. Customization of Relative Time Thresholds
Carbon allows you to customize the thresholds used for generating relative time strings. This means you can configure phrases like "moments ago," "a few seconds ago," etc., according to your application's requirements.
use Carbon\Carbon;
Carbon::setLocale('en');
Carbon::setToStringFormat('d/m/Y H:i:s');
Carbon::setHumanReadable(Carbon::THRESHOLD_WEEK, '%d days');
Carbon::setHumanReadable(Carbon::THRESHOLD_MONTH, '%d months');
Carbon::setHumanReadable(Carbon::THRESHOLD_YEAR, '%d years');
$date = Carbon::now()->subDays(5);
echo $date->diffForHumans(); // Output: 5 days ago
In the example above, we first set the locale to 'en' using setLocale()
to ensure the relative time strings are displayed in English.
Next, we set the format for string representation using setToStringFormat()
. In this case, we specify the format as 'd/m/Y H:i:s', which will be used when calling $date->tostring()
.
Then, we customize the thresholds for different time intervals using setHumanReadable()
. We set the threshold for a week to display the number of days ('%d days'
), for a month to display the number of months ('%d months'
), and for a year to display the number of years ('%d years'
).
Finally, we create a Carbon instance representing a date five days ago. When we call diffForHumans()
on $date
, it will generate the relative time string using the customized thresholds, resulting in the output "5 days ago."
By customizing the relative time thresholds, you can adapt the language and format of the relative time strings to suit your application's needs, providing a more personalized and user-friendly experience.
14. Fluent Interface for Date Operations
Carbon provides a fluent interface that allows you to chain multiple date operations together. This results in cleaner and more readable code. For example:
use Carbon\Carbon;
$date = Carbon::now()->addDays(2)->subHours(1)->startOfDay();
$date = Carbon::now()->addDays(5)->subHours(5)->endOfDay();
$date = Carbon::now()->addYears(1)->startOfYear();
$date = Carbon::now()->addYears(1)->endOfYear();
Conclusion
Carbon is an indispensable library for handling date and time-related operations in Laravel. Its extensive feature set, including date formatting, timezone handling, localization, time calculations, and hidden features, empowers developers to work with dates efficiently. By leveraging Carbon's capabilities, you can streamline your code, improve user experience, and handle complex date scenarios effortlessly. Start integrating Carbon into your Laravel applications and unlock its true potential.
Remember to consult the official Carbon documentation for more details and explore its vast array of features.
Thank you for reading until the end! If you found this article valuable, we would appreciate it if you could share it with others.
Happy Coding!
Single Action Controllers provide a streamlined approach to organizing your application's code by dedicating a controller class to a single action. This architectural pattern promotes separation of concerns, improves code readability, and simplifies the maintenance of your Laravel application. In this article, we will delve into an advanced example of Single Action Controllers in Laravel to demonstrate their practical usage.
Before we proceed, make sure you have a working Laravel installation along with its dependencies. You can refer to the official Laravel documentation for installation instructions.
Traditionally, a Laravel controller consists of multiple methods, each representing a specific action that handles an incoming request. However, in certain scenarios, a controller might only require a single action. For instance, consider a simple API endpoint that fetches user details. In such cases, creating a dedicated controller with just one method becomes more intuitive and cleaner.
Let's begin by setting up a new Laravel project. Open your terminal and navigate to the desired directory. Run the following command to create a new
laravel new single-action-example
Once the project is created, navigate to its directory:
cd single-action-example
To begin, generate a new Single Action Controller class using the make:controller
Artisan command. Open your terminal and run the following command:
php artisan make:controller ContactFormController --invokable
This will create the ContactFormController
class in the app/Http/Controllers
directory.
Next, open the ContactFormController.php
file and replace the code with the following:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class ContactFormController extends Controller
{
public function __invoke(Request $request)
{
// Validate the form data
$validatedData = $request->validate([
'name' => 'required',
'email' => 'required|email',
'message' => 'required',
]);
// Send an email with the form data
Mail::to('[email protected]')->send(new \App\Mail\ContactFormMail($validatedData));
return response()->json(['message' => 'Contact form submitted successfully']);
}
}
In this example, the __invoke
method handles the form submission. It first validates the form data using Laravel's validation rules. If the data is valid, it sends an email with the form data using the Mail
facade. Finally, it returns a JSON response to indicate the successful submission.
Now, let's define a route to access our Single Action Controller. Open the routes/web.php
file and add the following route definition:
use App\Http\Controllers\ContactFormController;
Route::post('/contact', ContactFormController::class);
In this example, we're using the post
method to define a route that matches the /contact
URI pattern. When this route is accessed, it will invoke the ContactFormController
class.
To test the contact form submission, you can create a simple HTML form in your view. Here's an example of how you can create a form in a Blade template:
<form action="/contact" method="POST">
@csrf
<label for="name">Name:</label>
<input type="text" name="name" id="name">
<label for="email">Email:</label>
<input type="email" name="email" id="email">
<label for="message">Message:</label>
<textarea name="message" id="message"></textarea>
<button type="submit">Submit</button>
</form>
Make sure to include the CSRF token by adding @csrf
within the form. This is necessary to protect your application from cross-site request forgery attacks.
When the form is submitted, the data will be sent to the /contact
route, which will invoke the ContactFormController
and handle the form submission.
Remember to handle the email sending logic appropriately in your application. In this example, we used the Mail
facade to send the email, assuming you have configured Laravel's mail settings correctly.
Single Action Controllers in Laravel provide a clean and focused approach to handling specific actions within your application. By dedicating a controller class to a single action, you can improve code organization, readability, and maintainability. Whether it's fetching user details or processing a contact form submission, Single Action Controllers offer a flexible and efficient way to structure your Laravel application.
Thank you for reading until the end! If you found this article valuable, we would appreciate it if you could share it with others.
This article is about the Stripe payment gateway to handle one-time payments with express checkout.
Introduction
Stripe is a payment service provider dealing with online payment processing for businesses around the globe.
Stripe offers different APIs for payment processing, which can be for one-time or subscription-based and recurring payments, and many more.
In this article, we're writing about the steps involved in integrating the express checkout.
Prerequisites
This article is for developers having an experience of mid-level or above with Laravel and PHP, as we won't cover the basics.
So we assume you already have experience with the below points.
Setup
We assume you already have a Laravel project installed in your machine, and you are ready to start the stripe integration.
Install official stripe-php-sdk
from packagist with composer using the below command.
composer require stripe/stripe-php
Before diving into the code, go to the stripe dashboard and grab the API credentials needed for the integration.
The red border boxes shown in the above picture have the place to reveal the Publishable key and Secret key from the stripe dashboard under the Developers tab.
As of crafting this article, Laravel is at 9.*
version.
You can then store the API credentials, depending on how you manage the API credentials via database or put them on the .env file.
Here we are going to use the .env file for this article.
STRIPE_PUBLISHABLE_KEY=YOUR-KEY-HERE
STRIPE_SECRET_KEY=YOUR-SECRET-KEY-HERE
Coding
Let's set up routes in Laravel where the user can access the order and continue through the payment flow.
<?php
use App\Http\Controllers\Payments\StripeController;
use Illuminate\Support\Facades\Route;
Route::get('/checkout/overview', [StripeController::class, 'overview'])
->name('checkout.overview');
Route::post('/checkout/payment/{order}/stripe', [StripeController::class, 'payment'])
->name('checkout.payment');
Route::get('/checkout/payment/{order}/approved', [StripeController::class, 'approved'])
->name('checkout.approved');
Route::get('/checkout/payment/{order}/cancelled', [StripeController::class, 'cancelled'])
->name('checkout.cancelled');
Now, we need a controller for handling the incoming request and responses from the route.
<?php
namespace App\Http\Controllers\Payments;
use App\Models\Order;
use App\Payments\Stripe;
use Illuminate\Routing\Controller;
class StripeController extends Controller
{
public function overview()
{
// implement your own order overview here
$order = Order::query()
->whereHas('items')
->with(['items'])
->paymentPending()
->first();
return view('stripe', ['order' => $order]);
}
/**
* @param $uuid
*/
public function payment($uuid)
{
// implement your own order here
$order = Order::paymentPending()
->where('uuid', $uuid)
->firstOrFail();
return Stripe::initialize($order);
}
/**
* @param $uuid
*/
public function approved($uuid)
{
$order = Order::query()
->where('uuid', $uuid)
->firstOrFail();
return Stripe::captured($order);
}
/**
* @param $uuid
*/
public function cancelled($uuid)
{
// render your cancelled page here
return redirect()->route('checkout.overview')
->with('error', 'You have cancelled the payment. Therefore, the order has not been placed yet.');
}
}
To interact with the database, we need a model. Here we have only created a single Order.php
model and used the order items as static values to simplify the integration.
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->increments('id');
$table->uuid();
$table->string('transaction_id')->nullable();
$table->float('amount')->unsigned()->nullable();
$table->integer('payment_status')->unsigned()->default(0);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('orders');
}
};
You have to deal with the dynamic order items based on your application architecture.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use HasFactory;
const PAYMENT_COMPLETED = 1;
const PAYMENT_PENDING = 0;
/**
* @var string
*/
protected $table = 'orders';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['uuid', 'invoice_number', 'transaction_id', 'total_paid', 'payment_status'];
/**
* @return \App\Models\OrderItem
*/
public function items()
{
return $this->hasMany(OrderItem::class);
}
/**
* @param $query
* @return \Illuminate\Database\Query\Builder
*/
public function scopePaymentPending($query)
{
return $query->where("{$this->table}.payment_status", self::PAYMENT_PENDING);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OrderItem extends Model
{
use HasFactory;
/**
* @var string
*/
protected $table = 'order_items';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['uuid', 'order_id', 'name', 'sku', 'quantity', 'description', 'amount'];
/**
* @return \App\Models\Order
*/
public function order()
{
return $this->belongsTo(Order::class);
}
}
We've got a simple page to display a continue to the payment form.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<title>Payment</title>
</head>
<body>
<section class="container mx-auto mt-4 mb-4">
<form action="{{ route('checkout.payment', [$order->uuid]) }}" method="POST">
@csrf
<button type="submit" class="bg-slate-900 text-white text-sm font-semibold h-10 px-6 rounded-md sm:w-auto">Continue to Payment</button>
</form>
</section>
</body>
</html>
For an e-commerce website, this page needs to be an order overview with order items and payment gateways choices for users.
Finally, we have a dedicated class created to handle the initialization of stripe checkout.
<?php
namespace App\Payments;
use Exception;
use Stripe\Checkout\Session;
use Stripe\Stripe as Checkout;
class Stripe
{
/**
* @param $order
*/
public static function initialize($order)
{
$static = new static;
$checkout = Checkout::setApiKey(env('STRIPE_SECRET_KEY'));
try {
$session = Session::create([
'client_reference_id' => $order->uuid,
'billing_address_collection' => 'required',
'line_items' => $static->toLineItems($order),
'mode' => 'payment',
'success_url' => $static->toReturnUrl($order),
'cancel_url' => $static->toCancelUrl($order),
]);
} catch (Exception $e) {
return redirect()->route('checkout.overview')
->with('error', 'Unknown error occurred, please try again later.');
}
$order->update(['transaction_id' => $session->id]);
if (filter_var($session->url, FILTER_VALIDATE_URL)) {
return redirect()->to($session->url, 303);
}
return redirect()->route('checkout.overview')
->with('error', 'Unknown error occurred, please try again later.');
}
/**
* @param $order
*/
public static function captured($order)
{
$checkout = Checkout::setApiKey(env('STRIPE_SECRET_KEY'));
try {
$session = Session::retrieve($order->transaction_id);
} catch (Exception $e) {
return redirect()->route('checkout.overview')
->with('error', 'The payment was not successful, please retry again.');
}
if ($approved = $session && $session->payment_status == 'paid') {
$order->update(['payment_status' => $order::PAYMENT_COMPLETED]);
// take user to order placed page with message
return redirect()->route('checkout.overview')
->with('success', 'Thank you for placing an order, the payment was captured successfully.');
}
return redirect()->route('checkout.overview')
->with('error', 'The payment was not successful, please retry again.');
}
/**
* @param $order
* @return array
*/
public function toLineItems($order)
{
$stack = [];
$static = new static;
foreach ($order->items as $item) {
$stack[] = [
'price_data' => [
'currency' => 'USD',
'product_data' => [
'name' => $item->name,
],
'unit_amount' => $static->toCentAmount($item->amount),
],
'quantity' => $item->quantity,
];
}
return $stack;
}
/**
* @param $order
*/
public function toCancelUrl($order)
{
return route('checkout.cancelled', $order->uuid);
}
/**
* @param $order
*/
public function toReturnUrl($order)
{
return route('checkout.approved', $order->uuid);
}
/**
* @param $amount
*/
public function toCentAmount($amount)
{
return (int) ($amount * 100);
return number_format($amount, 2, '.', '');
}
}
The request parameters are validated, and if everything is accurate, the request will be auto redirected to the payment gateway service to handle the actual payment.
After the payment, the user gets redirected back to the merchant website. The response received needs to be validated again to make sure the transaction was fully captured or not.
Webhook
We highly recommend handling the payment events via webhook. The webhook is a legit way to verify the payment status of an order.
The webhook is a far more secure way to verify the payment status of an order because it happens behind the scene on your server, as no user interaction is involved.
Conclusion
Thanks for following the article up to the end. I hope it was helpful to you.
Feel free to share this article on social media if you wish to share it in your circle.
]]>For websites accepting an online payment from their customer by selling some product, having an SSL certificate is mandatory to protect their e-commerce transaction data.
Getting an SSL certificate in the past used to cost money.
However, some providers now offer it free to make the internet a safe place.
If it is for your blog or making a small business website, almost likely everyone wants to keep the server and website costs lower.
You get it free of cost. Why not use it and secure the data on your website to make the internet world safer for everyone?
How to get a free SSL certificate?
The answer is simple. There are multiple ways you can get a free SSL certificate in 2022.
We will take you through the step-by-step guide here to show you how you can get it and install it on your server.
Prerequisites
I usually write down the article for Laravel developers in this blog. Laravel framework requires a server that can be easily accessible through an SSH connection.
I use Digital Ocean for almost all of my Laravel applications. So I heavily recommend Laravel developers to use it as it is pretty good for the Laravel framework to host with them.
So, if you already have a site and want an SSL certificate to be installed and configured, then you are at the right place here.
Step 1 - Generating an Origin CA TLS Certificate from Cloudflare
Cloudflare offers a free TLS certificate signed by them to install on your Nginx server.
The primary use of this TLS certificate is to establish a secure connection between your server and Cloudflare's servers.
Log in to your Cloudflare account in any secure web browser. Go to select your particular domain. Then, navigate to the SSL/TLS section on the dashboard.
Right there, navigate to the Origin Server tab, and click on the Create Certificate button.
Keep the selected option Generate private key and CSR with Cloudflare by default.
Now, click Create button, and on the next page, you will see the Origin Certificate and Private key.
You need to copy the generated content on those two particular keys and save it on your server.
Due to security reasons, the Private key will not show again, copy both of the keys to your server and make sure you keep the backup before you decide to click Ok.
We will take the /etc/ssl directory to keep the origin certificate and the private key files on the server.
The folder already exists on the server. Go to copy the key on the Origin Certificate and save it to /etc/ssl/cert.pem
sudo nano /etc/ssl/cert.pem
Similarly, save the private key.
sudo nano /etc/ssl/key.pem
Note: Be sure to avoid blank lines when copying the keys for the relevant certificates.
Now that you copied the key and certificate files to your server, you need to update the Nginx configuration to use them.
Step 2 - Pointing the Origin CA Certificate to Nginx
You need to update the Nginx configuration for your site to use the origin certificate and private key to secure the connection between Cloudflare’s servers and your server.
Next, you need to make sure the UFW allows HTTPS traffic.
You need to Enable Nginx Full, which will open both port 80 (HTTP) and port 443 (HTTPS)
sudo ufw allow 'Nginx Full'
Now reload UFW
sudo ufw reload
Now, you need to check the new rules are allowed and that UFW is active.
sudo ufw status
The output looks like below.
Output
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
Now you are ready to adjust your Nginx server block. Nginx creates a default server block during installation. Remove it if it still exists, as you’ve already configured a custom server block for your domain.
sudo nano /etc/nginx/sites-available/site_domain
The basic configuration on the file should look like this.
server {
listen 80;
listen [::]:80;
root /var/www/site_domain/html;
index index.html index.htm index.nginx-debian.html;
server_name site_domain www.site_domain;
location / {
try_files $uri $uri/ =404;
}
}
Here, the job is to modify the Nginx configuration file to follow the following instructions below.
The file modification looks like below.
server {
listen 80;
listen [::]:80;
server_name site_domain www.site_domain;
return 302 https://$server_name$request_uri;
}
server {
# SSL configuration
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
server_name site_domain www.site_domain;
root /var/www/site_domain/html;
index index.html index.htm index.nginx-debian.html;
location / {
try_files $uri $uri/ =404;
}
}
Finally, save the file and exit from the edit mode.
Now, test the Nginx configuration syntax to avoid errors.
sudo nginx -t
After all, when there are no syntax errors. You can reload the Nginx configuration to enable the changes on the server.
sudo systemctl restart nginx
Enable up Full (strict) mode on the SSL/TLs section on the dashboard.
Go to the Overview tab under SSL/TLS section on the dashboard.
By setting this up, Cloudflare always encrypts the connection between your server and their servers.
Finally, visit your website to validate the setup is working as intended.
The browser should report that the site is secure.
Step 3 - Set up Authenticated Origin Pulls
We need to set up Authenticated Origin Pulls to validate the origin server taking the Cloudflare rather than others.
We are validating the certificate from Cloudflare here.
Download the Cloudflare certificate signed by a CA from Cloudflare’s documentation.
Create this certificate in the path /etc/ssl/cloudflare.crt and save, avoid the blank lines on the file.
Again, update your Nginx configuration to use TLS Authenticated Origin Pulls to validate the requests coming to the server.
Open the configuration file for your domain.
sudo nano /etc/nginx/sites-available/site_domain
Add the ssl_client_certificate and ssl_verify_client directives as shown below and save the file.
. . .
server {
# SSL configuration
...
ssl_client_certificate /etc/ssl/cloudflare.crt;
ssl_verify_client on;
. . .
Verify the Nginx configuration and reload the server to enable the changes.
sudo nginx -t
sudo systemctl restart nginx
Finally, go to SSL/TLS section in the Cloudflare dashboard, open the Origin Server tab and toggle the Authenticated Origin Pulls option.
Visit your domain to validate your changes and to test the changes toggle between the Authenticated Origin Pulls option in the Cloudflare dashboard.
Conclusion
Thanks for reading up to the end. We hope this article helped to secure another website to the internet world.
Happy Coding!
]]>How to manage multiple PHP versions in your local development environment?
If you are having trouble managing multiple PHP versions in your local development environment, then you're at the right place to get the best solution for your issue.
If you are over Linux like me, it is possible to install multiple PHP versions to run one version at a time with your web server.
You may need to switch between them as per your requirement with your project that supports the particular PHP version.
Assume that you have installed multiple PHP versions (let's take a few, PHP 5.6, 7.0, 7.2, 7.4, 8.0, and 8.1).
Also, keep in mind that you can only set a single version as a default one to run with your web server (Nginx or Apache).
The script shows you some PHP versions that are already at the end of their life. However, it is just a demo, and you can always update it to match the versions you are currently using.
It is a challenge to become a modern web developer, as one should stay updated with new technologies and have to play with them every day.
Besides that, if a developer handles packages, frameworks, and libraries and their client's projects, they need to have a supported setup to apply new updates and fixes for their works.
Let's dive into the actual workflow on how you can manage multiple PHP versions in your machine.
Open up your favorite code editor and copy and paste the snippet below.
phpswap () {
if [[ -z "$1" ]]; then
echo "You did not specify a version (5.6, 7.0, 8.0, 8.1)"
elif [[ "$1" == "5.6" ]]; then
echo "Switching to PHP 5.6"
sudo ln -sf /usr/bin/php5.6 /etc/alternatives/php
sudo a2dismod php7.0
sudo a2dismod php8.0
sudo a2dismod php8.1
sudo a2enmod php5.6
sudo systemctl restart apache2
elif [[ "$1" == "7.0" ]]; then
echo "Switching to PHP 7.0"
sudo ln -sf /usr/bin/php7.0 /etc/alternatives/php
sudo a2dismod php5.6
sudo a2dismod php8.0
sudo a2dismod php8.1
sudo a2enmod php7.0
sudo systemctl restart apache2
elif [[ "$1" == "8.0" ]]; then
echo "Switching to PHP 8.0"
sudo ln -sf /usr/bin/php8.0 /etc/alternatives/php
sudo a2dismod php5.6
sudo a2dismod php7.0
sudo a2dismod php8.1
sudo a2enmod php8.0
sudo systemctl restart apache2
elif [[ "$1" == "8.1" ]]; then
echo "Switching to PHP 8.1"
sudo ln -sf /usr/bin/php8.1 /etc/alternatives/php
sudo a2dismod php5.6
sudo a2dismod php7.0
sudo a2dismod php8.0
sudo a2enmod php8.1
sudo systemctl restart apache2
else
echo "You did not specify a version (5.6, 7.0, 8.0, 8.1)"
fi
}
The script has support for multiple versions of PHP and has the way to run it with a single command and easy to remember bash function that you can run on your terminal whenever you need it.
Apply the relevant changes to the actual version you want to use in your environment.
After all, you need to apply it to the run time of your system somewhere.
Open up your terminal and hit the below command.
nano ~/.bashrc
Once the edit mode appears on your screen, paste the script somewhere in the file and save it.
Log off your system and login in again, or reboot if necessary, then you are ready to go with it.
Usage
Open up your terminal and check your current php version with php -v
After showing the current version, you can hit phpswap, and it alerts you with options you have set up previously with the bash function.
In my case, it shows like this below.
➜ phpswap
You did not specify a version (7.0, 8.0, 8.1)
Then, hit phpswap 8.1, and it does everything else for you, and after it finishes running, it's ready with another version activated.
Easy right?
Conclusion
Thanks for reading this post up to the end.
If the article was helpful to you, share it with your circle to help spread the good content.
If you have any feedback feel free to submit the contact us form, and I will get your message right into my inbox.
Happy Coding!
Last updated: Dec, 17, 2024
Along with this significant release, few first-party packages, including Laravel Jetstream, Fortify are also releasing to join the Laravel ecosystem.
The author of Laravel initially announced Laravel Jetstream at Laracon Online 2020 with a complete walk-through featuring the new goodies for Laravel 8.
The packages releasing will also be the next free and open-source packages by the Laravel team.
What is Jetstream?
Laravel Jetstream is a brand new application scaffolding package, which comes with some convenient features, and gives a perfect start point for your next Laravel 8 project.
In short, Jetstream includes features like login, registration, email verification, 2FA, session management, API support, and teams support.
The past versions of Laravel stood with the Bootstrap framework for a long time. As of now, with the extensive popularity of Tailwind CSS, Jetstream is also designed using Tailwind CSS, which is a utility-first CSS framework.
Jetstream offers two different flavors to choose between Livewire, Inertia, which allows developers to use their preferred ones.
Read: How to Deploy Laravel Application to VPS Server
Usage
$ php artisan jetstream:install livewire
// or
$ php artisan jetstream:install inertia
For teams feature, add --teams option on the artisan command.
$ php artisan jetstream:install livewire --teams
Once you install your Jetstream stack, remember to compile your frontend assets using the command below.
npm install && npm run dev
Through, Laravel Installer
The team has recently released the v4 version of the Laravel installer to support Jetstream's installation via the installer. Make sure you upgrade your composer package if you have it already installed.
$ laravel new your-next-project-name --jet
Features Overview
You will be able to see this beautifully designed dashboard view once you set it up correctly.
User Profile
Jetstream comes with a profile edit feature for the user, and the forms fields are necessary standard fields, including profile photo for the user registration, so you are always free to customize those files for your use case.
Security
Jetstream brings features like change password, enable 2FA, revoke authenticated browser sessions under the account. These are the regular features that most do not need customization. However, if you would like to modify, you could change the relevant code in action classes under the app/Actions/Fortify
directory.
Email Verification
Jetstream out of the box comes with an email verification feature. If you decide to enable this feature, once a user registers for your project, they will be taken to a screen to click the verification link sent to their email used during the registration.
To enable this feature, scroll to features array within the fortify.php
config file, as you have full control to edit the file.
Account Deletion
Jetstream introduces an action panel that allows the user to delete their account entirely. You have full control over the code to customize for the backend logic under App\Actions\Jetstream
classes.
API Support
Jetstream comes with first-party integration with Laravel Sanctum for API support. If you wonder how the Laravel Sanctum works, follow the official docs for full information.
Inside Jetstream, with Sanctum's use, each user can generate multiple API tokens for their account.
These tokens are used in API call to authorize the access to the application via the API call.
You must enable this feature as well under the jetstream.php
config file.
'features' => [
Feature::api(),
],
Teams
Jetstream packs a great feature as teams, as many people are going to use it in their next project. It gives a great way to manage a team, create a team, switch between the team, even manage roles, permission to the member, etc.
The team feature also needs to be enabled via the jetstream.php
config file.
'features' => [
Feature::teams(),
],
To read in-depth about the teams feature, and use case and customization, follow the official documentation.
While using any of the two choices from Livewire or Inertia, you can still utilize many packed features with your entire application.
Those features include components, models, forms, validation helpers. If your application context matches to use them, you are free to take them and never write your own again. To publish those files, use the below command.
$ php artisan vendor:publish --tag=jetstream-views
Closing
In the Laracon Online talk, Taylor mentioned, Laravel team has almost offered the non-billing part of Laravel Spark as Laravel Jetstream, which is entirely free for everyone now.
Conclusion
Thanks for staying up to the end of the article. If you think it is worth reading, share this article in your circle and follow me on Twitter for more updates.
]]>Throughout this article, you are going to learn how you can deploy your laravel apps, with the precise use of Laravel Envoy, including how you can efficiently deploy your updates once you make changes in your version control platforms like Github, Bitbucket.
Read: Laravel Envoy to Deploy your Application to Hosting Server
Also, this article will cover a few more other ideas about the deployment process for Laravel application on different platforms.
Let's get started step by step about the deployment on how you can start deploying your Laravel projects now using the latest Laravel version.
Prerequisites
Before you start, you need to have a deployment-ready server with everything up and running to launch your next Laravel app, as we are not going to deal with any server set up stuff here.
However, you will need to make sure your server meets the requirements based on the Laravel version you are using.
I regularly follow the Digital Ocean technical articles to study about server set up things, as they have an outstanding community with great content.
Set up SSH
Now, the initial step is to generate an SSH key on your server and add it back to VCS platforms like Github, Bitbucket, or whatever you use.
I primarily use Linux Ubuntu flavor to host my apps, so this article takes reference based on the Linux Ubuntu but also would work for most of the Linux flavors.
Access your server via console using SSH connection and follow the command below.
$ ssh-keygen -t rsa -b 4096 -C "[email protected]"
Copy that SSH key based on where you save it.
On Linux, you can cat the contents like below.
$ cat<~/.ssh/id_rsa.pub
After adding it to the version control platform, you can test your SSH connection like below.
$ ssh -T [email protected]
If you are a beginner and need more assistance, I suggest you read this article by Bitbucket, which has in-depth information on it.
Set up Composer
For those who want to set up composer, follow the steps below.
$ cd ~
$ mkdir bin
$ cd bin
$ curl -sS https://getcomposer.org/installer | php
Set up Envoy
// Envoy.blade.php
@setup
$branch = isset($branch) ? $branch : "master";
$serverUser = 'deployer';
$rootDirectory = '~/home/' . $serverUser;
$server = $serverUser . '@server_ip';
@endsetup
@servers(['production' => $server])
@task('clone', ['on' => 'production'])
echo '>> cd {{ $rootDirectory }}'
cd {{ $rootDirectory }}
echo '>> mkdir {{ $rootDirectory }}/project'
mkdir {{ $rootDirectory }}/project
echo '>> chmod 755 {{ $rootDirectory }}/project'
chmod 755 {{ $rootDirectory }}/project
echo '>> cd {{ $rootDirectory }}/project'
cd {{ $rootDirectory }}/project
echo '<< git clone [email protected]:username/project.git deploy'
git clone [email protected]:username/project.git deploy
@endtask
@task('environment', ['on' => 'production'])
echo '>> cd {{ $rootDirectory }}/project/deploy'
cd {{ $rootDirectory }}/project/deploy
echo '<< cp .env.example .env'
cp .env.example .env
echo '>> SSH to your server, paste your valid .env credentials & save them. Then run envoy run post-deploy'
@endtask
@task('composer-install', ['on' => 'production'])
echo '>> cd {{ $rootDirectory }}/project/deploy'
cd {{ $rootDirectory }}/project/deploy
echo '<< /home/{{ $serverUser }}/bin/composer.phar install --prefer-dist --no-scripts --no-dev -q -o'
/home/{{ $serverUser }}/bin/composer.phar install --prefer-dist --no-scripts --no-dev -q -o
@endtask
@task('composer-update', ['on' => 'production'])
echo '>> cd {{ $rootDirectory }}/project/deploy'
cd {{ $rootDirectory }}/project/deploy
echo '<< /home/{{ $serverUser }}/bin/composer.phar dump -o && php artisan optimize'
/home/{{ $serverUser }}/bin/composer.phar dump -o && php artisan optimize
@endtask
@task('migrate', ['on' => 'production'])
echo '>> cd {{ $rootDirectory }}/project/deploy'
cd {{ $rootDirectory }}/project/deploy
php artisan migrate --force;
@endtask
@task('symlink', ['on' => 'production'])
echo '<< ln -s /home/{{ $serverUser }}/project/deploy/public /var/www/html'
ln -s /home/{{ $serverUser }}/project/deploy/public /var/www/html
@endtask
@task('deploy-changes', ['on' => 'production'])
echo '>> cd {{ $rootDirectory }}/project/deploy'
cd {{ $rootDirectory }}/project/deploy
echo '>> git checkout {{ $branch }}'
git checkout {{ $branch }}
echo '<< git pull --rebase'
git pull --rebase
@endtask
@story('deploy', ['on' => 'production'])
setup
environment
@endstory
@story('post-deploy', ['on' => 'production'])
composer-install
composer-update
migrate
symlink
@endstory
@story('update')
deploy-changes
composer-update
migrate
@endstory
Deploy
You will only need to run this task when you launch your application for the initial phase on the remote server.
$ envoy run deploy
The initial task finishes with a message telling about preparing the .env file.
Set up .env
Prepare the .env configuration with the necessary credentials for your application to run on the server.
SSH to the server, copy & paste to the server where .env file lies, and save it.
Final Deploy
Once you save the server .env file with correct credentials, then run another command.
$ envoy run post-deploy
After the first successful deploy, you cannot run the same process to deploy your future updates on the repository. Now, your future job will be only pulling new changes or installing more new packages if needed.
$ envoy run update
Finishing
It is a reasonable question to come in your mind, this deployment script is likely to break the application, and it could take your application to go through downtime either on any of the deployment.
I agree with the above statement, as it is not a zero-downtime deployment solution, as the article was focused generally on the way of only deploying your code to the server.
As being a developer, having some server related knowledge is almost necessary for every day to work with fun on any application, no matter the size and infrastructure required to scale it.
People these days talk about zero-downtime deployment and have seen a lot of articles about it, and there are commercial platforms available to handle the concern.
If you want to deploy your code to your server without worrying about downtime, choose the service like Laravel Envoyer, which is a commercial product from the Laravel ecosystem.
Even more, you do not want to worry about managing servers and want to go hassle-free with auto-scaling your apps, use Forge, and Vapor a serverless platform.
Using those managed services, you need to pay for both your server cost as well as the service cost every month, using those platforms helps your team to focus more on the work rather than suffering from deployments and servers.
Also, as a free solution for zero-downtime deployment strategy, there is an open-source package built by Papertank.
Tip: People ask a lot of questions about how to deploy Laravel to shared hosting frequently. But, my answer to this is, Laravel works great on VPS service providers like Digital Ocean, Vultr Linode, AWS, or any Custom VPS, etc.
Conclusion
Thanks for being up to the end of the article. Please share this article to your circle, if you enjoyed reading it.
Follow me on Twitter for more updates or visit the blog more often to view new articles at a later date.
]]>By making the use of Blade style syntax, you can quickly set up tasks for deployment actions, run inbuilt Artisan commands, and many more. Envoy currently has support for Mac, Linux only.
Similar to any other composer packages you install with your Laravel application, Laravel Envoy also follows the same process.
If you don’t already have it installed, then follow the below simple command.
$ composer global require laravel/envoy
After a successful installation, now your turn is to start to set up your tasks needed to deploy your application. Test your set up like below, shows the screen like below with more other information.
$ envoy -v
Laravel Envoy 2.3.1
You can initialize the Envoy.blade.php file by using the command below.
$ envoy init [email protected]
Adding Tasks
Before starting to write any tasks, define a new file Envoy.blade.php in the root of your project directory.
@servers(['web' => '[email protected]'])
@task('deploy')
cd /path/to/site
git pull origin master
@endtask
As you can see above, the @servers
directive in a single line, referencing the array of servers to point around when you deploy the application.
The @task
directive keeps the bash script to run on your server in the deployment process. For the initialization of variables necessary for the deployment script, you can also use the @setup
directive to assign variables based on your application structure.
If your project logic requires to verify certain things, and that requires to include any PHP files in between the deployment process, you could always use the @include
directive at the top of your Envoy.blade.php file.
@include('vendor/autoload.php')
@setup
$branch = isset($branch) ? $branch : "main";
$env = isset($env) ? $env : "production";
$account = isset($account) ? $account : "sujipthapa";
@endsetup
@servers(['local' => '127.0.0.1', 'production' => '192.168.1.1'])
Define Variables
As per your requirement, Laravel Envoy also supports passing variables with values to your tasks within the command line.
$ envoy run deploy --branch=main --account=sujipthapa
Adding Stories
Adding @stories
directive helps you to group up your small tasks into a flow of actions, which gives you a way to maintain the sequence to execute scripts on your server in your deployment process.
For instance, you may add a story label as a post-deploy, which groups your tasks like git pull, composer install, migration, etc.
Notify
You may instantly send a notification to different channels like Telegram, Discord, Slack right inside from the Envoy setup file.
It just requires to define your simple setup for that process like below.
@finished
@slack('slack-webhoo-url', '#deploy')
@endfinished
Overview
As from the above in-depth run down, below can you can try to understand the Envoy set up with few useful directives and usual tasks defined for your to as quick overview.
@setup
$branch = isset($branch) ? $branch : "main";
$env = isset($env) ? $env : "production";
$account = isset($account) ? $account : "sujipthapa";
@endsetup
@servers(['local' => '127.0.0.1', 'production' => '192.168.1.1'])
@task('cd_server_path', ['on' => 'production'])
echo ">> cd /home/{{ $account }}/project"
cd /home/{{ $account }}/project
@endtask
@task('git_commands')
echo ">> git checkout {{ $branch }} ..."
git checkout {{ $branch }}
echo ">> git pull origin {{ $branch }} --force ..."
git pull origin {{ $branch }} --force
@endtask
@task('run_composer')
echo ">> composer depencencies..."
composer install --no-interaction --quiet --no-dev --prefer-dist --optimize-autoloader
@endtask
@task('run_migrate')
php artisan migrate --env={{ $env }} --force --no-interaction
@endtask
@story('deploy')
cd_server_path
git_commands
run_composer
run_migrate
@endstory
Finally, your turn is to run the deploy command.
$ envoy run deploy
I suggest you follow the official Laravel Envoy docs as more options and new features roll out every single day, as it is an open-source project, so many people contribute and use the ecosystem.
Conclusion
Thanks for reaching up to the end of the article. Please share this article to your circle, if you loved reading it.
Follow me on Twitter for more updates or visit the blog more often to view new articles at a later date.
]]>Hacktoberfest is a great event, which encourages participation in the open-source community to contribute, as the open-source community is growing bigger every year.
If you have never known about Hactoberfest before or never contributed before, be the one to complete the 2020 challenge and earn a limited-edition T-shirt this year.
I started my Hactoberfest participation since 2016 and trying to celebrate this event every year on the open-source contribution. I've contributed to a lot of projects in the past, and try to give more whenever possible.
Read : Hacktoberfest 2016, Hacktoberfest 2018
From the beginning of my developer carrier in 2013, there has been tremendous growth in the open-source community, with several popular open-source projects landing every day. This growing community is becoming a great motivation to newbies to learn new things and talent developers to become great creators of their platform to work full or part-time with the passion-filled in them.
Since the launch of the github sponsors platform, developers who are maintaining open-source projects can now get direct funding support from the community, businesses relying on the project would show interest in supporting them. So, developers receiving such support from the platform encourages them to work with full passion and generate more new ideas everyday.
Hacktoberfest is open to everyone in the global community. This event helps in growing yourself and the community, as you get a chance to learn new things, and open-source projects get better with your contribution.
So, better try and contribute weather, you are a beginner in development or a long-time contributor, your support helps to grow the community.
Read the guidelines that match your position (maintainer, contributor, company) in the official Hacktoberfest website.
I would recommend you to read the open-source contribution guidelines before you start contributing to any project.
Thanks to Digital Ocean for organizing this great event every year!
Happy Contributing!
]]>As like a few other payment gateways integration with PHP, I've recently worked on a new package for eSewa, which is a used in the majority of eCommerce platform in Nepal. I got a chance to work for the client to accept their payment online.
I have first explored on google to know if anyone has worked to create a better PHP package for the gateway. I found there is no any good package for the payment gateway. So decided to create a new reusable library with unit testing. I always try to share the real-time example and solution for the working developers through this blog.
Below, I'll cover complete steps needed to integrate eSewa API using the package built on top of Omnipay library.
Before diving into the code, let me first share my experience on how to grab API keys for development, production environment.
While I went through their API docs, there are no test credentials given in there. eSewa API credentials are available to the merchant after contacting the eSewa office via phone call or email support.
Now, let's jump into the code after you have grabbed the API credentials to make API calls by using the package.
Coding
The library is framework agnostic, can be used in any custom project, not only with Laravel. Here, we're going to integrate it on top of the Laravel framework. If you're a Laravel developer, hopefully, you will benefit from it to save your time.
Also, read:
Omnipay PayPal Integration
PayPal Instant Payment Notification
Include the package with your project with a composer command.
composer require league/omnipay sudiptpa/omnipay-esewa
Routes
Route::get('/checkout/payment/esewa', [
'name' => 'eSewa Checkout Payment',
'as' => 'checkout.payment.esewa',
'uses' => 'EsewaController@checkout',
]);
Route::post('/checkout/payment/{order}/esewa/process', [
'name' => 'eSewa Checkout Payment',
'as' => 'checkout.payment.esewa.process',
'uses' => 'EsewaController@payment',
]);
Route::get('/checkout/payment/{order}/esewa/completed', [
'name' => 'eSewa Payment Completed',
'as' => 'checkout.payment.esewa.completed',
'uses' => 'EsewaController@completed',
]);
Route::get('/checkout/payment/{order}/failed', [
'name' => 'eSewa Payment Failed',
'as' => 'checkout.payment.esewa.failed',
'uses' => 'EsewaController@failed',
]);
Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Order
* @package App
*/
class Order extends Model
{
const PAYMENT_COMPLETED = 1;
const PAYMENT_PENDING = 0;
/**
* @var string
*/
protected $table = 'orders';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}
Esewa
<?php
namespace App;
use Exception;
use Omnipay\Omnipay;
/**
* Class Esewa
* @package App
*/
class Esewa
{
/**
* @return \SecureGateway
*/
public function gateway()
{
$gateway = Omnipay::create('Esewa_Secure');
$gateway->setMerchantCode(config('services.esewa.merchant'));
$gateway->setTestMode(config('services.esewa.sandbox'));
return $gateway;
}
/**
* @param array $parameters
* @return $response
*/
public function purchase(array $parameters)
{
try {
$response = $this->gateway()
->purchase($parameters)
->send();
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
return $response;
}
/**
* @param array $parameters
* @return $response
*/
public function verifyPayment(array $parameters)
{
$response = $this->gateway()
->verifyPayment($parameters)
->send();
return $response;
}
/**
* @param $amount
*/
public function formatAmount($amount)
{
return number_format($amount, 2, '.', '');
}
/**
* @param $order
*/
public function getFailedUrl($order)
{
return route('checkout.payment.esewa.failed', $order->id);
}
/**
* @param $order
*/
public function getReturnUrl($order)
{
return route('checkout.payment.esewa.completed', $order->id);
}
}
Controller
<?php
namespace App\Http\Controllers;
use App\Esewa;
use App\Order;
use Exception;
use Illuminate\Http\Request;
/**
* Class EsewaController
* @package App\Http\Controllers
*/
class EsewaController extends Controller
{
/**
* @param Request $request
*/
public function checkout(Request $request)
{
$order = Order::findOrFail(mt_rand(1, 20));
return view('esewa.checkout', compact('order'));
}
/**
* @param $order_id
* @param Request $request
*/
public function payment($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
$gateway = with(new Esewa);
try {
$response = $gateway->purchase([
'amount' => $gateway->formatAmount($order->amount),
'totalAmount' => $gateway->formatAmount($order->amount),
'productCode' => 'ABAC2098',
'failedUrl' => $gateway->getFailedUrl($order),
'returnUrl' => $gateway->getReturnUrl($order),
], $request);
} catch (Exception $e) {
$order->update(['payment_status' => Order::PAYMENT_PENDING]);
return redirect()
->route('checkout.payment.esewa.failed', [$order->id])
->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
}
if ($response->isRedirect()) {
$response->redirect();
}
return redirect()->back()->with([
'message' => "We're unable to process your payment at the moment, please try again !",
]);
}
/**
* @param $order_id
* @param Request $request
*/
public function completed($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
$gateway = with(new Esewa);
$response = $gateway->verifyPayment([
'amount' => $gateway->formatAmount($order->amount),
'referenceNumber' => $request->get('refId'),
'productCode' => $request->get('oid'),
], $request);
if ($response->isSuccessful()) {
$order->update([
'transaction_id' => $request->get('refId'),
'payment_status' => Order::PAYMENT_COMPLETED,
]);
return redirect()->route('checkout.payment.esewa')->with([
'message' => 'Thank you for your shopping, Your recent payment was successful.',
]);
}
return redirect()->route('checkout.payment.esewa')->with([
'message' => 'Thank you for your shopping, However, the payment has been declined.',
]);
}
/**
* @param $order_id
* @param Request $request
*/
public function failed($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
return view('esewa.checkout', compact('order'));
}
}
HTML
@extends('default')
@section('content')
<div class="title m-b-md">
eSewa Checkout
</div>
<div class="links">
@if($message = session('message'))
<p>
{{ $message }}
</p>
@endif
<p>
<strong>QuietComfort® 25 Acoustic Noise Cancelling® headphones — Apple devices</strong>
</p>
<br>
<form method="POST" action="{{ route('checkout.payment.esewa.process', $order->id) }}">
@csrf
<button class="btn btn-primary" type="submit">
${{ $order->amount }} Pay with eSewa
</button>
</form>
</div>
@stop
Code
The sample code is available on github example repository.
Conclusion
Thanks for following up this article up to the end. If you have any feedback for me, feel free to leave your comment. If you think you have found a bug, please reach us via email or create an issue on github. Do not forget to share if you think worthreading and sharing this article.
Happy Coding!
]]>The process below helps you to understand the following things.
The reason behind writing this article is peoples, preferring my previous articles about Laravel login and register with username or email support for earlier versions of Laravel.
Compatibility
If you are starting your fresh Laravel 5.7 project, and want to add the support for username or email for login and register, you could follow the steps from the beginning.
For any existing project just upgraded to Laravel 5.7, it is, of course, useful to follow the article to customize your authentication system.
Customization
Let's begin with the registration part to add support for username or email login with Laravel 5.7.
Register
For a fresh Laravel 5.7 setup, begin with generating the default auth scaffolding with an artisan console command, which ships with the framework out of the box.
Routes
// no email verification
Auth::routes();
// after adding, email verification
Auth::routes(['verify' => true]);
I would like to recommend you to follow the earlier article [Email Verification after Registration with Laravel 5.7] side by side with this article to implement email verification with Laravel 5.7 authentication system.
Database
For existing project add a new migration file using an artisan command to add a new field to the users' table, and for the new fresh project, you can easily edit the migration file generated with auth scaffolding and add a single line below.
$table->string('username')->unique();
To fill value in the database for username
field, we've to add a key username to the $fillable
property under app/User.php
database model.
protected $fillable = ['name', 'email', 'password', 'username'];
View
The user registration form also requires a little bit of customization to add the username field.
<div class="form-group row">
<label for="username" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>
<div class="col-md-6">
<input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required>
@if ($errors->has('username'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('username') }}</strong>
</span>
@endif
</div>
</div>
Controller
The default auth system ships with a controller class, which handles the necessary methods for the registration system. We've to edit a few methods under that class to add the username to the database.
Login
We've already added the necessary routes, email verification steps linked with another article. For the login system to support username or email we need to override a few methods on the login controller.
The login form requires a very little change on the input type field from email to text.
<input id="email" type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
The controller class for login also ships with the framework and uses an AuthenticatesUsers
trait, which handles the specific methods necessary for the default auth system.
To apply the new changes we need, we have to override the methods under the controller class for the login system.
// LoginController.php
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
$field = $this->field($request);
return [
$field => $request->get($this->username()),
'password' => $request->get('password'),
];
}
/**
* Determine if the request field is email or username.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
public function field(Request $request)
{
$email = $this->username();
return filter_var($request->get($email), FILTER_VALIDATE_EMAIL) ? $email : 'username';
}
/**
* Validate the user login request.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function validateLogin(Request $request)
{
$field = $this->field($request);
$messages = ["{$this->username()}.exists" => 'The account you are trying to login is not registered or it has been disabled.'];
$this->validate($request, [
$this->username() => "required|exists:users,{$field}",
'password' => 'required',
], $messages);
}
Conclusion
Thanks for following up my regular articles, and reading up to the end. If you have any feedback for me, feel free to leave your comment. Do not forget to share if you think worth sharing, and reading my articles.
Happy Coding!
]]>I'm writing my second blog post about the Hacktoberfest challenge, but the competition is the third edition for me in real, by regularly winning the T-shirts from this great event.
The Hacktoberfest 2018 is the 5th edition, which was continuously organized by two giant tech companies @DigitalOcean, @Github in the previous years but this year the newest partner @Twilio also joining to run this challenge for the developers all over the world.
I'm super excited this time as well by looking at the excellent design of the Hacktober 2018 T-shirt.
Who can participate?
It is open to everyone in our global community!
How can I participate?
Open a minimum of 5 pull requests on Github, by choosing your favorite projects. If you need the contribution guide, follow this official contribution guide provided by Github.
When to participate?
You can sign up at the official website of Hacktoberfest 2018 anytime between October 1 and October 31.
The organizers are the world famous technology companies providing following great services to their customers.
Digital Ocean, Inc - An American based cloud infrastructure service provider, serving customers over the world with more than 13 data centers across different countries.
GitHub, Inc - A web-based code hosting service for version control using Git. It is a widely known source code repository platform for different types of applications.
Twilio - A cloud communications platform as a service company based in San Francisco, California. Twilio allows developers to programmatically make and receive phone calls, send and receive SMS.
Since last year, the Hacktoberfest fan created a platform to check if you completed the Hacktoberfest challenge to receive the T-shirt or not by viewing the pull request and activity.
Credit: The above preview image credit goes to Hacktoberfest 2018.
Thanks for reading the article up to the end, if you are interested and need help in participating in this challenge, please feel free to leave your comment below. I will look back and try to help as much as possible.
Happy Coding!
]]>In this article, we're implementing the complete email verification process in depth. We're going to achieve the verification feature by using the standard framework code without doing any modification.
Let's start the coding together.
Auth Scaffolding
Let's start with publishing the default auth scaffoldings by using the artisan command.
After publishing the necessary files, we don't have to worry about the routes, views, controllers required for authentication, as they ship with the framework, but you can always customize them if needed.
php artisan make:auth
Database
The new class is introduced in the framework to implement the email verification system.
So, now the App\User
model should implement the Illuminate\Contracts\Auth\MustVerifyEmail
contract.
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements MustVerifyEmail
{
// ...
}
We need to add one more field email_verified_at
to the user's table to store the verified timestamp when a user clicks the activation link sent to their email address.
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Routing
A new controller Auth\VerificationController
class is introduced to handle the necessary logic to send an email verification link to the user's email address.
Also, register the verification routes by passing the parameter like below.
// before
Auth::routes();
// after
Auth::routes(['verify' => true]);
Middleware
A new middleware Illuminate\Auth\Middleware\EnsureEmailIsVerified
class is introduced to protect the routes from being accessed by the unauthorized users.
You can have a look at the Kernel.php
to see the registered middleware.
protected $routeMiddleware = [
...
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
Also, you may want to protect other routes, like profile, the dashboard to non-activated users by attaching the middleware on routes.
// example
Route::get('/home', 'HomeController@index')
->name('home')
->middleware('verified');
Route::get('/member/profile', function () {
// verified users only
})->middleware('verified');
Views
The necessary views for login, register, and email verification are created inside resources/views/auth
directory when you publish the auth scaffoldings.
After Verification
After the successful verification, the application redirects a user to the /home
page by default.
You are free to customize the redirect location by defining a redirectTo
method or property on the VerificationController.php
.
protected $redirectTo = '/dashboard';
Also,
If you want to control the page redirection when the user interacts with login, register pages, you want them to go back to the previous page.
Read: Redirect to Previous URL After Logging In
Conclusion
Thanks for reading the article up to the end, please let me know through comments if you have any issue on the integration.
Happy Coding!
]]>The release will receive bug fixes until February 2019 and security fixes until August 2019.
This release continues to improvements for the previous version 5.6, and also includes some exciting new features.
In this blog post, I'm listing some cool features that are already announced by the Laravel team and taking some reference from the github repository.
I'll keep on updating this post up to the final release is announced from the official team, as new pull requests are still coming from the community contributors.
1. Laravel Nova
The most awaited laravel package that was announced by Taylor during Laracon US 2018, and which is a beautiful admin panel package for Laravel application. It is a code-driven admin panel for your new or existing laravel project. The previous version of Laravel still supports Nova, as it is a simple composer package. To know in depth about it follow the official documentation, also go through the introduction article on the medium by Taylor.
Laravel Nova is officially released now on Aug 22, 2018, the initial release v1.0.* (Orion) is now available to purchase from the official website.
2. Email Verification
The framework introducing optional email verification feature with this release. To utilize this feature, you have to add the email_verified_at timestamp column to the user's table migration that ships with the framework.
To advise newly joined users to verify their email, the User model should implement the MustVerifyEmail interface.
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements MustVerifyEmail
{
// ...
}
After implementing the interface, the newly registered members will receive an email containing a signed verification link. Once the user clicks this link, the application will automatically update the database and redirect the user to the intended location.
This new feature also introduced a verified middleware. If you wish to protect your application's routes from only verified members, it can be attached to the application routes.
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
3. Guest User Gates / Policies
The previous version of Laravel used to return false if an unauthenticated user wants to access the application. However, with the new version (5.7) it will now allow declaring an "optional" type-hint or supplying a null default value to permit the guest user to pass through the authorization checks.
Gate::define('update-product', function (?User $user, Product $product) {
// ...
});
4. Symfony Dump Server
The Symfony's dump-server is coming to Laravel 5.7. It is a command via package built by a Laravel community member Marcel Pociot.
This feature will be a lot useful to debug an error on your application without interrupting the application runtime.
The command runs in the background and collects data transmitted from the application and shows output through the console mode.
php artisan dump-server
// Output to the HTML file.
php artisan dump-server --format=html > report.html
This package is open sourced by the author, contribute if you have any idea for it.
5. URL Generator & Callable Syntax
To generate the URLs to controller actions, Laravel now supports callable array syntax, instead of strings only.
Previously,
$url = action('BlogController@index');
$url = action('BlogController@view', ['id' => 1]);
Now,
use App\Http\Controllers\HomeController;
$url = action([BlogController::class, 'index']);
$url = action([BlogController::class, 'view'], ['id' => 1]);
6. Paginator Links
You can now control the number of pagination links on each side of the paginated URLs design. By default, there will be three links created on each side of the paginator links. The newly introduced method onEachSide() allows to control them with Laravel 5.7.
{{ $pages->onEachSide(5)->links() }}
7. Read / Write Streams method in Filesystem
Laravel 5.7 introduces new methods for the Filesystem integration.
Storage::disk('s3')->writeStream(
'destination-file.zip',
Storage::disk('local')->readStream('source-file.zip')
);
8. Notification Localization
You can now assign locale for the notifications to send other than the current language.
The Illuminate\Notifications\Notification class adds new locale method to assign the desired language.
$user->notify((new NewUser($user))->locale('np'));
You could also utilize the facade to set localization for multiple notification entries.
Notification::locale('np')
->send($subscribers, new WeeklyNewsletter($newsletter));
9. Improved Error Messages
You can now better track your errors messages with your Laravel application. As of Laravel 5.7, you will get a cleaner concise message saying that the method doesn't exist on the specific model.
10. Resources Directory Changes
A little bit changes to the resources directory coming with the new release.
This change was announced by Taylor with a tweet, as assets directly will go away and js, sass, lang, views coming out into the resources directory.
When you upgrade your application to 5.7, you don’t need to reconstruct the resources/asset directory according to the newer directory structure. The older structure will still work.
11. Testing Artisan Commands
The first employee of Laravel (Mohamed Said) recently contributed a great feature in the framework to test artisan commands. He announced via his tweet about this feature, and which is also documented in official documentation already. Now, with this addition, framework now provides a simple API for testing console applications that ask for user input.
class InstallCommandTest extends TestCase
{
public function testInstallTest()
{
$this->artisan('app:setup', [
'name' => 'Setup New Project'
])
->expectsQuestion('Are you sure you want to start installation ?', 'Yes')
->expectsOutput('Initializing...')
->expectsQuestion('Please select your preferred version', 'v2.5')
->expectsOutput('Installing...')
->expectsQuestion('Do you want to run composer dump -o ?', 'Yes')
->expectsOutput('Generating Optimized Autoload Files...')
->assertExitCode(0);
}
}
Conclusion
Thanks for reading this article up to the end, please follow this article in later dates to know more new features coming to the 5.7 release.
Happy Coding!
]]>A lot of modern webs, mobile apps using social login to give a great user experience while using their platforms.
Today in this blog post, I'm explaining the process of customizing to use own passwordless authentication system with the Laravel framework.
Let's start together.
This article will utilize features from Laravel 5.6 version.
laravel new passwordless-auth
cd passwordless-auth
php artisan make:auth
After publishing the default Laravel auth scaffoldings, we now need to remove unnecessary files listed below.
ResetPasswordController.php
ForgotPasswordController.php
passwords/email.blade.php
passwords/reset.blade.php
The register, login pages come up with password fields, as we're building passwordless auth, so we have to tweak on those files.
login.blade.php
register.blade.php
Note: It is better to give some instructions on each pages describing how our passwordless authentication system works.
Now, adding auth routes needed for this integration.
We're not going to use the default routes which ship with the framework for the auth system.
We'll care the naming convention of routes, but we have to avoid unnecessary ones coming with the framework which are not necessary for this integration.
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login/attempt', 'Auth\LoginController@attempt')->name('login.attempt');
Route::get('login/{token}/validate', 'Auth\LoginController@login')
->name('login.token.validate')
->middleware('signed');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');
Let's start with registering a user without a password to log in.
Before writing any codes for login, register process, let me give you some idea on what things need to update.
Signed Route Middleware
The middleware prevents the user to misuse the expired URLs by checking the signature validity.
protected $routeMiddleware = [
...
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
];
The new feature signed route was introduced to the framework with version 5.6.
Handle InvalidSignatureException
We need to handle the exception for invalid signature, if a user tries to login with the expired signed URL show message to them to generate new URL.
use Illuminate\Routing\Exceptions\InvalidSignatureException;
public function render($request, Exception $exception)
{
if (is_a($exception, InvalidSignatureException::class)) {
return response()->view('_signature-expired');
}
return parent::render($request, $exception);
}
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2>{{ __('Error') }}</h2>
</div>
<div class="card-body">
<div class="alert alert-danger text-center text-muted">
The signature seems to be expired, please try generating a new one and try again.
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
Remove Password Fields
We're not using the password field anymore, so remove from the migration file and User model.
After all, these make sure you run the migration to generate database tables.
Register
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
use RegistersUsers;
/**
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
]);
}
/**
* Handle a registration request for the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
return redirect()->route('login')
->with(['success' => 'Success! your account is registered.']);
}
}
The main updates in this file are, remove password form validation, create methods.
Another major update is to prevent a user from logging in automatically into the application after registration inside the register method.
Tip: I've seen BuySell Ads, a popular advertising platform using this kind of system as only admin adds the user.
Login
I've added a new trait for the Login process to keep the overrides a little bit cleaner.
Previously with the framework's default auth, there is a trait called AuthenticatesUsers.
Now, with this integration, we will be importing new trait which itself imports the existing AuthenticatesUsers.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Auth\Traits;
use App\Http\Controllers\Controller;
class LoginController extends Controller
{
use Traits\PasswordLessAuth;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
Also, the Login Throttling feature will still work.
<?php
namespace App\Http\Controllers\Auth\Traits;
use App\LoginAttempt;
use App\Notifications\NewLoginAttempt;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
trait PasswordLessAuth
{
use AuthenticatesUsers;
/**
* Validate the user login request.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function validateLogin(Request $request)
{
$messages = ['exists' => trans('auth.exists')];
$this->validate($request, [
$this->username() => 'required|email|exists:users',
], $messages);
}
/**
* Handle a login attempt request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function attempt(Request $request)
{
$this->incrementLoginAttempts($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$this->validateLogin($request);
if ($this->createLoginAttempt($request)) {
return $this->sendAttemptResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function login($token, Request $request)
{
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($token, $request)) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
/**
* Attempt to log the user into the application.
*
* @param string $token
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function attemptLogin($token, Request $request)
{
$user = LoginAttempt::userFromToken($token);
if (is_object($user)) {
return $this->guard()->login($user);
}
}
/**
* Attempt to log the user into the application.
*
* @param \Illuminate\Http\Request $request
* @return \App\LoginAttempt
*/
protected function createLoginAttempt(Request $request)
{
$authorize = LoginAttempt::create([
'email' => $request->input($this->username()),
'token' => str_random(40) . time(),
]);
$authorize->notify(new NewLoginAttempt($authorize));
return $authorize;
}
/**
* @param $request
*/
public function sendAttemptResponse($request)
{
return \View::make('auth._link-sent');
}
}
To customize the validation message for the user exists rule, open up resources/lang/en/auth.php and update like below.
<?php
return [
...
'exists' => 'The provided email address does not match our records.'
];
Let me give some run down about the login feature and new methods added to the PasswordLessAuth.php trait.
Let's create a model, migration and notification class for the login attempt process.
<?php
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class LoginAttempt extends Model
{
use Notifiable;
/**
* @var string
*/
protected $table = 'login_attempts';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'email', 'token',
];
/**
* @return mixed
*/
public function user()
{
return $this->hasOne(User::class, 'email', 'email');
}
/**
* @param $token
*/
public static function userFromToken($token)
{
$query = self::where('token', $token)
->where('created_at', '>', Carbon::parse('-15 minutes'))
->first();
return $query->user ?? null;
}
}
Add, those fields by creating a new migration file like below.
Schema::create('login_attempts', function (Blueprint $table) {
$table->increments('id');
$table->string('email')->index();
$table->string('token')->index();
$table->timestamps();
});
To send the login link in an email, we have a new notification class.
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\URL;
class NewLoginAttempt extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($attempt)
{
$this->attempt = $attempt;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return with(new MailMessage)
->from(env('ADMIN_MAIL_ADDRESS'))
->subject('Login Your Account')
->greeting("Hello {$this->attempt->user->name}!")
->line('Please click the button below to get access to the application, which will be valid only for 15 minutes.')
->action('Login to your account', URL::temporarySignedRoute('login.token.validate', now()->addMinutes(15), [$this->attempt->token]))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
After a user makes a valid attempt to login into the application, we display a message to the user with a view file like below.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2>{{ __('Success') }}</h2>
</div>
<div class="card-body">
<div class="alert text-center text-muted">
Please check your email for a login link, which will be valid for next 15 minutes only.
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<a class="btn btn-link" href="{{ route('login') }}">
{{ __('Get Another Link') }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
Conclusion
Thanks for reading this article up to the end, please don't forget to give your feedback in the comment section below.
Also Read: Laravel 5.6 Login, Register, Activation with Username or Email Support
Happy Coding!
]]>Let's suppose you're releasing a new package, but before announcing a new major stable version, you want to test it thoroughly in the development branch.
Now, to use the specific git commit before publishing a new tag, composer offers a way to include the commit hash value directly via the command line or via the require block in the composer.json file.
composer require vendor/package:dev-master#0fcf728
or
"require": {
"vendor/package": "dev-master#0fcf728"
}
The composer command above will check out your package version to that specific commit, so you can test the code properly before finally releasing a stable version.
Also, sometime you may want to install a specific released version of the package.
composer require vendor/package:version
// Example
composer require sudiptpa/paypal-ipn:1.0.x-dev
Also Read: Speed up your Composer Install or Update Process
Thanks for reading the article up to the end, if you have any feedback feel free to leave your comment below.
If it is worth reading, let other people know about it by sharing this post.
Happy Coding!
]]>To follow this article you should have existing or fresh laravel setup with version 5.6, as you will be seeing some codes samples only supporting this particular version and they may not be compatible with other older versions.
The regular laravel installation comes without auth scaffolding with the recent releases, to generate the default auth scaffolding it ships with an artisan command out of the box.
If you haven't already generated them, use the below artisan command to populate them.
php artisan make:auth
Let's start with the additional routes for authentication under routes/web.php
Auth::routes();
Route::get('activate/{token}', 'Auth\RegisterController@activate')
->name('activate');
To add username support on register process, let's add few new fields in the migration file.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('username')->unique();
$table->string('email')->unique();
$table->string('password');
$table->string('token');
$table->integer('active')->default(0);
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
We've added three new fields (username
, token
, active
), to store unique username, activation token, active field to main active flag for users.
Now, updating model app/User.php
with new fields.
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
const ACTIVE = 1;
const INACTIVE = 0;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'username', 'email', 'password', 'token', 'active',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
Register
Adding username field for a registration form inside resources/views/auth/register.blade.php
<div class="form-group row">
<label for="username" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>
<div class="col-md-6">
<input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required>
@if ($errors->has('username'))
<span class="invalid-feedback">
<strong>{{ $errors->first('username') }}</strong>
</span>
@endif
</div>
</div>
To view, the full source code for the registration form read the article up to end.
To make the username support coming through the registration form, we need to modify few methods inside the controller.
// App\Http\Controllers\Auth\RegisterController.php
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'username' => 'required|string|max:20|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'username' => $data['username'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'token' => str_random(40) . time(),
]);
$user->notify(new UserActivate($user));
return $user;
}
/**
* Handle a registration request for the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
return redirect()->route('login')
->with(['success' => 'Congratulations! your account is registered, you will shortly receive an email to activate your account.']);
}
/**
* @param $token
*/
public function activate($token = null)
{
$user = User::where('token', $token)->first();
if (empty($user)) {
return redirect()->to('/')
->with(['error' => 'Your activation code is either expired or invalid.']);
}
$user->update(['token' => null, 'active' => User::ACTIVE]);
return redirect()->route('login')
->with(['success' => 'Congratulations! your account is now activated.']);
}
Let's point out the updates made in the above controller.
validator()
method.create()
method to call a notification class to send an activation email to the user after registration is completed.register()
method originally called from the RegistersUsers trait to protect from being auto-login to non-activated users.activate()
to allow activation to the new users.We've added a new notification class, UserActivate.php
to send an activation link to the users while they register their new account.
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class UserActivate extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @param $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->from(env('ADMIN_MAIL_ADDRESS'))
->subject('Activate Account!')
->greeting(sprintf('Hi, %s', $this->user->name))
->line('We just noticed that you created a new account. You will need to activate your account to sign in into this account.')
->action('Activate', route('activate', [$this->user->token]))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
The customization for registration process is finally done with above changes, now we will be looking at login process below.
Login
We've updated the file login.blade.php
to add support for email or username. To make that addition we've changed the input type email to be text to support both username or email but the input type name remains same.
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail / Username') }}</label>
<div class="col-md-6">
<input id="email" type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
Now, we're adding another method to the controller.
// App\Http\Controllers\Auth\LoginController.php
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
$field = $this->field($request);
return [
$field => $request->get($this->username()),
'password' => $request->get('password'),
'active' => User::ACTIVE,
];
}
/**
* Determine if the request field is email or username.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
public function field(Request $request)
{
$email = $this->username();
return filter_var($request->get($email), FILTER_VALIDATE_EMAIL) ? $email : 'username';
}
/**
* Validate the user login request.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function validateLogin(Request $request)
{
$field = $this->field($request);
$messages = ["{$this->username()}.exists" => 'The account you are trying to login is not activated or it has been disabled.'];
$this->validate($request, [
$this->username() => "required|exists:users,{$field},active," . User::ACTIVE,
'password' => 'required',
], $messages);
}
Let's point out what has changed inside the controller above.
credentials()
, originally called from AuthenticatesUsers
trait which collects credential to attempt the authentication is now overridden to the controller.credentials()
method.After updating the above files the login process also now supports username or email.
Thanks for reading this article up to the end, please don't forget to give your feedback in the comment section below.
Happy Coding!
]]>By using this package (omnipay/securepay) we can implement different gateways listed below.
We're going to integrate SecurePay DirectPost v2 in this article below.
Before starting the coding let's install the package via composer.
We're going to integrate this package with Laravel v5.6 for this article, use the command below to install the package.
composer require league/omnipay omnipay/securepay
Let's add some new routes for this integration under app/routes/web.php
Route::get('/checkout/payment', [
'name' => 'Payment',
'as' => 'checkout.payment',
'uses' => 'PaymentController@checkout',
]);
Route::post('/checkout/payment/{order}/process', [
'name' => 'Payment',
'as' => 'checkout.payment.process',
'uses' => 'PaymentController@payment',
]);
Route::get('/checkout/payment/{order}/completed', [
'name' => 'Payment Completed',
'as' => 'checkout.payment.completed',
'uses' => 'PaymentController@completed',
]);
Route::get('/checkout/payment/{order}/failed', [
'name' => 'Payment Failed',
'as' => 'checkout.payment.failed',
'uses' => 'PaymentController@failed',
]);
Adding new model toapp/Order.php
handle database operations to store orders coming through the website.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
const PAYMENT_COMPLETED = 1;
const PAYMENT_PENDING = 0;
/**
* @var string
*/
protected $table = 'orders';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}
This model has really basic stuff to show the process while building this integration guide, you are free to customize as per the requirement of your application.
A new database table is needed to store the orders. Let's create a table orders
with a console command.
php artisan make:migration create_table_orders --create=orders
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->increments('id');
$table->string('transaction_id')->nullable();
$table->float('amount')->unsigned()->nullable();
$table->integer('payment_status')->unsigned()->default(0);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('orders');
}
}
Let's add a new classapp/SecurePay.php
which interacts with library classes and framework classes to interact with API services.
<?php
namespace App;
use Exception;
use Illuminate\Http\Request;
use Omnipay\Common\CreditCard;
use Omnipay\Common\Exception\InvalidCreditCardException;
use Omnipay\Omnipay;
/**
* Class SecurePay
* @package App
*/
class SecurePay
{
/**
* @return mixed
*/
public function gateway()
{
$gateway = Omnipay::create('SecurePay_DirectPost');
$gateway->setMerchantId(config('services.securepay.merchant_id'));
$gateway->setTransactionPassword(config('services.securepay.password'));
$gateway->setTestMode(config('services.securepay.sandbox'));
return $gateway;
}
/**
* @param array $parameters
* @param Request $request
*/
public function card(array $parameters, Request $request)
{
return [
'card' => new CreditCard([
'firstName' => $request->get('first_name'),
'lastName' => $request->get('last_name'),
'number' => $request->get('card_number'),
'expiryMonth' => $request->get('expiry_month'),
'expiryYear' => $request->get('expiry_year'),
'cvv' => $request->get('cvc'),
]),
];
}
/**
* @param array $parameters
* @param Request $request
* @return mixed
*/
public function purchase(array $parameters, Request $request)
{
$parameters = array_merge($parameters, $this->card($parameters, $request));
try {
$response = $this->gateway()
->purchase($parameters)
->send();
} catch (InvalidCreditCardException $e) {
throw new Exception($e->getMessage());
}
return $response;
}
/**
* @param array $parameters
* @param Request $request
* @return mixed
*/
public function complete(array $parameters, Request $request)
{
$parameters = array_merge($parameters, $this->card($parameters, $request));
$response = $this->gateway()
->completePurchase($parameters)
->send();
return $response;
}
/**
* @param $amount
*/
public function formatAmount($amount)
{
return number_format($amount, 2, '.', '');
}
/**
* @param $order
*/
public function getCancelUrl($order)
{
return route('checkout.payment.failed', $order->id);
}
/**
* @param $order
*/
public function getReturnUrl($order)
{
return route('checkout.payment.completed', $order->id);
}
}
To store the API credentials required to connect with API service, you can use config/services.php
which ships with the framework out of the box.
'securepay' => [
'merchant_id' => env('SECUREPAY_MERCHANTID'),
'password' => env('SECUREPAY_PASSWORD'),
'sandbox' => env('SECUREPAY_SANDBOX', true)
],
Now, we need a controller class to handle and process all incoming requests into the application. Let's add a class PaymentController.php
<?php
namespace App\Http\Controllers;
use App\Order;
use App\SecurePay;
use Exception;
use Illuminate\Http\Request;
/**
* Class PaymentController
* @package App\Http\Controllers
*/
class PaymentController extends Controller
{
/**
* @param Request $request
*/
public function checkout(Request $request)
{
$order = Order::findOrFail(mt_rand(1, 140));
// you application logic goes here
// the above order is just for example.
return view('checkout.payment', compact('order'));
}
/**
* @param $order_id
* @param Request $request
*/
public function payment($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
$gateway = new SecurePay;
try {
$response = $gateway->purchase([
'amount' => $gateway->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $gateway->getCancelUrl($order),
'returnUrl' => $gateway->getReturnUrl($order),
], $request);
} catch (Exception $e) {
$order->update(['payment_status' => Order::PAYMENT_PENDING]);
return redirect()
->route('checkout.payment.failed', [$order->id])
->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
}
if ($response->isRedirect()) {
$response->redirect();
}
return redirect()->back()->with([
'message' => "We're unable to process your payment at the moment, please try again !",
]);
}
/**
* @param $order_id
* @param Request $request
* @return mixed
*/
public function completed($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
$gateway = new SecurePay;
$response = $gateway->complete([
'amount' => $gateway->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $gateway->getCancelUrl($order),
'returnUrl' => $gateway->getReturnUrl($order),
], $request);
if ($response->isSuccessful()) {
$order->update([
'transaction_id' => $response->getTransactionReference(),
'payment_status' => Order::PAYMENT_COMPLETED,
]);
return redirect()->route('checkout.payment')->with([
'message' => 'Payment Successful, Thank you for your order !',
]);
}
return redirect()->back()->with([
'message' => $response->getMessage(),
]);
}
/**
* @param $order_id
* @param Request $request
* @return mixed
*/
public function failed($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
return view('checkout.payment', compact('order'));
}
}
To submit the payment information by the customer we need a checkout page, for this example, I have implemented a nice view with the code snippet below.
<div class="card-details">
<h3 class="title">Credit Card Details</h3>
<span>@csrf</span>
<div class="row">
<div class="form-group col-sm-8">
<label for="card-holder">Card Holder</label>
<div class="input-group expiration-date">
<input type="text" class="form-control" placeholder="Fist Name" name="first_name" aria-label="Fist Name" aria-describedby="basic-addon1">
<span class="date-separator">/</span>
<input type="text" class="form-control" placeholder="Last Name" name="last_name" aria-label="Last Name" aria-describedby="basic-addon1">
</div>
</div>
<div class="form-group col-sm-4">
<label for="">Expiration Date</label>
<div class="input-group expiration-date">
<input type="text" class="form-control" placeholder="MM" aria-label="MM" name="expiry_month" aria-describedby="basic-addon1">
<span class="date-separator">/</span>
<input type="text" class="form-control" placeholder="YY" aria-label="YY" name="expiry_year" aria-describedby="basic-addon1">
</div>
</div>
<div class="form-group col-sm-8">
<label for="card-number">Card Number</label>
<input id="card-number" type="text" class="form-control" placeholder="Card Number" name="card_number" aria-label="Card Holder" aria-describedby="basic-addon1">
</div>
<div class="form-group col-sm-4">
<label for="cvc">CVC</label>
<input id="cvc" type="text" class="form-control" placeholder="CVC" aria-label="CVC" name="cvc" aria-describedby="basic-addon1">
</div>
<div class="form-group col-sm-12">
<button type="submit" class="btn btn-primary btn-block">
<i class="fa fa-credit-card" aria-hidden="true"></i>
Pay with Credit Card
</button>
</div>
</div>
</div>
To view the full HTML code click here.
The preview looks like below.
To view the full source code written while crafting this blog post, visit the github repository and grab the code.
Thanks for reading up to the end, I hope you enjoyed reading this article. If you have any feedback on this article feel free to leave your comment below.
Happy Coding!
]]>In my previous article I explained about how to Integrate Omnipay v2.* with Laravel framework.
In the new release v3.0, the team focused mainly on separating the HTTP Client, to be independent with Guzzle. This release now supports symfony 3,4 components to run with all Laravel v5.* versions. This is a most awaited release, developers were not able to use, as it was not fully compatible with latest symfony components.
To know about the breaking changes and new additions to the package, view the full release note from their official doc and github repository.
The original omnipay/omnipay
package has been changed as league/omnipay
By using this package (omnipay/paypal
) we can implement different gateways listed below.
We're going to integrate PayPal Express Checkout in this article below.
Before starting the coding let's install the package via composer.
composer require league/omnipay omnipay/paypal
Let's add new routes for the PayPal integration under app/routes/web.php
Route::get('/paypal/{order?}', [
'name' => 'PayPal Express Checkout',
'as' => 'order.paypal',
'uses' => 'PayPalController@form',
]);
Route::post('/checkout/payment/{order}/paypal', [
'name' => 'PayPal Express Checkout',
'as' => 'checkout.payment.paypal',
'uses' => 'PayPalController@checkout',
]);
Route::get('/paypal/checkout/{order}/completed', [
'name' => 'PayPal Express Checkout',
'as' => 'paypal.checkout.completed',
'uses' => 'PayPalController@completed',
]);
Route::get('/paypal/checkout/{order}/cancelled', [
'name' => 'PayPal Express Checkout',
'as' => 'paypal.checkout.cancelled',
'uses' => 'PayPalController@cancelled',
]);
Route::post('/webhook/paypal/{order?}/{env?}', [
'name' => 'PayPal Express IPN',
'as' => 'webhook.paypal.ipn',
'uses' => 'PayPalController@webhook',
]);
I mainly write named routes all over my applications, having name routes are easy to create URLs from anywhere within the project with a simple helper route()
, I have mentioned before about this mechanism with my previous articles as well.
Adding required models for this implementation.
app/Order.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
const PAYMENT_COMPLETED = 1;
const PAYMENT_PENDING = 0;
/**
* @var string
*/
protected $table = 'orders';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}
The order model only has few basic fields implemented just for the simple example, you are free to customize with your required fields in your application.
Adding new database table orders
, you need to create a migration file with a console command.
php artisan make:migration create_table_orders --create=orders
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->increments('id');
$table->string('transaction_id')->nullable();
$table->float('amount')->unsigned()->nullable();
$table->integer('payment_status')->unsigned()->default(0);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('orders');
}
}
Adding new helper class for generating gateway object and routes to interact with PayPal API.
app/PayPal.php
<?php
namespace App;
use Omnipay\Omnipay;
/**
* Class PayPal
* @package App
*/
class PayPal
{
/**
* @return mixed
*/
public function gateway()
{
$gateway = Omnipay::create('PayPal_Express');
$gateway->setUsername(config('services.paypal.username'));
$gateway->setPassword(config('services.paypal.password'));
$gateway->setSignature(config('services.paypal.signature'));
$gateway->setTestMode(config('services.paypal.sandbox'));
return $gateway;
}
/**
* @param array $parameters
* @return mixed
*/
public function purchase(array $parameters)
{
$response = $this->gateway()
->purchase($parameters)
->send();
return $response;
}
/**
* @param array $parameters
*/
public function complete(array $parameters)
{
$response = $this->gateway()
->completePurchase($parameters)
->send();
return $response;
}
/**
* @param $amount
*/
public function formatAmount($amount)
{
return number_format($amount, 2, '.', '');
}
/**
* @param $order
*/
public function getCancelUrl($order)
{
return route('paypal.checkout.cancelled', $order->id);
}
/**
* @param $order
*/
public function getReturnUrl($order)
{
return route('paypal.checkout.completed', $order->id);
}
/**
* @param $order
*/
public function getNotifyUrl($order)
{
$env = config('services.paypal.sandbox') ? "sandbox" : "live";
return route('webhook.paypal.ipn', [$order->id, $env]);
}
}
Laravel ships with a config file config/services.php
to store credentials to be used with third-party API services. For this implementation add following credentials under the same file like below.
'paypal' => [
'username' => env('PAYPAL_USERNAME'),
'password' => env('PAYPAL_PASSWORD'),
'signature' => env('PAYPAL_SIGNATURE'),
'sandbox' => env('PAYPAL_SANDBOX'),
],
To handle and maintain the request coming into the application we need to pass information to the controller. We are adding new PayPalController.php
<?php
namespace App\Http\Controllers;
use App\Order;
use App\PayPal;
use Illuminate\Http\Request;
/**
* Class PayPalController
* @package App\Http\Controllers
*/
class PayPalController extends Controller
{
/**
* @param Request $request
*/
public function form(Request $request)
{
$order = Order::findOrFail(mt_rand(1, 140));
// the above order is just for example.
return view('form', compact('order'));
}
/**
* @param $order_id
* @param Request $request
*/
public function checkout($order_id, Request $request)
{
$order = Order::findOrFail(decrypt($order_id));
$paypal = new PayPal;
$response = $paypal->purchase([
'amount' => $paypal->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $paypal->getCancelUrl($order),
'returnUrl' => $paypal->getReturnUrl($order),
]);
if ($response->isRedirect()) {
$response->redirect();
}
return redirect()->back()->with([
'message' => $response->getMessage(),
]);
}
/**
* @param $order_id
* @param Request $request
* @return mixed
*/
public function completed($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
$paypal = new PayPal;
$response = $paypal->complete([
'amount' => $paypal->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $paypal->getCancelUrl($order),
'returnUrl' => $paypal->getReturnUrl($order),
'notifyUrl' => $paypal->getNotifyUrl($order),
]);
if ($response->isSuccessful()) {
$order->update([
'transaction_id' => $response->getTransactionReference(),
'payment_status' => Order::PAYMENT_COMPLETED,
]);
return redirect()->route('order.paypal', encrypt($order_id))->with([
'message' => 'You recent payment is sucessful with reference code ' . $response->getTransactionReference(),
]);
}
return redirect()->back()->with([
'message' => $response->getMessage(),
]);
}
/**
* @param $order_id
*/
public function cancelled($order_id)
{
$order = Order::findOrFail($order_id);
return redirect()->route('order.paypal', encrypt($order_id))->with([
'message' => 'You have cancelled your recent PayPal payment !',
]);
}
/**
* @param $order_id
* @param $env
* @param Request $request
*/
public function webhook($order_id, $env, Request $request)
{
// to do with new release of sudiptpa/paypal-ipn v3.0 (under development)
}
}
Finally, adding a simple view to submitting a form to pay the order amount with PayPal, the form redirects the user to PayPal hosted page to enter the user account details for the security reason.
app/resources/views/form.blade.php
@extends('app')
@section('content')
<div class="container">
<div class="gateway--info">
<div class="gateway--desc">
@if(session()->has('message'))
<p class="message">
{{ session('message') }}
</p>
@endif
<div class="row">
<div class="col">
<img src="{{ asset('images/paypal.png') }}" class="img-responsive gateway__img">
</div>
<div class="col">
<img src="{{ asset('images/laravel.png') }}" class="img-responsive gateway__img">
</div>
</div>
<p><strong>Order Overview !</strong></p>
<hr>
<p>Item : Yearly Subscription cost !</p>
<p>Amount : ${{ $order->amount }}</p>
<hr>
</div>
<div class="gateway--paypal">
<form method="POST" action="{{ route('checkout.payment.paypal', ['order' => encrypt(mt_rand(1, 140))]) }}">
{{ csrf_field() }}
<button class="btn btn-pay">
<i class="fa fa-paypal" aria-hidden="true"></i> Pay with PayPal
</button>
</form>
</div>
</div>
</div>
@stop
The preview of the above view looks like below, I just made for this example.
For the above real-time example, I tried to cover only the mechanism to handle the payment request, failed payment, canceled a payment. You are free to customize it and manage as per your application architecture. The above example was created with Laravel v5.6 version but it works for all v5.* versions, that's the benefit of the new release for omnipay/paypal latest version v3.0.
To view the full source codes, view few commits (65e4b5a, 75a9585, c469e59, ab0b4db b24fa97) on github example repository.
Thanks for reading this article up on the end, please leave your feedback below in the comment section.
Happy Coding!
]]>In this article, I'm writing about auto-suggesting postcode, the suburb of Australia with Australia Post API while filling up the address form.
I was recently working with an Australian client, it was an e-commerce platform to sell their products online.
I've also integrated shipping services like Temando, Transdirect, Auspost to calculate the postage cost in real-time. To protect their customer from not making mistake while filling up address form, they were interested in integrating real-time postcode, suburb auto-suggest. The idea was just for making the real-time postage cost calculation more consistent with the valid location.
1. Grab API Key
Before starting to build the feature and to make an API call, firstly we need to grab an API key from the Auspost developers center.
2. Workflow
3. Coding
I'm going to integrate this feature on top of Laravel framework v5.6, you are free to utilize it with any type of PHP project.
Adding new routes to display form, and for AJAX API call to the backend.
routes/web.php
Route::get('/postcode/search', [
'name' => 'Australia Post Postcode Search',
'as' => 'app.postcode.search',
'uses' => 'AustraliaPostController@index',
]);
Route::get('/postcode/api/search', [
'name' => 'Australia Post Postcode Search',
'as' => 'app.postcode.api.search',
'uses' => 'AustraliaPostController@search',
]);
app/Http/Controllers/AustraliaPostController.php
<?php
namespace App\Http\Controllers;
use App\Auspost\Request as Auspost;
use Illuminate\Http\Request;
/**
* Class AustraliaPostController
* @package App\Http\Controllers
*/
class AustraliaPostController extends Controller
{
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Australia Post API search form.
*
* @return mixed
*/
public function index()
{
return view('auspost.search');
}
/**
* @return array
*/
public function search()
{
$parameters = [
'q' => $this->request->get('query'),
];
$response = with(new Auspost(config('auspost.auth_key')))
->send($parameters);
$localities = collect($response['localities']['locality'] ?? []);
if ($localities->count()) {
$collection = is_array($localities->first()) ? $localities : [$localities];
return response()->json($collection);
}
return [];
}
}
config/auspost.php
<?php
// replace xx with your real Auth Key sent by Auspost developers centre.
return [
'auth_key' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
];
app/Auspost/Request.php
<?php
namespace App\Auspost;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
/**
* Class Request
* @package App\Auspost
*/
class Request
{
const END_POINT = 'https://digitalapi.auspost.com.au/postcode/search.json';
/**
* The API token.
*
* @var string
*/
protected $token;
/**
* The guzzle http client.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* Create a new request instance.
*
* @param string $token
*/
public function __construct($token)
{
$this->token = $token;
$this->client = new Client();
}
public function getEndpoint()
{
return self::END_POINT;
}
/**
* Retrive data via API call from Auspost server.
*
* @param array $parameters
* @param $method
*
* @return App\Auspost\Response
*/
public function send(array $parameters = [], $method = 'get')
{
$url = $this->getEndpoint() . '?' . http_build_query($parameters);
$parameters = [
'headers' => [
'Content-Type' => 'application/json',
'Auth-Key' => $this->token,
],
];
try {
$response = $this->client->request($method, $url, $parameters);
} catch (ClientException $exception) {
return $exception;
}
return with(new Response($response))->toArray();
}
}
app/Auspost/Response.php
<?php
namespace App\Auspost;
use GuzzleHttp\Psr7\Response as GuzzleResponse;
/**
* Class Response.
* @package App\Auspost
*/
class Response extends GuzzleResponse
{
/**
* The guzzle http client response.
*
* @var \GuzzleHttp\Message\Response
*/
protected $response;
/**
* Create a new response instance.
*
* @param GuzzleResponse $response
*/
public function __construct(GuzzleResponse $response)
{
$this->response = $response;
}
/**
* @return mixed
*/
public function toJson()
{
return (string) $this->response->getBody();
}
/**
* @return mixed
*/
public function toArray()
{
return json_decode($this->toJson(), true);
}
}
resources/views/search.blade.php
<div class="card-body">
<form method="POST" action="/register">
@csrf
<div class="form-group row">
<label for="search" class="col-md-4 col-form-label text-md-right">Postcode</label>
<div class="col-md-8">
<input id="search" type="text" data-suggest-postcode="{{ route('app.postcode.api.search') }}" class="form-control" name="email">
</div>
</div>
</form>
</div>
I have only shown a small block of code above for the form, to view the full source code for the above tutorial feel free to visit the repository on github.
Also, view the last updated code here with another pull request in GitHub.
4. Preview
Note: To run the above example, it requires twitter typeahead javascript library v0.11.1, Laravel 5.6, guzzle 6.*, but you could still use it with lower version of Laravel and guzzle library.
Conclusion
Thanks for reading the full article up to the end, feel free to leave your feedback if you are happy reading it.
Happy Coding!
]]>
At my work, we heavily use Laravel framework, which follows the PSR-2 coding standard. So, every day we create new packages, add new classes we first format our code to PSR-2 coding standard before submitting a pull request. We always strive to follow the coding standard and modern best practices to keep our code cleaner and elegant.
I personally use sublime as my primary code editor all the time, the main reason is its lightness and popularity among the developers around the world, another beneficial part is the huge availability of open-source packages for it.
Below, I'm going to cover how we format our PHP code to PSR-2 coding standard in sublime text. The team at symfony and the contributors have done really great job building a package that can be used with sublime text to format coding standard.
Step 1.
Installing php-cs-fixer via composer to run globally on your machine.
composer global require friendsofphp/php-cs-fixer
After the installation process is completed open your terminal and simply run php-cs-fixer command just to confirm it's installed properly.
It is always a tedious job when you go and fix the formatting on each file you have in your project. Below, I have an example for you if you want to try to format your files manually.
//Eg:
php-cs-fixer fix /path/project --rules=@PSR2
Please refer to the official doc to discover all the available rules and commands you could use for your formatting.
Step 2.
To automate the code formatting process, we're going to set up a build process in a sublime text, so whenever you need to format your code instead of running the long command, you could just press the button on a keyword to fix it.
Open your sublime text editor.
Go to Tools Menu > Build System > New Build System
{
"shell_cmd": "php-cs-fixer fix $file --rules=@PSR2"
}
Now, try to save, a prompt window will open, create a new folder called Builds at that position, save it inside that folder with name PSR-2.sublime-build
The build process setup is now ready to use, before using it we need to make a selection under Tools Menu > Build System > PSR 2 which is a new command we just added with the process above.
Usage
Learn more about PSR-2 Coding Standard.
Conclusion
Thanks for reading the article up to the end, if you are happy reading it and was helpful to you, feel free to share and leave your feedback below in the comment section.
Happy Coding!
]]>API Credentials
Follow up the developer guide via their documentation, generate Client ID, Client Secret and you will need to go to another site to generate an access token.
Step 1
Let’s add new config array under existing file config/services.php
'instagram' => [
'access-token' => 'xxxxx.xxx.xxxx',
//replace xxx with your actual access token
],
Step 2
Adding a new route to handle the URL /instagram/feed
under routes/web.php
Route::get('/instagram/feed', [
'name' => 'Instagram Feed',
'as' => 'app.instagram.feed',
'uses' => 'InstagramController@feed',
]);
Step 3
Installing package via composer, use command from below or follow the installation guide from the package repository on github.
composer require vinkla/instagram php-http/message php-http/guzzle6-adapter
Step 4
Creating a new controller to handle the request and execute the API call.
<?php
namespace App\Http\Controllers;
use Vinkla\Instagram\Instagram;
/**
* Class InstagramController
* @package App\Http\Controllers
*/
class InstagramController extends Controller
{
/**
* @return mixed
*/
public function feed()
{
$instagram = new Instagram(config('services.instagram.access-token'));
$images = collect($instagram->get())->map(function ($each) {
return $each->images->standard_resolution->url;
});
return view('gallery', compact('images'));
}
}
Now, look at the code above in the controller, I have added the logic to pull down the Instagram feeds via API and utilized the Laravel collection map()
method to filter the images with standard resolution only. If you would like to show likes, a caption associated with those images you could customize that array and call them while rendering on the view file.
Step 5
Finally, its time to loop the images on gallery.blade.php file under resources/views.
<div class="tz-gallery">
<div class="row">
@foreach($images as $key => $image)
<div class="col-sm-12 col-md-4">
<a class="lightbox" href="{{ $image }}">
<img src="{{ $image }}" alt="Instagram Feed" class="img-responsive">
</a>
</div>
@endforeach
</div>
</div>
I have added a small block of code to just show the process of looping and displaying the images. The full source code I wrote for this tutorial is available on github, feel free to review the full pull request and utilize on your project.
Conclusion
Thanks for reading this article up to the end, if it was helpful to you, feel free share the knowledge with others, also don't forget to leave your feedback below in the comment section.
Happy Coding!
]]>Recently I built this feature for my own blog, if you would like to view it open up the home page of my blog and see around the right sidebar.
I have used the popular laravel package laravel-analytics built by Spatie.
Prerequisites
Before starting to do the actual coding, first, you need to generate the API credentials required for API
connectivity.
I am not going to cover the process of generating the API credentials, but I recommend you to follow the full steps explained by Spatie in their package documentation page.
Feature
The trending section I have built has following features, it was based on my own requirement.
If you would like to show the trending posts directly to your view without storing any records to the database or without any implicit logic, that is also provided by the package out of the box.
I will be doing it differently in this article, see below.
Coding
Installing package via console mode with the composer.
composer require spatie/laravel-analytics
The package will be auto-registered in laravel v5.5 and above. If you are using lower version register the package by following the installation guide.
app/Blog.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Blog
* @package App
*/
class Blog extends Model
{
/**
* @var string
*/
protected $table = 'blog';
/**
* @var array
*/
protected $dates = ['published_at', 'deleted_at'];
/**
* @var string
*/
protected $fillable = ['user_id', 'name', 'slug', 'excerpt', 'content', 'status', 'published_at'];
/**
* @param $slug
*/
public static function findByTitle($slug = null)
{
return self::where('slug', $slug)->first();
}
}
I am assuming that you are running blogs already, my blog model looks like above.
Adding new migration for trendings table.
php artisan make:migration create_trendings_table --create=trendings
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTrendingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('trendings', function (Blueprint $table) {
$table->increments('id');
$table->integer('blog_id')->nullable();
$table->integer('views')->nullable();
$table->string('url', 1024)->nullable();
$table->string('page_title', 1024)->nullable();
$table->tinyInteger('status')->default(1)->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('trendings');
}
}
app/Trending.php
<?php
namespace App;
use App\Blog;
use Illuminate\Database\Eloquent\Model;
use Spatie\Analytics\Period;
/**
* Class Trending
* @package App
*/
class Trending extends Model
{
/**
* @var string
*/
protected $table = 'trendings';
/**
* @var boolean
*/
public $timestamps = true;
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var string
*/
protected $fillable = ['blog_id', 'views', 'url', 'page_title'];
/**
* @return mixed
*/
public function blog()
{
return $this->belongsTo(Blog::class);
}
/**
* @param $query
* @return mixed
*/
public function scopeMonthly($query)
{
$period = Period::months(1);
return $query->whereBetween('created_at', [
$period->startDate,
$period->endDate->endOfDay(),
]);
}
/**
* @param $query
* @return mixed
*/
public function scopeWeekly($query)
{
$period = Period::days(7);
return $query->whereBetween('created_at', [
$period->startDate,
$period->endDate->endOfDay(),
]);
}
}
Adding new console command for API
connectivity.
php artisan make:command TrendingCommand
<?php
namespace App\Console\Commands;
use App\Blog;
use App\Trending;
use Illuminate\Console\Command;
use Spatie\Analytics\AnalyticsFacade as Analytics;
use Spatie\Analytics\Period;
/**
* Class TrendingCommand
* @package App\Console\Commands
*/
class TrendingCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'analytics:trending';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync page view from Google Analytics API';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$pages = Analytics::fetchMostVisitedPages(Period::days(1), 300);
if ($pages->count()) {
$this->purge();
$pages->map(function ($each) {
$each = (object) $each;
if (starts_with($each->url, '/blog/')) {
$slug = str_replace("/blog/", '', $each->url);
$blog = Blog::findByTitle($slug);
if (!empty($blog)) {
Trending::create([
'blog_id' => $blog->id,
'views' => $each->pageViews,
'status' => $blog->status,
'page_title' => $blog->name,
'url' => $each->url,
]);
$this->info("{$blog->name} - {$each->pageViews} \n");
}
}
});
}
}
/**
* @return mixed
*/
public function purge()
{
$period = Period::days(8);
$this->info("Purging records before : {$period->startDate} \n");
return Trending::where('created_at', '<', $period->startDate)
->forceDelete();
}
}
Registering console command to run with the application.
app/Console/Kernel.php
protected $commands = [
'App\Console\Commands\TrendingCommand',
];
app/Helpers/Trending.php
<?php
namespace App\Helpers;
use App\Trending as Popular;
use Illuminate\Support\Facades\Cache;
/**
* Class Trending
* @package App\Helpers
*/
class Trending
{
/**
* @return mixed
*/
public static function weekly($take = 15)
{
$collection = collect();
$trendings = Cache::remember('popular', 60 * 24, function () {
return Popular::with('blog')->weekly()->get();
});
$trendings->groupBy('blog_id')->map(function ($each) use ($collection) {
$object = collect($each);
$item = $object->first();
$item->views = $object->sum('views');
$collection->push($item);
});
return $collection->take($take);
}
}
The job of the above helper class is to implicitly manipulate the seven days pages views and sort them out by the highest page views.
Now, creating new view partial to include it where ever you want to show the trending list.
resources/views/_trending.blade.php
@inject('trending', 'App\Helpers\Trending')
@php
$trendings = $trending::weekly();
@endphp
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Trending</div>
<div class="card-body">
@if($trendings->count())
<ul>
@foreach($trendings as $key => $each)
<li>
<div class="article">
<a href="{{ asset($each->url) }}" title="{{ $each->views }}">
<span class="text-muted">{{ $each->blog->published_at->format('d M, Y') }}</span><br>
{{ $each->blog->name }}
</a>
</div>
</li>
@endforeach
</ul>
@endif
</div>
</div>
</div>
</div>
</div>
@endsection
Alternative
If you would like to consume the real-time data via API instead of going through the above feature I built for my use case, you could just follow the package documentation and it has enough resources to understand.
// pull down real time data via Google Analytics API
Analytics::fetchMostVisitedPages(Period::days(7));
Now after completing the coding work, the most important thing is to set up a cron job that runs mid-night at 12:00 am to pull down the complete page views for that day.
My cron job looks like below.
0 0 * * * /usr/bin/php /home/deployer/project/artisan analytics:trending
Conclusion
Thanks for reading the article up to the end, if you are happy reading it and was helpful to you, feel free to share and leave your feedback below in the comment section. The article was written based on Laravel v5.6
, feel free to utilize it with higher or lower versions as per your requirement. The full source code is available on github.
Happy Coding!
]]>In this post, I will show you how to automatically export articles whenever you publish a new article on your website. I recently built this feature for my own website, and it was built on top of Laravel framework.
If your website runs other than WordPress and you want to integrate the RSS feed, this post will help you to understand how to do that for your platform. If you would like to know about exporting the Instant Articles from WordPress website, you can read this doc from facebook developers platform.
Two different ways to export Instant Articles.
RSS Publishing, API Publishing
The easiest way is setting up RSS feed, which will be automatically ingested periodically by Facebook. So there will be no other configurations required to pull down your fresh articles from your CMS.
Let's begin to build RSS feed, to allow Facebook to read in XML version.
Adding new routes under routes/web.php
Route::get('blog/rss', [
'as' => 'app.blog.rss',
'uses' => 'BlogController@rss',
]);
Route::get('blog/{slug}', [
'as' => 'app.blog.view',
'uses' => 'BlogController@view',
]);
Adding new Blog model app/Blog.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Blog
* @package App
*/
class Blog extends Model
{
const STATUS_ENABLED = 1;
const STATUS_DISABLED = 0;
/**
* @var string
*/
protected $table = 'blog';
/**
* @var array
*/
protected $dates = ['published_at', 'deleted_at'];
/**
* @var string
*/
protected $fillable = ['user_id', 'name', 'slug', 'guid', 'excerpt', 'content', 'status', 'published_at'];
/**
* @param Builder $query
* @return mixed
*/
public function scopePublished($query)
{
return $query->where('published_at', '<=', now());
}
/**
* @param Builder $query
* @return mixed
*/
public function scopeEnabled($query)
{
return $query->where('status', self::STATUS_ENABLED);
}
}
Adding new controller BlogController.php
<?php
namespace App\Http\Controllers;
use App\Blog;
use Illuminate\Support\Str;
/**
* Class BlogController
* @package App\Http\Controllers
*/
class BlogController extends Controller
{
public function rss()
{
$blogs = Blog::enabled()
->published()
->latest('published_at')
->get();
$blogs->map(function ($each) {
if (empty($each->guid)) {
$each->guid = Str::uuid();
}
});
return view('rss', [
'blogs' => $blogs,
'title' => 'Short title about your blog',
]);
}
/**
* @param $slug
*/
public function view($slug)
{
return Blog::where('slug', $slug)->first();
}
}
Str::uuid()
was introduced with Laravel v5.6, with lower versions you could use my simple package for creating guid.
composer require sudiptpa/guid
To discover the features, please through the github link.
Adding new view resources/rss.blade.php
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>{{ $title }}</title>
<link>{{ url('/') }}</link>
<description>
A short description about your blog.
</description>
<language>en-us</language>
<lastBuildDate>{{ date('c') }}</lastBuildDate>
@forelse($blogs as $key => $blog)
<item>
<title><![CDATA[{{ $blog->name }}]]></title>
<link>{{ route('app.blog.view', ['slug' => $blog->slug]) }}</link>
<guid>{{ $blog->guid }}</guid>
<pubDate>{{ date('c', strtotime($blog->published_at)) }}</pubDate>
<author>Author Name</author>
<description><![CDATA[{{ $blog->excerpt }}]]></description>
<content:encoded>
<![CDATA[
@include('partials._rss')
]]>
</content:encoded>
</item>
@empty
<item>No feeds found</item>
@endforelse
</channel>
</rss>
Adding another partial file included with the above view. resources/partials/_rss.blade.php
<!doctype html>
<html lang="en" prefix="op: http://media.facebook.com/op#">
<head>
<meta charset="utf-8">
<meta property="op:markup_version" content="v1.0">
<meta property="fb:article_style" content="default"/>
<link rel="canonical" href="{{ route('app.blog.view', ['slug' => $blog->slug]) }}">
<title>{{ $blog->name }}</title>
</head>
<body>
<article>
<header>
<h1>{{ $blog->name }}</h1>
<h2> {{ $blog->excerpt }}</h2>
<h3 class="op-kicker">
PHP <!-- Replace with your category name-->
</h3>
<address>
Sujip Thapa <!-- Replace with your author name-->
</address>
<time class="op-published" dateTime="$blog->published_at->format('c') }}">{{ $blog->published_at->format('M d Y, h:i a') }}</time>
<time class="op-modified" dateTime="{{ $blog->updated_at->format('c') }}">{{ $blog->updated_at->format('M d Y, h:i a') }}</time>
</header>
{{ $blog->content }}
<footer>
<aside>
A short footer note for your each Instant Articles.
</aside>
<small>© Copyright {{ date('Y') }}</small>
</footer>
</article>
</body>
</html>
Note: After exporting articles to Instant Articles platform, you will need to share the post on your facebook page, after sharing on that page article will start appearing as Instant Article.
Finally, to implement the above feature, follow the below steps.
in the input box paste the link ending with blog/rss: (example: https://sujipthapa.co/blog/rss)
After saving the RSS feed URL, Facebook will start to retrieve fresh articles periodically.
Conclusion
Thanks for reading the article up to the end, feel free to give your feedback below in the comment section if you like.
Happy Coding!
]]>Why is defining preferred domain necessary?
Defining a Google preferred domain is a way of instructing Google to use a final version of your domain to be used forever. Your web pages link should be permanent and never be changing frequently so that Google can start indexing, which is a process of adding pages of your website into Google search. The indexed pages will appear in Google searches around the world with matching keywords.
The important part is if your web pages URL are not same when a google crawler runs on your site or the pages start to redirect to 404, Google will start removing them from indexing and they will not appear in search results.
The example of what I wanted to describe above is, your domain should always be like, either www or non-www (i.e. http://www.sujipthapa.co or http://sujipthapa.co).
The primary concern of setting up a google preferred domain is to improve the SEO of your website, that instructs Google to always recognize the final version of your URL to appear in search results.
I would like to recommend you to read this Google support article to understand in detail about the benefits of setting google preferred domain for your website.
Note: Below with the code, it will only focus on forcing your URL to be the preferred version you have selected on Google Search Console. The code will depend on the configuration and will instruct to redirect on the preferred version.
Setup
The first step of the setup is verifying your preferred domain on google end. Read the Google support article carefully and define your preferred version of your domain.
Let's start by adding new config file under config/domain.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Preferred Domain / Canonical Domain
|--------------------------------------------------------------------------
| Example: https://www.example.com (www), https://example.com (non-www)
|
| Available Mode: "//www.", "//"
*/
'preferred' => '//',
/*
|--------------------------------------------------------------------------
| HTTP Protocol
|--------------------------------------------------------------------------
|
| Available Mode: "http://", "https://"
*/
'protocol' => 'https://',
];
The above configuration file has two settings available, one is preferred domain, and another is for SSL mode. I highly recommend you to install SSL certificate for your website. If an SSL certificate is not installed, beginning in July 2018 your site visitors will see a “Not Secure” warning, recently Google Security Blog announced with the release of Chrome 68, it will mark all web pages with HTTP as "not secure" version.
Adding new middleware class PreferredDomain.php
under app/Http/Middleware
directory.
<?php
namespace App\Http\Middleware;
use App\Domain;
use Closure;
/**
* Class PreferredDomain
* @package App\Http\Middleware
*/
class PreferredDomain
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$domain = new Domain($request);
if ($domain->diff()) {
return redirect()->to($domain->translated(), 301);
}
return $next($request);
}
}
This middleware should monitor every HTTP request on your website. So, register it like below under app/Http/Kernal.php
/**
* @var array
*/
protected $middleware = [
...
\App\Http\Middleware\PreferredDomain::class,
];
Again, adding new class Domain.php
under app
directory to apply a way of translating your non-preferred domain to preferred version automatically, it will be translated to the domain version you have defined in the configuration file. It will monitor each request to verify that each request URL is consistent with your preferred version on Google Search Console.
<?php
namespace App;
use Illuminate\Http\Request;
/**
* Class Domain
* @package App
*/
class Domain
{
const FORCE_HTTP = 'http://';
const FORCE_HTTPS = 'https://';
const FORCE_WWW = '//www.';
const FORCE_NOWWW = '//';
const REGEX_FILTER_WWW = '/(\/\/www\.|\/\/)/';
const REGEX_FILTER_HTTPS = '/^(http:\/\/|https:\/\/)/';
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* @var string
*/
protected $translated;
/**
* @param \Illuminate\Http\Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* @return bool
*/
public function isEqual(): bool
{
return $this->request->fullUrl() !== $this->translated();
}
/**
* Determines if the original url differs with translated.
*
* @return bool
*/
public function diff(): bool
{
$this->translated = $this->translate();
return $this->isEqual();
}
/**
* @return string
*/
public function translated(): string
{
if (!$this->translated) {
$this->translated = $this->translate();
}
return $this->translated;
}
/**
* @return string
*/
public function translate(): string
{
$url = $this->request->fullUrl();
$protocol = $this->getProtocol();
$filtered = preg_replace(self::REGEX_FILTER_HTTPS, $protocol, $url);
$preferred = $this->getPreferred();
return preg_replace(self::REGEX_FILTER_WWW, $preferred, $filtered);
}
/**
* Determines if the request supports the https,
* otherwise, fallback to default (http) protocol.
*
* @return string
*/
public function getProtocol(): string
{
if (!$this->request->secure()) {
return self::FORCE_HTTP;
}
return config('domain.protocol') ?? self::FORCE_HTTP;
}
/**
* Determines the preferred domain from config.
*
* @return string
*/
public function getPreferred(): string
{
return config('domain.preferred') ?? self::FORCE_NOWWW;
}
}
Let's say my preferred version is - https://sujipthapa.co but user tries to go to https://www.sujipthapa.co now the middleware will monitor and redirect the non-preferred to preferred version. The job of the middleware is to translate the non-preferred to preferred version and issue a 301 redirect to correct version.
This is an implicit process that executes before the HTTP request enters into the application.
I have used the above middleware Laravel v5.5
, but it is still compatible with the v5.6
version released in Feb 2018.
Conclusion
Thanks for reading the post up to the end, feel free to share, comment your feedback below in the comment section.
Happy Coding!
]]>Let's say on your working computer you have installed multiple PHP versions (for eg: PHP 7.0 and PHP 7.2). As a default version, the PHP 7.0 is set to Nginx or Apache and CLI mode.
Of course, as a modern web developer who works on the different framework, packages need to have multiple PHP versions installed on their working machine. During their work on different projects, the project may not support all latest versions, so the developer may need to switch to a required version of PHP to run that project.
Below I will show you how to use the command line as well as a bash alias function to easily switch your versions.
You may want to go from PHP 7.2 to 7.0, use the commands below.
If you don't know how to check the php version on Linux Ubuntu type below command on terminal.
php -v
//php 7.2 to php 7.0 switcher
sudo a2dismod php7.2 ; sudo a2enmod php7.0 ; sudo systemctl restart apache2
sudo ln -sfn /usr/bin/php7.0 /etc/alternatives/php
//if you are using nginx
sudo systemctl restart nginx
Similarly, while PHP version 7.0 is active, you can fall back to 7.2 with the following commands.
//php 7.0 to php 7.2 switcher
sudo a2dismod php7.0 ; sudo a2enmod php7.2 ; sudo systemctl restart apache2
sudo ln -sfn /usr/bin/php7.2 /etc/alternatives/php
//if you are using nginx
sudo systemctl restart nginx
Repeating the above process frequently is really irritating, time-wasting, right?
Finally, I decided to create a bash alias as a function to reduce my time to repeat the php switching and easy to remember.
Setup
Open up your terminal and type.
sudo nano ~/.bashrc
Scroll around the last line, where we want to paste the below function.
phpswap () {
local IS_PHP7=`php --version|grep "PHP 7.2"`
if [[ -z $IS_PHP7 ]]; then
echo "Switching to PHP 7.2"
sudo a2dismod php7.0;
sudo a2enmod php7.2;
sudo systemctl restart apache2;
sudo ln -sfn /usr/bin/php7.2 /etc/alternatives/php
else
echo "Switching to PHP 7.0"
sudo a2dismod php7.2;
sudo a2enmod php7.0;
sudo systemctl restart apache2;
sudo ln -sfn /usr/bin/php7.0 /etc/alternatives/php
fi
}
Usage
After completing the above process simply hit phpswap
on your terminal.
I use multiple PHP versions, 7.2, 7.0 with Ubuntu 16.04.3 LTS. I simply run phpswap
over the terminal, it detects the current default PHP
version and switches to exactly reverse version which I need to go with.
Easy right?
We're done!
Conclusion
Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.
Happy Coding!
Last Updated: 16th Feb, 2018
Below, I am going to show in detail about the way of handling the webhook notification that comes from PayPal into the Laravel application.
If you are a beginner level developer I highly recommend you to read this official doc to understand the flow of PayPal Instant Payment Notification.
Why handling IPN is necessary?
Let's say a customer at your site is trying to pay for certain service or goods, your application takes the user to PayPal to complete the payment, the user makes payment, it is completed but due to some technical error the payment completed page from PayPal couldn't return to your application to handle completed orders. In that case, your application may fail to send emails, update the database for recently paid order status and its payment status.
To handle this situation PayPal gives a webhook service that sends POST
request with a payload about the payment status.
Let's get started :)
Before diving into the code first let's understand how to set up a notification URL that PayPal actually uses to send a POST
request to your web application. There are actually two different ways to setup IPN notification URL.
In the previous article, you can see how to send the notify URL with the payment request, I have made the notify URL to be dynamic with parameters like order_id
, environment
, so the URL changes for each order.
$paypal = new PayPal;
$response = $paypal->complete([
'amount' => $paypal->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $paypal->getCancelUrl($order),
'returnUrl' => $paypal->getReturnUrl($order),
'notifyUrl' => $paypal->getNotifyUrl($order),
]);
//route for generating notify URL
/**
* @param $order
*/
public function getNotifyUrl($order)
{
$env = config('paypal.credentials.sandbox') ? "sandbox" : "live";
return route('webhook.paypal.ipn', [$order->id, $env]);
}
To understand this article, I recommend you to read the previous post about integrating PayPal with Laravel.
Let's start diving into the code.
Installation
composer require sudiptpa/paypal-ipn
If you are using Laravel v4 stick with 1.0.x-dev
in your composer.json
file.
{
"require": {
"sudiptpa/paypal-ipn": "1.0.x-dev",
}
}
Setup
Defining a route to handle POST
request coming from PayPal server.
Route::post('/webhook/paypal/{order?}/{env?}', [
'name' => 'PayPal Express IPN',
'as' => 'webhook.paypal.ipn',
'uses' => 'PayPalController@webhook',
]);
After creating a route to accept incoming POST
request via PayPal, you need to keep in mind that Laravel by default filters each HTTP
requests entering the application with VerifyCsrfToken
middleware. In order to allow the above route to access the application the URL
should be excluded from the csrf
check.
I have previously covered about it with another article "Disabling CSRF on Specific Route via Middleware", so you can go throgh it to understand how to do that.
Let's create a migration for storing IPN records in the database.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTablePaypalIpnRecords extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('paypal_ipn_records', function (Blueprint $table) {
$table->increments('id');
$table->integer('order_id')->nullable();
$table->string('verified');
$table->string('transaction_id');
$table->string('payment_status');
$table->string('request_method')->nullable();
$table->string('request_url')->nullable();
$table->longText('request_headers')->nullable();
$table->longText('payload')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('paypal_ipn_records');
}
}
A new model PayPalIPN.php
for storing the IPN logs into database.
<?php
namespace App;
use App\Order;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class PayPalIPN
* @package App
*/
class PayPalIPN extends Model
{
use SoftDeletes;
const COMPLETED = "Completed";
const IPN_FAILURE = "FALIURE";
const IPN_INVALID = "INVALID";
const IPN_VERIFIED = "VERIFIED";
/**
* @var boolean
*/
public $timestamps = true;
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['order_id', 'verified', 'transaction_id', 'payment_status', 'request_method', 'request_url', 'request_headers', 'payload'];
/**
* @var string
*/
protected $table = 'paypal_ipn_records';
/**
* @return boolena
*/
public function isCompleted()
{
return in_array($this->payment_status, [self::COMPLETED]);
}
/**
* @return boolena
*/
public function isVerified()
{
return in_array($this->verified, [self::IPN_VERIFIED]);
}
/**
* @return mixed
*/
public function orders()
{
return $this->belongsTo(Order::class);
}
}
Adding few new methods in Order.php
model for easy condition check and model scope to perform database operation.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class Order
* @package App
*/
class Order extends Model
{
use SoftDeletes;
const COMPLETED = 1;
const PENDING = 0;
/**
* @var string
*/
protected $table = 'orders';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['transaction_id', 'amount', 'payment_status'];
/**
* @param Builder $query
* @param string $transaction_id
* @return mixed
*/
public function scopeFindByTransactionId($query, $transaction_id)
{
return $query->where('transaction_id', $transaction_id);
}
/**
* Payment completed.
*
* @return boolean
*/
public function paid()
{
return in_array($this->payment_status, [self::COMPLETED]);
}
/**
* Payment is still pending.
*
* @return boolean
*/
public function unpaid()
{
return in_array($this->payment_status, [self::PENDING]);
}
}
Now creating a webhook()
method on PayPalController.php
<?php
namespace App\Http\Controllers;
use App\Order;
use App\PayPal;
use App\Repositories\IPNRepository;
use Illuminate\Http\Request;
use PayPal\IPN\Listener\Http\ArrayListener;
/**
* Class PayPalController
* @package App\Http\Controllers
*/
class PayPalController extends Controller
{
/**
* @param IPNRepository $repository
*/
public function __construct(IPNRepository $repository)
{
$this->repository = $repository;
}
/**
* @param $order_id
* @param $env
* @param Request $request
*/
public function webhook($order_id, $env, Request $request)
{
$listener = new ArrayListener;
if ($env == 'sandbox') {
$listener->useSandbox();
}
$listener->setData($request->all());
$listener = $listener->run();
$listener->onInvalid(function (IPNInvalid $event) use ($order_id) {
$this->repository->handle($event, PayPalIPN::IPN_INVALID, $order_id);
});
$listener->onVerified(function (IPNVerified $event) use ($order_id) {
$this->repository->handle($event, PayPalIPN::IPN_VERIFIED, $order_id);
});
$listener->onVerificationFailure(function (IPNVerificationFailure $event) use ($order_id) {
$this->repository->handle($event, PayPalIPN::IPN_FAILURE, $order_id);
});
$listener->listen();
}
}
Again, creating another class IPNRepository.php
that act as a bridge between model and controller.
<?php
namespace App\Repositories;
use App\Order;
use App\PayPalIPN;
use Illuminate\Http\Request;
/**
* Class IPNRepository
* @package App\Repositories
*/
class IPNRepository
{
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* @param $event
* @param $verified
* @param $order_id
*/
public function handle($event, $verified, $order_id)
{
$object = $event->getMessage();
if (is_numeric($order_id)) {
$order = Order::find($order_id);
}
if (empty($order)) {
$order = Order::findByTransactionId(
$object->get('txn_id')
)->first();
}
$paypal = PayPalIPN::create([
'verified' => $verified,
'transaction_id' => $object->get('txn_id'),
'order_id' => $order ? $order->id : null,
'payment_status' => $object->get('payment_status'),
'request_method' => $this->request->method(),
'request_url' => $this->request->url(),
'request_headers' => json_encode($this->request->header()),
'payload' => json_encode($this->request->all()),
]);
if ($paypal->isVerified() && $paypal->isCompleted()) {
if ($order && $order->unpaid()) {
$order->update([
'payment_status' => $order::COMPLETED,
]);
// notify customer
// notify order handling staff
// update database logic
}
}
}
}
Testing
PayPal provides an Instant Payment Notification (IPN) simulator to test your integration.
If you want to test with real sandbox credentials, use your staging server for your application, as PayPal never comes to your local development environment with IPN request.
I personally use Insomnia, to design, and test APIs.
Conclusion
The complete source code is available in the github repository, where I push the real codes I prepare while creating every single tutorial on my blog.
Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.
Last Updated: Jan 7, 2021
Happy Coding!
csrf
token. The issue was mainly with the POST
request to the application via external service, so it threw TokenMismatchException
via the VerifyCsrfToken
middleware.
One best thing is Laravel ships with CSRF
enabled by default for each HTTP
request that enters the application, which is made really easy, it handles automatically.
If your application consumes third-party API service, that service may be a webhook to notify about any event and that sends HTTP
request to your application. You need to be aware that Laravel filters the request that enters without csrf
token, as it monitors all request entering into the application for security reason.
There is a good solution as well, and that also ships with the framework by default. See below how to disable checking csrf
token for specific routes in your application, and that fixed my issue as well.
app/Http/Middleware/VerifyCsrfToken.php
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'/webhook/paypal/*',
];
You could specify multiple URLs on that array
if you would like to exclude other routes.
Conclusion
Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.
Happy Coding!
]]>Now, here in this article, I am going to cover about comparing two different timestamp columns to filter out records from the database.
Recently I was working on filtering some API activity log to push them on notification interface, and the database design was with columns consisted of timestamp datatype as synced_at
, optimized_at
.
I wanted to filter the activity log based on values in two dates columns synced_at
, optimized_at
, my logic was to fetch database records if the timestamp in synced_at
is greater optimized_at
. The API process normally does a remote sync and does some database optimization task for making frontend stuff load faster with a database view.
I want to give you two different ways of comparing the columns.
Activity::where('synced_at', '>', DB::raw('optimized_at'))
->latest()
->get();
or
Activity::whereRaw('unsynced_at > optimized_at')
->latest()
->get();
Both of the database queries from the above code snippet give same results.
Conclusion
Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.
Happy Coding!
]]>"Cache-Control: no-cache, must-revalidate"
.
I came up with the best solution with a laravel middleware and decided to share the code snippets with the laravel community people as well. The code snippet will give an idea for you to work on next similar logic and the great solution for setting up the custom header.
Let's get started ;)
Create new middleware with an artisan command, it will be stored in \App\Http\Middleware\NoCache.php
php artisan make:middleware NoCache
<?php
namespace App\Http\Middleware;
use Closure;
/**
* Class NoCache
* @package App\Http\Middleware
*/
class NoCache
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$response->header('Cache-Control', 'no-cache, must-revalidate');
return $response;
}
}
Open \App\Http\Kernel.php
and register the middleware.
There are different ways you might want to use this middleware, you could either apply it globally on every HTTP request over the application or use only on specific routes.
a. Register like below to apply globally on every HTTP request.
/**
* @var array
*/
protected $middleware = [
...
\App\Http\Middleware\NoCache::class,
];
b. Another way is, registering with a key and apply it on routes with that key like below.
/**
* @var array
*/
protected $routeMiddleware = [
...
'nocache' => \App\Http\Middleware\NoCache::class,
];
Now, applying middleware on routes.
Route::group(['middleware' => 'nocache'], function () {
Route::get('blog/{slug}', [
'as' => 'app.blog.view',
'uses' => 'BlogController@view',
]);
});
We are done!
Conclusion
Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.
Happy Coding!
]]>The notifications feature is fully available with later versions after it was launched along with the framework version v5.3. After that people started creating different notifications channel that can be used with your laravel application.
Laravel framework ships with support for sending email, SMS(via Nexmo), and Slack. If you wish to store notifications in a database, they may also be stored, so you can display.
I’ve been working on a new update for this website to make auto-posting to the facebook page via API when a new blog post is published on the website. I'm using a package that has already been created by Ahmed Ashraf.
I will give a full overview of available methods and fully functional code snippets of how I implemented it to automatically post them on my Facebook page.
Package Installation
You can easily install this package via composer:
composer require laravel-notification-channels/facebook-poster
If you are below Laravel v5.5, you need to register the service provider class to config/app.php
...
'providers' => [
...
NotificationChannels\FacebookPoster\FacebookPosterServiceProvider::class,
],
...
Next, you need to go to Facebook to generate some API credentials, to set up this Facebook Poster service.
I recently wrote a detailed blog post to cover the process for generating API credentials, visit this post "Generating Never Expiring Facebook Page Access Token".
After completing the process of generating the API credentials, put them on config/services.php
'facebook_poster' => [
'app_id' => env('FACEBOOK_APP_ID'),
'app_secret' => env('FACEBOOK_APP_SECRET'),
'access_token' => env('FACEBOOK_ACCESS_TOKEN'),
],
Setup Model
In my project setup, I have a Blog model that is responsible for storing and retrieving all blog posts. I'm observing the laravel model events, laravel fires many events in different context. In my case, I am listening to the created
event to automate this process.
You can use your own model related to your topic that you already have. Simply use the Notifiable
trait to it.
use Illuminate\Notifications\Notifiable;
/**
* Class Blog
* @package App
*/
class Blog extends Model
{
use Notifiable;
Now, create a notification class with a command:
php artisan make:notification ArticlePublished
All you need to do is, adjust via()
method, and create a new toFacebookPoster()
method, which will be called via FacebookPosterChannel.php class, view the complete class below.
app\Notifications\ArticlePublished.php
<?php
namespace App\Notifications;
use App\Blog;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use NotificationChannels\FacebookPoster\FacebookPosterChannel;
use NotificationChannels\FacebookPoster\FacebookPosterPost;
/**
* Class ArticlePublished
* @package App\Notifications
*/
class ArticlePublished extends Notification
{
use Queueable;
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [FacebookPosterChannel::class];
}
/**
* @param $blog
*/
public function toFacebookPoster($blog)
{
return with(new FacebookPosterPost($blog->title))
->withLink($blog->getLink());
}
}
Now, most interesting part is making the process automated. I'm creating an event listener to stay ready to fire up a notification when a new blog is stored in the database.
If you want to discover the supported methods, visit the github repository for the package.
You could either fire the notification via the controller after the blog post is created:
/**
* Create a new blog instance.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// Validate the request...
$blog = Blog::create([
'title' => $request->title,
'slug' => $request->slug
]);
$blog->notify(new ArticlePublished);
}
Another better way is listening to the model events, the cleaner way I like and the way I follow every single day.
Observing Model
Create a new event subscriber class under, app/Listeners
call it BlogEventSubscriber.php
<?php
namespace App\Listeners;
use App\Notifications\ArticlePublished;
/**
* Class BlogEventSubscriber
* @package App\Listeners
*/
class BlogEventSubscriber
{
/**
* Handle blog creating events.
*
* @param $blog
*/
public function onCreated($blog)
{
$blog->notify(new ArticlePublished());
}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'eloquent.created: App\Blog',
'App\Listeners\BlogEventSubscriber@onCreated'
);
}
}
We need to register this class in, app/Providers/EventServiceProvider.php
/**
* The subscriber classes to register.
*
* @var array
*/
protected $subscribe = [
'App\Listeners\BlogEventSubscriber',
];
Development Issue
I tried to send the localhost link along with Facebook post but it throws an exception while sending localhost URL, so be aware of that.
public function toFacebookPoster($blog)
{
return with(new FacebookPosterPost($blog->title))
->withLink($blog->getLink());
}
We are done!
Conclusion
Thanks for reading this article up to the end, if you have any feedback regarding the post please feel free to leave your comment below. The code snippets are fully tested, and the full source is available in the github repository.
]]>Below, I will cover the processes that are needed on facebook end to collect the required credentials to make the API connectivity successful.
The main credentials needed are, app_id
, app_secrect
, access_token
for my use case.
We can easily get the app_id
, app_secrect
after creating the app, but to get the long-lived page access token there are some steps to follow.
Facebook gives short-lived, long-lived access tokens. I prefer to generate long-lived access token just to get rid of the expiry date and monitoring effort required for it.
I will cover all the steps we need to follow to get the never expiring page access token.
Steps
1. Sign up for facebook developers channel.
2. Create a Facebook App or use an existing app if you already have one.
3. Copy, App ID, App Secret
4. Now, navigate to Facebook Graph API Explorer, to generate short-lived access token.
a. Firstly, Select an app from Application drop-down.
b. Again, in the next drop-down select "Get user access token".
c. Once you click on “Get user access token in the drop down” you will see a pop-up window like below. There you will be able to select the permission(scopes) for the user access token.
d. Here for my use case I have only selected “publish pages” and “manager pages” permissions which are needed to create never expiring page access token. If would like to understand more about the scopes visit permissions doc.
Generate user access token by clicking on "Get Access Token" button, this will create short-lived user access token.
5. Now we will need to create a long-lived user access token.
Now, navigate to Access Token Tool this page, you will see short-lived user access token, app access token for all the apps you have under your account.
Press debug option at right side for the user access token for the current app we are trying to create a long-lived access token. This will again take you to Access Token Debugger where you will see the full information for the short-lived user access token.
Short-lived user access token means that will expire after an hour. So to extend the expiry date, we need to go to the bottom, there is an option to generate long-lived(2 months) user access token. Click on “Extend Access Token” and you will get the long-lived access token.
6. Lastly, creating never expiring Access Token.
Again, go to Graph API Explorer and paste the recently created long-lived user access token in the Access Token field.
Now, we need to change the API endpoint to access “/me/accounts” and click on "Submit" button. This process will show the result with all pages information including page access token i.e never expiring token.
7. Finally, we are at the verification step to make sure the page access token is never expiring.
Hurray!
Last Updated - May 24, 2018 : The process I have explained above also works for new version v3.0 of Facebook Graph API
Conclusion
Thanks for reading this article up to the end, if you have any feedback regarding the post please feel free to leave your comment below.
Happy Coding!
]]>In this article, I am going to give an overview of how to speed up your composer update process. I recently used and experimented the process, so I wanted to share with everyone who wants to utilize this speedy way.
There are only two methods, I know and I did experiment with.
Disabling the Xdebug mode
Installing a third-party library prestissimo created by @hirak
If you want to check your xdebug mode, when running composer you can follow this StackOverflow link.
Another method is using a global composer plugin prestissimo, which is blazingly faster, this plugin helps to installs dependencies in parallel mode.
Follow the below command to install the composer plugin globally on your machine.
Install
composer global require hirak/prestissimo
Uninstall
composer global remove hirak/prestissimo
After installing the composer plugin globally you are done, next is to go to your terminal and hit composer update and test the speed.
I am personally using this composer plugin to make my composer process faster. I highly recommend you to try it. I haven't run into any problems using it at all. If you found any issues let people know about it, so feel free to leave your comment below.
In my case the composer update used to take around 10 minutes to finish, now after utilizing this package, it turned into 1 min and less, which is blazingly faster for me, a lot of time-saving while waiting for a package to install and move to code works.
Thanks for reading this article, If you like feel free to share with other people in your circle to let them know about the above.
Enjoy !
]]>I was talking about the user experience while browsing the pages, the behavior for the end-user might differ to each other, but the website owner should make a policy for the better user experience. Especially, the e-commerce websites should consider this types of UI for their end-user.
Let's say you are running an e-commerce website, where people can view products, create orders. Let's assume you want the website easily navigates back the user to the previous page after logging into the website.
Let's start building the feature for redirecting the user back to the previous page.
Let's add a section in the login file.
resources/views/auth/login.blade.php
<div class="form-group">
@if (Request::has('previous'))
<input type="hidden" name="previous" value="{{ Request::get('previous') }}">
@else
<input type="hidden" name="previous" value="{{ URL::previous() }}">
@endif
</div>
View the login.blade.php file on github for the full updated code.
Additionally, you might want to add a login link to your website pages like:
<a href="{{ route('login') . '?previous=' . Request::fullUrl() }}">
Login
</a>
When a user clicks the login link, the page redirects to the login page, it then sets the previous
URL to input that will be sent to login POST
request.
/**
* @return string
*/
public function redirectTo()
{
if ($this->request->has('previous')) {
$this->redirectTo = $this->request->get('previous');
}
return $this->redirectTo ?? '/home';
}
The LoginController.php will handle the previous input value to check if there is the previous link to redirect back. A method redirectTo()
created in LoginController.php is responsible to determine the next redirect path after the user is successfully authenticated.
If you're not familiar with Laravel's auth system and confused that only defining the new redirectTo()
method helps in redirecting to our intended URL, then you can have a look at, AuthenticatesUsers.php
, RedirectsUsers.php
. We cannot touch the core files shipped with Laravel framework, but there is a way we can override those methods to change the behavior as per our requirement.
Conclusion
Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>Medium – Read, write and share stories that matter.
Medium is an online publishing platform developed by Evan Williams, and launched in August 2012. It is owned by A Medium Corporation
Here I'm going to show how I implemented a medium style clean image zoom-in, zoom-out feature for your website. If you would like to see the medium style image zoom-in, out feature on my blog that I implemented recently with the new design published last week, visit some articles to try out the feature. for example: here is a post with few images, have a look at this blog post.
Before diving into the code, I would like to say big thanks to the package creator @francoischalifour, who built this awesome package as open source.
The package is available on the npm registry, with no dependencies.
Features
Installation
npm install --save medium-zoom
# or
yarn add medium-zoom
Or, If you want to use the CDN version:
<script src="https://unpkg.com/medium-zoom@0/dist/medium-zoom.min.js"></script>
Usage
Import the script:
<script src="node_modules/medium-zoom/dist/medium-zoom.min.js"></script>
Or, using the module syntax or imports:
const mediumZoom = require('medium-zoom')
// or
import mediumZoom from 'medium-zoom'
You don't need to worry about the CSS styling imports.
Integration
mediumZoom(<selector>, <options>)
In my case, I wanted to use medium style zoom, in-out feature for every image I add to the blog post. i.e It is only applied to the post detail page, where the user wants to view the image in zoom, in-out mode. I wanted my readers to enjoy the view of images without having to open it on a new tab to understand it clearly.
mediumZoom('.post__container img');
By default, the zoom is applied to all scaled images (with HTML or CSS properties). You can specify the zoomable images with a CSS selector and add options.
Additionally, you can pass an HTML Element, a NodeList, an HTMLCollection or an array of images to the plugin.
// CSS selector
mediumZoom('#cover')
// HTML Element
mediumZoom(document.getElementById('cover'))
// NodeList
mediumZoom(document.querySelectorAll('[data-action="zoom"]'))
// HTMLCollection
mediumZoom(document.images)
// Array
const imagesToZoom = [
document.querySelector('#cover'),
...document.querySelectorAll('[data-action="zoom"]')
]
mediumZoom(imagesToZoom)
API
Options can be passed via a JavaScript object through the mediumZoom
call.
Open up the github repo and go through the API options available if you wish to add the customized feature for your web application.
Go through the examples section if you wish to understand the complete possible features provided by the package.
Conclusion
Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>In this blog post, I will go in detail to cover following stuff.
I will be using Laravel v5.5 to build this feature. Also, for user's device information, IP to location and to generate a unique token for every new request I will be using my own two PHP packages published on packagist as open source, and browser detection library by @cbschuld. So make sure you have a composer, PHP 7.0 >= installed in your environment.
Package Installation
You can install those packages via composer.
composer require sudiptpa/guid
composer require sudiptpa/ipstack
Now let's start with creating the foundation of the feature to implement with Laravel project.
app/routes/web.php
Route::group(['middleware' => ['authorize', 'auth']], function () {
Route::get('/dashboard', [
'name' => 'Dashboard',
'as' => 'dashboard',
'uses' => 'HomeController@dashboard',
]);
});
Route::group(['middleware' => ['auth']], function () {
Route::get('/authorize/{token}', [
'name' => 'Authorize Login',
'as' => 'authorize.device',
'uses' => 'Auth\AuthorizeController@verify',
]);
Route::post('/authorize/resend', [
'name' => 'Authorize',
'as' => 'authorize.resend',
'uses' => 'Auth\AuthorizeController@resend',
]);
});
The first grouped route shows, that the user cannot access the dashboard without logging in and, needs authorization. I will show the authorization middleware very soon below.
Similarly, the second grouped routes, show that only authenticated users can verify the device with associated IP address.
app/database/migrations
Now let's create a migration table to store the authorizes for all users.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAuthorizesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('authorizes', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->nullable();
$table->boolean('authorized')->nullable();
$table->string('token')->nullable();
$table->string('ip_address')->nullable();
$table->string('browser')->nullable();
$table->string('os')->nullable();
$table->string('location')->nullable();
$table->tinyInteger('attempt')->default(0)->nullable();
$table->timestamp('authorized_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('authorizes');
}
}
app/Authorize.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
/**
* Class Authorize
* @package App
*/
class Authorize extends Model
{
/**
* @var string
*/
protected $table = 'authorizes';
/**
* @var boolean
*/
public $timestamps = true;
/**
* @var array
*/
protected $dates = ['authorized_at', 'deleted_at'];
/**
* @var string
*/
protected $fillable = [
'user_id', 'authorized', 'token', 'ip_address', 'browser', 'os', 'location', 'attempt', 'authorized_at',
];
/**
* @param $query
* @return mixed
*/
public function scopeCurrentUser($query)
{
return $query->where('user_id', Auth::id());
}
/**
* @param $date
*/
public function setAuthorizedAtAttribute($date)
{
$this->attributes['authorized_at'] = Carbon::parse($date);
}
/**
* @return mixed
*/
public static function active()
{
return with(new self)
->where('ip_address', Request::ip())
->where('authorized', true)
->where('authorized_at', '<', Carbon::tomorrow())
->first();
}
/**
* @return mixed
*/
public function resetAttempt()
{
$this->update(['attempt' => 0]);
return $this;
}
/**
* @return mixed
*/
public function noAttempt()
{
return $this->attempt < 1;
}
/**
* @param $token
*/
public static function validateToken($token = null)
{
$query = self::where([
'token' => $token,
])->first();
if (sizeof($query)) {
$query->update([
'authorized' => true,
'authorized_at' => now(),
]);
return self::active();
}
}
/**
* @return mixed
*/
public static function make()
{
return self::firstOrCreate([
'ip_address' => Request::ip(),
'authorized' => false,
'user_id' => Auth::id(),
]);
}
/**
* @return mixed
*/
public static function inactive()
{
$query = self::active();
return $query ? null : true;
}
}
In this model I have written few methods that actually work on authorization, you will see its usage below with middleware, controller.
In this blog post, I am only covering only the dashboard, login routes to be protected from the authorize
middleware. I only forced the LoginController.php to apply on login routes only. Make sure you apply this middleware to other routes to be protected from unauthorized access to your application.
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('authorize')->only('login');
$this->middleware('guest')->except('logout');
}
app/Http/Middleware/AuthorizeDevice.php
<?php
namespace App\Http\Middleware;
use App\Authorize;
use App\Mail\AuthorizeDevice as AuthorizeMail;
use Closure;
use Illuminate\Support\Facades\Mail;
/**
* Class AuthorizeDevice
* @package App\Http\Middleware
*/
class AuthorizeDevice
{
/**
* @var \App\Authorize
*/
private $authorize;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (Authorize::inactive() && auth()->check()) {
$this->authorize = Authorize::make();
if ($this->authorize->noAttempt()) {
Mail::to($request->user())
->send(new AuthorizeMail($this->authorize));
$this->authorize->increment('attempt');
}
if ($this->timeout()) {
auth()->guard()->logout();
$request->session()->invalidate();
return redirect('/')->with([
'status' => 'You are logged out of system, please follow the link we sent before 15 minutes to authorize your device, the link will be valid with same IP for 24hrs.',
]);
}
return response()->view('auth.authorize');
}
return $next($request);
}
/**
* Determines if the authorize attempt is timed out.
*
* @return bool
*/
private function timeout()
{
$waiting = $this->authorize
->created_at
->addMinutes(15);
if (now() >= $waiting) {
return true;
}
return false;
}
}
Now register the middleware within app/Http/Kernel.php
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
...
'authorize' => \App\Http\Middleware\AuthorizeDevice::class,
];
The most interesting part of this blog is the above middleware class that handles the incoming request into an application that enters the actual routes which are protected before the user's device is authorized.
Open up the Authorize.php model from GitHub to understand the full methods about how it is working.
The middleware is before middleware, follow the laravel official doc if you don't know about it.
Actually, it works with the authenticated user's when the device is not authorized, so it protects the user's trying to enter the application without authorization.
The middleware first, checks, if there is an authorization, exists in the database or creates a new with the current IP address for currently logged in user.
It sends the email requesting authorization to access the application, see below for the simple preview I made while writing this blog post.
The middleware is also handling the session timeout for the authorization to be made within next 15 min after the email has been sent, otherwise, it will log out the user automatically.
If you wish to manage the separate middleware for the timeout feature you could use another class, and register it accordingly like we did above.
Alright, now let's move to email sending code.
app/Mail/AuthorizeDevice.php
<?php
namespace App\Mail;
use App\Browser;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Sujip\Ipstack\Ipstack;
/**
* Class AuthorizeDevice
* @package App\Mail
*/
class AuthorizeDevice extends Mailable
{
use Queueable, SerializesModels;
/**
* @var mixed
*/
protected $authorize;
/**
* Create a new message instance.
*
* @param $authorize
* @return void
*/
public function __construct($authorize)
{
$this->authorize = $authorize;
$this->browser = new Browser;
}
/**
* @return mixed
*/
public function setBrowser()
{
$this->authorize->browser = $this->browser->getBrowser();
return $this;
}
/**
* @return mixed
*/
public function setToken()
{
$this->authorize->token = guid();
return $this;
}
/**
* @return mixed
*/
public function setLocation()
{
$location = with(new Ipstack(
$this->authorize->ip_address
))->formatted();
$this->authorize->location = $location;
return $this;
}
/**
* @return mixed
*/
public function setPlatform()
{
$this->authorize->os = $this->browser->getPlatform();
return $this;
}
public function saveAuthorize()
{
$this->authorize->save();
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$this
->setBrowser()
->setToken()
->setLocation()
->setPlatform()
->saveAuthorize();
return $this
->view('emails.auth.authorize')
->with(['authorize' => $this->authorize]);
}
}
The mail sending class prepares data like location, IP, token, browser, platform(os) and email sending view. In order to collect the user's current device information, I am using a third-party class written by @cbschuld as open source code in GitHub. I would like to say thanks for the nice package to him.
Also, to generate the token, I am using a GUID generator package that I wrote recently for my own use case and published as opensource as well. The guid()
helper function will return global unique identifier every time a user requires a token to authorize the device.
The coolest feature in the Laravel 5.5 Illuminate\Mail\Mailable
is mail preview to test the email view before sending it. I loved it. :)
See example below how I tested myself.
Route::get('/mailable', function () {
$authorize = App\Authorize::find(1);
return new App\Mail\AuthorizeDevice($authorize);
});
After sending the email requesting you to authorize the device, the preview I built was like below, you are free to apply CSS
as per your use case.
The AuthorizationController.php was written to handle, resend authorize email, and validate the token from the email sent to the current user.
<?php
namespace App\Http\Controllers\Auth;
use App\Authorize;
use App\Http\Controllers\Controller;
use App\Mail\AuthorizeDevice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redirect;
/**
* Class AuthorizeController
* @package App\Http\Controllers\Auth
*/
class AuthorizeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Validate the token for the Authorization.
*
* @param $token
* @return \Illuminate\Http\Response
*/
public function verify($token = null)
{
if (Authorize::validateToken($token)) {
return Redirect::route('dashboard')->with([
'status' => 'Awesome ! you are now authorized !',
]);
}
return Redirect::route('login')->with([
'error' => "Oh snap ! the authorization token is either expired or invalid. Click on Email didn't arraive ? again",
]);
}
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function resend(Request $request)
{
if (Authorize::inactive() && auth()->check()) {
$authorize = Authorize::make()
->resetAttempt();
Mail::to($request->user())
->send(new AuthorizeDevice($authorize));
$authorize->increment('attempt');
return view('auth.authorize');
}
}
}
View the auth/authorize.blade.php from github to know how it was like.
View the complete code pushed to github while I was writing this blog post.
Conclusion
Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>I've abandoned the legacy package sudiptpa/geoip as the service provider discontinued on July 1st, 2018.
I was working on my own use case to find out IP to Geo Location and finally found a free open source service from http://freegeoip.net. It is a community funded project. The service includes GeoLite2 data created by MaxMind
freegeoip.net provides a public HTTP API for software developers to search the geolocation of IP addresses. It uses a database of IP addresses that are associated with cities along with other relevant information like time zone, latitude, and longitude.
You're allowed up to 15,000 queries per hour by default. Once this limit is reached, all of your requests will result in HTTP 403, forbidden, until your quota is cleared.
The freegeoip web server is free and open source so if the public service limit is a problem for you, download it and run your own instance.
API support.
The HTTP API takes GET requests in XML, JSON, CSV format.
http://freegeoip.net/{format}/{IP_or_hostname}
While making an API call, if no IP or hostname is provided, then your own IP is looked up.
Package Implementation
To make this API call simple and organized I decided to create a package for my own use case as well as for the community people to use it as open source. It is available to view it on Github.
Installation
You can install the package via composer.
composer require sudiptpa/geoip
Usage
This package only supports json
format for now.
Here are a few examples on how you can use the package:
$geo = new Sujip\GeoIp\GeoIp($ip);
$geo->country();
$geo->city();
$geo->region();
$geo->formatted(); // Jawalakhel, Central Region, Nepal
Also, have a look at the source code of Sujip\GeoIp\GeoIp
to discover the methods you can use via GitHub.
Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>I will be using Laravel framework to demonstrate this integration. So before starting, I will assume you have laravel framework setup, facebook account ready to use in facebook developers mode.
At the end of the post, you will get the below output.
Now let's start with creating the basic foundation to show the blog posts and view it.
app/routes/web.php
Route::group(['prefix' => 'blog'], function () {
Route::get('/', [
'as' => 'app.blog',
'uses' => 'BlogController@index',
]);
Route::get('/{slug}', [
'as' => 'app.blog.view',
'uses' => 'BlogController@view',
]);
});
app/database/migrations
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBlogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('slug');
$table->string('excerpt');
$table->longText('content')->nullable();
$table->timestamp('published_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('blogs');
}
}
app/Blog.php
<?php
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* Class Blog
* @package App
*/
class Blog extends Model
{
/**
* @var string
*/
protected $table = 'blogs';
/**
* @var array
*/
protected $dates = ['published_at'];
/**
* @var array
*/
protected $fillable = ['title', 'slug', 'excerpt', 'content', 'published_at'];
/**
* @param $query
* @return mixed
*/
public function scopePublished($query)
{
return $query->where('published_at', '<=', Carbon::now());
}
/**
* @param $date
*/
public function setPublishedAtAttribute($date)
{
$this->attributes['published_at'] = Carbon::parse($date);
}
}
app/Http/Controllers/BlogController.php
<?php
namespace App\Http\Controllers;
use App\Blog;
use Illuminate\Http\Request;
/**
* Class BlogController
* @package App\Http\Controllers
*/
class BlogController extends Controller
{
/**
* @var int
*/
protected $paginate = 10;
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
public function index()
{
$blogs = Blog::published()->paginate(
$this->paginate
);
return view('blog.index', [
'blogs' => $blogs,
'title' => 'Blogs',
'open_graph' => [
'title' => 'Blogs',
'image' => asset('assets/logo.jpeg'),
'url' => $this->request->url(),
'description' => 'A blog website to share tutorials and tips !',
'keywords' => 'A Laravel Blog, Tips, Tutorials',
],
]);
}
/**
* @param $slug
*/
public function view($slug)
{
$blog = Blog::published()
->where('slug', $slug)
->firstOrFail();
return view('blog.view', [
'blog' => $blog,
'open_graph' => [
'title' => $blog->title,
'image' => asset('assets/preview.jpeg'),
'url' => $this->request->url(),
'description' => $blog->excerpt,
],
]);
}
}
Now, I will assume you have following code in your layout blade in your view folder. See how I have created my layout file here for this tutorial.
<!DOCTYPE html>
<html lang="en">
<head>
....
....
....
@yield('head')
</head>
<body>
Open up the file form Github repository index.blade.php
Similarly, open up view.blade.php
The above integration will show the following output.
Blogs
Blog Preview
Now, let's navigate to facebook developers doc.
Scroll down to - Like Button Configurator
You are free to configure the URL to Like, Width, Layout, Action Type, Button Size, Show Friends's Faces, Include Share Button options as per your wish.
After completing the above configuration click on Get Code button. It will show a pop up window like below. Don't forget to read the information shown in the pop up window carefully. The share feature requires open graph meta tags on heads section of your html page, so have a look at the code to know how I implemented it.
There are two different way of implementing the facebook real-time like, share buttons.
Javascript SDK
Step 1: Include the JavaScript SDK on your page once, ideally right after the opening body
tag.
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.11&appId=180539309049634';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
Don't forget to replace the appId=180539309049634
with your own app. Select your facebook app before generating the code. If you don't have any app for your site, create one from facebook developers page under My App.
Step 2: Place this code wherever you want the plugin to appear on your page.
<div class="fb-like" data-href="{{ route('app.blog.view', ['slug' => $post->slug]) }}" data-layout="standard" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div>
IFrame
<iframe src="https://www.facebook.com/plugins/like.php?href={{ route('app.blog.view', ['slug' => $post->slug]) }}&width=51&layout=box_count&action=like&size=small&show_faces=true&share=true&height=65&appId" width="51" height="65" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
Place this code wherever you want the plugin to appear on your page.
Now, look at the index.blade.php, view.blade.php on my Github repo about how I implemented the code to show the facebook real-time like, share buttons.
View the complete code pushed to github while I was writing this tutorial.
Finally! now we have a complete guide setup for integrating facebook, real-time like, share buttons.
Conclusion
Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>Begin with installation via Composer:
composer require sudiptpa/guid
I was inspired mainly to create this package while I was searching for GUID information in the php.net official PHP website. I found a good solution by a community member Dave Pearson, view source code here.
I was looking a solution for Laravel, so I created this package as reusable for everyone as an opensource package.
Usage
In order to consume this package from Laravel application, register the package service provider within your config/app.php
file.
'providers' => [
Sujip\Guid\GuidServiceProvider::class,
]
'aliases' => [
'Guid' => 'Sujip\Guid\Guid'
]
If your Laravel application is running with v5.5
and higher version, this package also has support for package auto-discovery, Laravel will automatically register its service providers and facades when it is installed, creating a convenient installation experience for you.
If you are a Laravel Developer:
Make use of Laravel Facade:
echo "GUID: " . Guid::create(); //example output : 2b23924f-0eaa-4133-848e-7ce1edeca8c9
or use a helper function guid()
echo "GUID: " . guid(); // example output: 2b23924f-0eaa-4133-848e-7ce1edeca8c9
If you want to use this package outside of the framework.
use Sujip\Guid\Guid;
$guid = new Guid;
$guid = $guid->create();
Output
//Example: 2b23924f-0eaa-4133-848e-7ce1edeca8c9
Conclusion
This package is framework agnostic, which means it can be used in any type of PHP projects.
Thanks for reading up to the end. If you have any feedback regarding this blog post feel free to leave your comment below. Also don't forget to share with friends if you like it.
Happy Coding!
]]>Before diving into the code, let's understand why the preferred domain is much important for SEO, web pages indexing on Search Engine.
Follow the link to read in detail from Google support article.
The preferred domain is the one that you would like used to index your website's pages (it is sometimes referred to as the canonical domain). Links may point to your site using both the www and non-www versions of the URL (for instance, http://www.example.com and http://example.com). The preferred domain is the version that you want to be used for your site in the search results.
Here, in this guide, I'm going to implement non-www
preferred domain for every incoming HTTP request to the application. I have created a custom Laravel Middleware to set the preferred domain.
People usually do this with server configuration, (eg: with .htaccess for apache), but you can also force it from application context. I have used this mechanism with several Laravel applications I have built.
Let's start with creating a middleware with an artisan command.
php artisan make:middleware PreferredDomain
Open up your app/Http/Middleware/PreferredDomin.php
and paste code below.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Redirect;
/**
* Class PreferredDomain
* @package App\Http\Middleware
*/
class PreferredDomain
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (starts_with($request->header('host'), 'www.')) {
$host = str_replace('www.', '', $request->header('host'));
$request->headers->set('host', $host);
return Redirect::to($request->fullUrl(), 301);
}
return $next($request);
}
}
We want to run this middle over every HTTP request that comes to our application, simply we need to register the middleware class in the $middleware
property in app/Http/Kernel.php
class.
protected $middleware = [
...
\App\Http\Middleware\PreferredDomain::class,
];
Note: If you're still using v4.2 version of Laravel don't worry I have solution for you too as well ;)
Open up your app/filters.php
App::before(function ($request) {
if (starts_with($request->header('host'), 'www.')) {
$host = str_replace('www.', '', $request->header('host'));
$request->headers->set('host', $host);
return Redirect::to($request->fullUrl(), 301);
}
});
Conclusion
This tutorial heavily depends on Laravel framework, I primarily use Laravel to build my projects.
Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.
Last Updated: 21st Jan, 2018
Happy Coding!
]]>If you are new to my blog, view my previous post about Laravel v5.4 authentication with username or email.
In my previous blog post, people were requesting to cover register, login. So, here I am going to cover both process with new version v5.5 just released at the end of August 2017.
Let's start the tutorial :)
Before starting to look at the code you should be ready with running v5.5 or fresh installation setup on your machine.
If you need a guide for the installation process see the release note and follow the official guide.
Let's begin with creating authentication scaffolding, migrations for User
model.
Generate authentication scaffolding with an artisan command.
php artisan make:auth
As we are going to add username
field on the database.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('username')->nullable()->unique();
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
Now simply run migration and go ahead.
php artisan migrate
After running this command your database table is now ready with username field support.
Now, open up your User
model, add the username
field to the $fillable
array.
protected $fillable = [
'name', 'email', 'password', 'username',
];
Open up your RegisterController.php
, I have added a validation rule for the username
in validator()
method. Similarly, I have added username
field to create()
method as well.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'username' => 'required|string|max:20|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'username' => $data['username'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
Also, I have changed a little bit on resources/views/auth/register.blade.php
to support username, email. So, simply add username field on it.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Register</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('register') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Name</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="help-block">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('username') ? ' has-error' : '' }}">
<label for="username" class="col-md-4 control-label">Username</label>
<div class="col-md-6">
<input id="username" type="text" class="form-control" name="username" value="{{ old('username') }}" required>
@if ($errors->has('username'))
<span class="help-block">
<strong>{{ $errors->first('username') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Register
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
You're done with the registration process. Now let's look at the login process.
Open up Illuminate\Foundation\Auth\AuthenticatesUsers it has several methods to handle authentication process. In our customization we only need to override one method credentials()
to our App\Http\Controllers\Auth\LoginController.php
Also, don't forget to add an import use Illuminate\Http\Request;
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
$field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
? $this->username()
: 'username';
return [
$field => $request->get($this->username()),
'password' => $request->password,
];
}
}
You will require changing the login form to support username, email field, simply open up resources/views/auth/login.blade.php
and just change input type to be text from email, just leave other as it is.
<input id="email" type="text" class="form-control" name="email" value="{{ old('email') }}" required autofocus>
Alright ! now we have a full functionality to support login, register with username, email with Laravel v5.5.
Conclusion
This tutorial heavily depends on Laravel 5.5, so if you are going to implement this on another version of framework please have a look at the traits used properly to override the credentials array. If you need guide about v5.4 view the previous post here.
Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>Omnipay libraries are stable, consistent, and fully united tested by the developers and contributors around the globe. I would like to thank them for their great effort on these awesome open source packages.
Omnipay libraries are really easy to understand and integrate with any framework or non-framework based projects.
Also, have a look at their Github repository to discover the supported gateways you want to integrate with your project.
You can simply pull the packages which are hosted freely on packagist by using composer.
In this blog post, I will go step by steps to integrate the popular library for PayPal express checkout with omnipay/omnipay-paypal.
I am going to show the steps with laravel framework v5.4.
Before getting started with the guidelines you will need the following stuff ready for your machine.
After successfully installing the laravel on your machine.
Important Note
If you are using Symfony 3 or Symfony 3 components, or laravel v5.* which uses Symfony 3 components please note that Omnipay 2.* version still uses guzzle 3.*, which depends on symfony/event-dispatcher 2.*. It conflicts with Symfony 3 in the regular installation. For this fix, we may need to wait until Omnipay 3.* release, which is still under development.
For the alternative fix, go to the command line cd to your project root to force the installation of symfony/event-dispatcher ^2.8 which is fully compatible with both Symfony 3 components and guzzle 3.*.
composer require symfony/event-dispatcher:^2.8
Go to `composer.json` and put the following.
{
"require": {
"omnipay/paypal": "~2.0"
}
}
or
composer require omnipay/paypal
Now let's create some example route to handle the requests and Controller and Model for it.
app/routes/web.php
<?php
Route::get('/{order?}', [
'name' => 'PayPal Express Checkout',
'as' => 'app.home',
'uses' => 'PayPalController@form',
]);
Route::post('/checkout/payment/{order}/paypal', [
'name' => 'PayPal Express Checkout',
'as' => 'checkout.payment.paypal',
'uses' => 'PayPalController@checkout',
]);
Route::get('/paypal/checkout/{order}/completed', [
'name' => 'PayPal Express Checkout',
'as' => 'paypal.checkout.completed',
'uses' => 'PayPalController@completed',
]);
Route::get('/paypal/checkout/{order}/cancelled', [
'name' => 'PayPal Express Checkout',
'as' => 'paypal.checkout.cancelled',
'uses' => 'PayPalController@cancelled',
]);
Route::post('/webhook/paypal/{order?}/{env?}', [
'name' => 'PayPal Express IPN',
'as' => 'webhook.paypal.ipn',
'uses' => 'PayPalController@webhook',
]);
In my projects I prefer writing named routes, we can simply use route()
helper from our views. You can change the above web.php routes to your application specific standard URLs.
app/Order.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Order
* @package App
*/
class Order extends Model
{
/**
* @var string
*/
protected $table = 'orders';
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}
In the Order.php model, I am just covering basic requirements for all type projects. You are free to change and assign them in your application with the relevant type of database fields.
resources/views/form.blade.php
@extends('app')
@section('content')
<div class="container">
<div class="gateway--info">
<div class="gateway--desc">
@if(session()->has('message'))
<p class="message">
{{ session('message') }}
</p>
@endif
<p><strong>Order Overview !</strong></p>
<hr>
<p>Item : Yearly Subscription cost !</p>
<p>Amount : ${{ $order->amount }}</p>
<hr>
</div>
<div class="gateway--paypal">
<form method="POST" action="{{ route('checkout.payment.paypal', ['order' => encrypt(mt_rand(1, 20))]) }}">
{{ csrf_field() }}
<button class="btn btn-pay">
<i class="fa fa-paypal" aria-hidden="true"></i> Pay with PayPal
</button>
</form>
</div>
</div>
</div>
@stop
In the traditional applications, people used to set values on forms and submit the data. I personally don't prefer that. In the above form.blade.php I have created a form action with a route() helper and encrypted my order id to make it safe.
app/config/paypal.com
<?php
return [
'credentials' => [
'username' => env('PAYPAL_USERNAME'),
'password' => env('PAYPAL_PASSWORD'),
'signature' => env('PAYPAL_SIGNATURE'),
'sandbox' => env('PAYPAL_SANDBOX')
],
];
If you wish to store the API credentials in the database that is your choice, for the example I have created a config file. So replace the xxx characters with your own credentials.
app/PayPal.php
<?php
namespace App;
use Omnipay\Omnipay;
/**
* Class PayPal
* @package App
*/
class PayPal
{
/**
* @return mixed
*/
public function gateway()
{
$gateway = Omnipay::create('PayPal_Express');
$gateway->setUsername(config('paypal.credentials.username'));
$gateway->setPassword(config('paypal.credentials.password'));
$gateway->setSignature(config('paypal.credentials.signature'));
$gateway->setTestMode(config('paypal.credentials.sandbox'));
return $gateway;
}
/**
* @param array $parameters
* @return mixed
*/
public function purchase(array $parameters)
{
$response = $this->gateway()
->purchase($parameters)
->send();
return $response;
}
/**
* @param array $parameters
*/
public function complete(array $parameters)
{
$response = $this->gateway()
->completePurchase($parameters)
->send();
return $response;
}
/**
* @param $amount
*/
public function formatAmount($amount)
{
return number_format($amount, 2, '.', '');
}
/**
* @param $order
*/
public function getCancelUrl($order)
{
return route('paypal.checkout.cancelled', $order->id);
}
/**
* @param $order
*/
public function getReturnUrl($order)
{
return route('paypal.checkout.completed', $order->id);
}
/**
* @param $order
*/
public function getNotifyUrl($order)
{
$env = config('paypal.credentials.sandbox') ? "sandbox" : "live";
return route('webhook.paypal.ipn', [$order->id, $env]);
}
}
I prefer separation of concerns, in the example, I have a new class PayPal.php
as a helper class for PayPalController.php
I think many people also like the clean code in the controller rather than over-complicating things on Controllers.
app/Http/Controllers/PayPalController.php
<?php
namespace App\Http\Controllers;
use App\Order;
use App\PayPal;
use Illuminate\Http\Request;
/**
* Class PayPalController
* @package App\Http\Controllers
*/
class PayPalController extends Controller
{
/**
* @param Request $request
*/
public function form(Request $request, $order_id = null)
{
$order_id = $order_id ?: encrypt(1);
$order = Order::findOrFail(decrypt($order_id));
return view('form', compact('order'));
}
/**
* @param $order_id
* @param Request $request
*/
public function checkout($order_id, Request $request)
{
$order = Order::findOrFail(decrypt($order_id));
$paypal = new PayPal;
$response = $paypal->purchase([
'amount' => $paypal->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $paypal->getCancelUrl($order),
'returnUrl' => $paypal->getReturnUrl($order),
]);
if ($response->isRedirect()) {
$response->redirect();
}
return redirect()->back()->with([
'message' => $response->getMessage(),
]);
}
/**
* @param $order_id
* @param Request $request
* @return mixed
*/
public function completed($order_id, Request $request)
{
$order = Order::findOrFail($order_id);
$paypal = new PayPal;
$response = $paypal->complete([
'amount' => $paypal->formatAmount($order->amount),
'transactionId' => $order->id,
'currency' => 'USD',
'cancelUrl' => $paypal->getCancelUrl($order),
'returnUrl' => $paypal->getReturnUrl($order),
'notifyUrl' => $paypal->getNotifyUrl($order),
]);
if ($response->isSuccessful()) {
$order->update(['transaction_id' => $response->getTransactionReference()]);
return redirect()->route('app.home', encrypt($order_id))->with([
'message' => 'You recent payment is sucessful with reference code ' . $response->getTransactionReference(),
]);
}
return redirect()->back()->with([
'message' => $response->getMessage(),
]);
}
/**
* @param $order_id
*/
public function cancelled($order_id)
{
$order = Order::findOrFail($order_id);
return redirect()->route('app.home', encrypt($order_id))->with([
'message' => 'You have cancelled your recent PayPal payment !',
]);
}
/**
* @param $order_id
* @param $env
*/
public function webhook($order_id, $env)
{
// to do with next blog post
}
}
The above implementation only covers, make a payment, handle failed, canceled the payment. I have only shown basic stuff to get up and running, you are free to update as per your application requirements.
Thanks for reading up on the end!
If you have any feedback, any typo mistake in the post above please feel free to leave your comments below.
If you want to view the overall code on Github follow this link.
For the PayPal Instant Payment Notification support, please follow the link here to read my another blog post.
Note: The team from Omnipay recently released the new version v3.0 with support for symfony 3,4 components. Also, I published a new fresh blog post to explain the new changes in v3.0 with complete integration guide.
Happy Coding!
]]>From this tutorial, we want to customize the default auth system in our Laravel 5.4 application to allow users to sign in using their username or email address. As of now Laravel framework only supports sign in with email only. After going through this tutorial your application will be able to allow sign in with both username or email, also you can easily extend this concept to any other version of Laravel framework.
Before moving to start the tutorial you should be ready with Laravel 5.4 setup on your machine.
Let's begin with creating authentication scaffolding, migrations for User
model.
Run below command to generate authentication scaffolding.
php artisan make:auth
If you already have experience with Laravel, then you may know about auth system, which ships with framework out of the box.
If you want to start from scratch, delete the user's table from database/migrations
directory and hit the following command to generate new migration file for the user's table.
php artisan make:migration create_users_table --create="users"
Now, we are adding username field to migration file.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('username')->unique();
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
Let's migrate the database and go ahead.
php artisan migrate
Now let's look at LoginController.php
which implements AuthenticatesUsers.php
trait. which also ships with framework out of the box.
If you look at the trait, which includes several methods only related to authenticating the user into the application. Now its time to deal with our actual requirement.
I am going to override the credentials()
methods from AuthenticatesUsers.php
trait to LoginController.php
Now, my LoginController.php
looks like below.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'logout']);
}
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
$field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
? $this->username()
: 'username';
return [
$field => $request->get($this->username()),
'password' => $request->password,
];
}
}
Also, I changed few things in my login.blade.php
, the email input field to be the text for username support.
<input type="text" class="form-control" name="email" placeholder="E-Mail / Username" value="{{ old('email') }}" required autofocus>
Okay! now we have a functionality to allow login with username or email.
Last Updated: 10th, September 2017
If you would like to view updated new blog post to cover login/register with Laravel v5.5 here.
Conclusion
This tutorial heavily depends on Laravel 5.4, so if you are going to implement this on another version of framework please have a look at the traits used properly to override the credentials array.
Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>Keep in mind, this error occured while I was using phpunit, version line five.
composer global require "phpunit/phpunit"
Lately, I was working on my open source project on Github, which is an Omnipay based PHP Library for NAB Transact, and stuck with the Class PHPUnit_Util_DeprecatedFeature_Logger PHP Fatal error.
This post is to help someone else that tries it – and comes across the same issue like me.
PHP Fatal error: Class PHPUnit_Util_DeprecatedFeature_Logger contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PHPUnit_Framework_TestListener::addRiskyTest) in …vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php on line 202
My phpunit.xml.dist file was like below.
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
colors="true"
syntaxCheck="false">
./tests/
./src
My fix was simple – it took some systematic editing of the phpunit.xml.dist file to figure it out.
The actual fix was simple – the problematical line for me was:
colors = "false"
I’ve said for a long time that “you don’t get paid the big bucks for knowing what to do – it’s for knowing how to fix it when you make the inevitable screw-ups”.
Now, when I run my PHPUnit tests it is working normally.
Thanks for reading up to the end.
Happy Coding!
]]>The v4 line of Laravel is already deprecated, but a lot of developers around the world are still using it and haven’t upgraded to the latest releases. This recent release allows them to move to PHP 7.0 and take new advantage of its speed improvements as well as security fixes without having to upgrade their app's code.
Ideally, those who are still on v4 version have plans to upgrade to v5 but the Laravel team understands that not every business has the luxury of being on the latest and greatest at all times.
There will always be an exception in their company to their typical policy of no new features on old versions of applications.
Post Credit: The original post was published on https://laravel-news.com and wanted to write few links to refer my readers.
Conclusion
Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.
Happy Coding!
]]>Its time to celebrate an open source (#Hacktoberfest) in this October by participating with Hacktoberfest 2016. It is a month-long (festival of code) campaign to spread open source resources, which is organized by DigitalOcean and hosted on GitHub.
Rules for developers
To get a Hacktoberfest 2016 t-shirt, you must create four pull requests, starting October 1 - October 31 2016.
Image Courtesy Github
To participate, simply open a pull request and contribute to any open source project. You can fix a bug, add a feature, or even improve some documentation.
Don't forget to mention #hacktoberfest hashtag on Twitter, Facebook, or Instagram once you've made your pull requests.
After you create atleast four pull requests by October 31st, you'll get the satisfaction of sharing your code with the world—and a t-shirt, of course.
Last Updated : 26th, Oct 2016
Completed the Hacktoberfest challenge!
Happy Coding !
The thing is, sometime with version of PHP on hosting servers, we have to think about the alternative way.
Here with this post I am going to share a pro tip for developers, I learned when i also got stuck with.
Alternative way for to make it working on old version of PHP for method array_column , this method is available only on (PHP 5 >= 5.5.0, PHP 7) see official doc
$array = [
['developer' => ['name' => 'Sujip Thapa', 'age' => 25, 'expericence' => '+3 years']],
['developer' => ['name' => 'Aakash Gurung', 'age' => 25, 'expericence' => '+3 years']],
];
In PHP >=5.5 get all developers name only:
$developers = array_column($array, 'name');
In PHP < 5.5 - alternative way to get all developers name only:
$developers = array_map(function ($each) {
return $each['name'];
}, $array);
If you are a Laravel developer and using v4.2 you also can do like below, see official doc
$developers = array_fetch($array, 'name');
Output will be same:
$developers = ['Sujip Thapa', 'Aakash Gurung'];
Happy Coding !
So adding a way to display the active git branch can help developers avoid the branching problems and code conflict with other developers some time.
In this post, you will get a chance to learn how you can show your active git branch on your Linux terminal on the bash prompt.
Open up your terminal and hit cd ~
to stay on the root. Open up the .bashrc
file from there.
Example:
nano ~/.bashrc
Scroll to the place where you want to paste the content.
Pickup the below snippet and paste into the .bashrc file and save it.
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\u@\h \[\033[32m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "
Then, save the file and exit from the edit mode.
After the update, you need to restart the terminal or source, and it should start working for you.
Happy Coding!
I hope this was helpful for you, and feel free to share it with other developers around you.
Feel free to share the article on social media to share the knowledge among the developers in your circle.
Last Updated: April 26, 2022
]]>
If your answer is Yes, you are at the right place; here in this post, we are writing about the Laravel cheat sheet to quickly pick up the Laravel syntax for different features available in the Laravel ecosystem.
The cheat sheet guide is a complete reference for different versions of the framework.
The cheat sheet guide supports responsive design, so can be viewed in any devices.
Contribute to the project on github - https://github.com/summerblue/laravel5-cheatsheet
Open the cheat sheet - https://summerblue.github.io/laravel5-cheatsheet
Laravel Cheat Sheet - https://learninglaravel.net/cheatsheet/
Laravel Artisan Cheatsheet by @jbrooksuk a developer at Laravel.
Last Updated: 19th Mar, 2022
Happy Coding!
]]>Here, in this blog post, we are going to set up a renowned mail service provider to work your project with low hassle. The mail service provider I'm setting up is Mailgun.
I use Digital Ocean as a primary hosting partner for the Laravel projects I work for my clients. We are going to setup Mailgun with Digital Ocean platform to send emails from the web application. In the previous years, Mailgun used to work without doing any configuration side of things to Digital Ocean end. In the following years, they want us the verify the ownership to use their service with any third party application.
Before following this article, I will assume you have following services available to you if you would like to implement the step by step guide.
Prerequisites
Before starting let's understand some benefits of using Mailgun.
There are a lot of competitors to this service providing the same quality of email services. I am using Mailgun for long with my projects, so due to its simplicity and reliability, I am considering to use it furthermore for now.
We are not going through any large server setup in this article. We are just going to look at the Networking part under the Digital Ocean account to manage a few DNS configuration to make emails stuff work with Mailgun.
Mailgun
After your successful registration with Mailgun service, the first step is to go to the Domains tab. For the fresh new account, you will see one record saying sandbox domain, which allows testing the development phase emails of your application before going to production.
Now, our concern is to setup emails environment for the production application.
Go through the steps below to add a new domain.
Review the following headings on the page based on your need according to your application context.
In this article, we are going to look more in-depth about Domain Verification & DNS.
Before you add a few DNS configuration to the hosting partner end, the Mailgun shows the status of your domain as Unverified.
Note: I am taking Digital Ocean as my hosting partner, but not only Digital Ocean, this article works for any hosting provider which gives you access to manage DNS settings from their user panel based on what type of plan you choose from them.
After completing the above few things in the Mailgun end, we now have to log in into the hosting partner, for me its Digital Ocean.
Digital Ocean
After logging into digital Ocean dashboard, navigate to the Networking tab. It shows you the list of domains attached with droplets you own for your applications.
If you are going to manage the new domain, add it to the list, or select the existing one from the list.
We have to add a few DNS records like TXT, MX, CNAME to verify the Mailgun domain form the Digital Ocean DNS management panel.
Add following records under the Networking > yourdomain.com.
After all setup, you have to lastly go to Mailgun, under Domain Verification & DNS click on Check DNS Record Now, to verify the domain configuration page.
Usage
If you are using Mailgun with Laravel, you have to update your .env file or config/mail.php
to use mailgun driver, and config/services.php
'mailgun' => [
'domain' => 'your-mailgun-domain',
'secret' => 'your-mailgun-api-key',
],
To get the API Key, select the domain from the Domains list, and you will see it on that page.
Testing
To test your production emails, now perform some email sending functionality, the emails log will appear in the Mailgun dashboard as processed, delivered, dropped and so on.
Conclusion
Thanks for reading this article up to the end. If you have any queries on setting up Mailgun, feel free to ask via comment. I will try to reply or even help for other hosting partners including Digital Ocean.
Last Updated: 7th, October 2018
Happy Coding!
]]>sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom
Enjoy!
]]>