Showing posts with label laravel. Show all posts
Showing posts with label laravel. Show all posts

Friday, April 5, 2024

RabbitMQ implementation in Laravel and Linux Centos 8

The use of multiple servers with dedicated functionality can communicate with each other through API and notifications. 

Overview

Example where a backend server that performs a task followed by notification to user via Email, while to another server through PUSH notification.

email and notifications
The push allows communication between 2 computer systems through an agreed protocol. RabbitMQ is a message-queueing software also known as a message broker or queue manager, that provides such a service, implementing protocols AMQP 1.0 and MQTT 5. It is can be used under the Apache License 2.0 and Mozilla Public License 2.

In a simple description on its usage;
  1. A producer: Sends a message to RabbitMQ with a specified exchange (direct, topic, or fanout) and queue(s) name.
  2. RabbitMQ: Places the message to the queue(s).
  3. A consumer: Configured to retrieve message from a queue.
  4. A consumer: Check and retrieve message from the queue.
  5. RabbitMQ: Remove message from the queue
Communication with RabbitMQ can be simplified through the library amqplib that implements the machinery needed to make clients for AMQP 0-9-1.
Messaging queue

Steps
  1. Install RabbitMQ
  2. Configure RabbitMQ
  3. Add RabbitMQ package in a Laravel project
  4. Create a service of Laravel jobs or other automated function
  5. Create Laravel controller to publish and consume messages
  6. Tinker to test Producer
  7. Tinker to test Consumer

Install RabbitMQ

Lets imagine following environment;
  1. RabbitMQ server: IP 10.1.1.101
  2. Producer server: IP 10.1.1.102
  3. Consumer server: IP 10.1.1.103
On RabbitMQ, ensure PHP is installed, including the package socket. On this example, SELINUX is enabled.

On RabbitMQ, install the server application. 

curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
sudo yum makecache -y --disablerepo='*' --enablerepo='rabbitmq_rabbitmq-server'
sudo yum -y --disablerepo='*' --enablerepo='rabbitmq_rabbitmq-server' --enablerepo='rabbitmq_erlang'  install rabbitmq-server
rpm -qi rabbitmq-server
Name        : rabbitmq-server
Version     : 3.13.0
Release     : 1.el8
Architecture: noarch

Configure the server

By default, RabbitMQ uses port 5672, and for the web administration port 15672. Update the server hosts file with its domain name and enable the web based management
echo "127.0.0.1 rabbitmq.demo" | sudo tee -a /etc/hosts
sudo systemctl enable --now rabbitmq-server.service
sudo rabbitmqctl status 
sudo rabbitmq-plugins enable rabbitmq_management
ss -tunelp | grep 15672
sudo firewall-cmd --add-port={5672,15672}/tcp --permanent
sudo firewall-cmd --reload
Verify access: Open URL http://rabbitmq.demo:15672 on a web browser. Then ensure tall feature flags are viewable.

rabbitmqctl list_feature_flags
rabbitmqctl enable_feature_flag all

Create users

Create the initial administrator user as admin and a password.

sudo rabbitmqctl add_user admin SECRETPASSWORD

sudo rabbitmqctl set_user_tags admin administrator
sudo yum -y  --disablerepo='pgdg*'  install mlocate 
sudo updatedb

Create user to access from Producer and Consumer.
sudo rabbitmqctl add_user user1 SECRETPASSWORD
sudo rabbitmqctl set_user_tags user1 management,monitoring

Web console users
Create a virtual host, then click on username user1 and set permissions as required. Example, add to Topic permission with read/write regexp value as .*

Laravel support for rabbitmq

Create or use an existing laravel project.
composer require php-amqplib/php-amqplib
composer update
sudo semanage port -a -t http_port_t -p tcp 5672

Edit Laravel's .env file:
RABBITMQ_HOST=mem.hqcloak
RABBITMQ_IP=10.1.1.101
RABBITMQ_PORT=5672
RABBITMQ_VHOST="/"
RABBITMQ_LOGIN=user1
RABBITMQ_PASSWORD=SECRETPASSWORD
RABBITMQ_QUEUE="queue1"

Laravel rabbitmq service 

Create the file in app\Services\RabbitMQService.php . No examples is provided for usage of this service.
<?php
namespace App\Services;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Connection\AMQPSSLConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Exchange\AMQPExchangeType;
use Illuminate\Support\Facades\Log;

class RabbitMQService
{
    protected $connection;
    protected $channel;
    protected $exchange = 'amq.topic';
    protected $queue = null;
    protected $routingKey = 'routing_key';
    protected $status = true;

    public function __construct()
    {
        $this->connection = new AMQPStreamConnection(
            env('RABBITMQ_HOST'),
            env('RABBITMQ_PORT'),
            env('RABBITMQ_LOGIN'),
            env('RABBITMQ_PASSWORD'),
            env('RABBITMQ_VHOST')
        );
        $this->channel = $this->connection->channel();
        /*
            name: $exchange
            type: direct
            passive: false // don't check if an exchange with the same name exists
            durable: false // the exchange will not survive server restarts
            auto_delete: true // the exchange will be deleted once the channel is closed.
        */
        $this->channel->exchange_declare($this->exchange, 'topic', false, true, false);
        /*
            name: $queue    // should be unique in fanout exchange. Let RabbitMQ create
                            // a queue name for us
            passive: false  // don't check if a queue with the same name exists
            durable: false  // the queue will not survive server restarts
            exclusive: true // the queue can not be accessed by other channels
            auto_delete: true // the queue will be deleted once the channel is closed.
        */
        $queue = env('RABBITMQ_QUEUE', 'queue1');
        $this->init($queue, 'routing_key');
    }

    public function init($queue, $routing)
    {
        $this->queue = $queue;
        $this->routingKey = $routing;
        $this->channel->queue_declare($this->queue, false, true, false, false);
        $this->channel->queue_bind($this->queue, $this->exchange, $this->routingKey);
    }

    /**
     * custom message format: code | value | extradata
     */
    public function publish($message)
    {
        if (null == $this->queue) {
            return;
        }
        $msg = new AMQPMessage($message);
        $this->channel->basic_publish($msg, $this->exchange, $this->routingKey);
    }

    public function stop()
    {
        $this->status = false;
    }

    public function consume($callback)
    {
        if (null == $this->queue) {
            return;
        }
        /*
            queue: Queue from where to get the messages
            consumer_tag: Consumer identifier
            no_local: Don't receive messages published by this consumer.
            no_ack: If set to true, automatic acknowledgement mode will be used by this consumer. See https://www.rabbitmq.com/confirms.html for details.
            exclusive: Request exclusive consumer access, meaning only this consumer can access the queue
            nowait: don't wait for a server response. In case of error the server will raise a channel
                    exception
            callback: A PHP Callback
        */
        $this->channel->basic_consume($this->queue, 'test', false, true, false, false, $callback);
        while ($this->channel->is_consuming()) {
            if (false == $this->status) {
                break;
            }
            $this->channel->wait();
        }
    }

    public function __destruct()
    {
        $this->channel->close();
        $this->connection->close();
    }

}


Create Laravel Controller

On the server Producer and Consumer create controller that can publish or consume, with the file app\Http\Controllers\RabbitMQController.php

<?php
namespace App\Http\Controllers;
use App\Services\RabbitMQService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class RabbitMQController extends Controller
{
    public function publishMessage(Request $request)
    {
        $message = $request->message;
        $result = $this->publish($message);
        return response('Message published to RabbitMQ');
    }

    public function publish($message)
    {
        $rabbitMQService = new RabbitMQService();
        $rabbitMQService->publish($message);
        return response('Message published to RabbitMQ');
    }

    public function consumeMessage()
    {
        $rabbitMQService = new RabbitMQService();
        $callback = function ($msg) {
            echo "Received message: " . $msg->body . "\n";
        };
        $rabbitMQService->consume($callback);
    }

    public function consume()
    {
        $rabbitMQService = new RabbitMQService();
        $callback = function ($msg) {
            $data = $msg->body;
            echo "Received:".$data;
        };
        $rabbitMQService->consume($callback);
    }
}

Create a tinker to test Producer

On server Producer, create the file ./tinker-producer.php
$controller = app()->make('App\Http\Controllers\API\RabbitMQController');
$results = app()->call([$controller, 'publish'], ['message'=>'1001|ACTION|VALUE'] );
print( "$results");

Run tinker

more tinker-producer.php | php artisan tinker

On RabbitMQ web console, observer creation of the queue and the message.

Create a tinker to test Consumer

On server Consumer, create the file ./tinker-consumer.php
$controller = app()->make('App\Http\Controllers\RabbitMQController');
$results = app()->call([$controller, 'consume'],[] );
print( "$results");

Run tinker

more tinker-consumer.php | php artisan tinker

On RabbitMQ web console, observer queue and message consumed.



Friday, January 26, 2024

Handling date and time with carbon

In PHP and Laravel, date and time can be managed using Carbon. Default PHP uses the Date object, which does not have as many flexibility as Carbon object. To start using Carbon on Laravel, add at top, along with other "use" statements. 

use Carbon\Carbon

Here are examples of its usage. Declare current date and time

$currentDateTime = Carbon::now();

The current value can be printed with 

print_r($currentDateTime);


Format to user specific output.

$now = Carbon::now()->format('d-m-Y'); // 1-1-2024

$now->toDateString(); // 2024-01-01

$now->toFormattedDateString(); // Jan 1, 2024

$now->toTimeString(); // 00:00:00

$now->toDateTimeString(); // 2024-01-01 00:00:00

$now->toDayDateTimeString(); // Mon, Jan 1, 2024 12:00 AM

$now->toCookieString(); // Monday, 01-Jan-2024 00:00:00 UTC

$now->toIso8601String(); // 2024-01-01T00:00:00+00:00


Other ways of creating a Carbon object

Carbon::parse('2023-03-10'); // Carbon instance for 2023-01-01 00:00:00

Carbon::parse('Monday of this week'); // Monday of this week

Carbon::parse('first day of January 2024'); // first day of January 2024

Carbon::parse('first day of this month'); // first day of this month

Carbon::parse('first day of next month'); // first day of next month

Carbon::parse('first day of last month'); // first day of last month

Carbon::parse('last day of last month'); // last day of last month


Retrieve values of a carbon in various formats;

$now->year; 

$now->month; 

$now->dayOfWeek; 

$now->englishDayOfWeek; 

$now->englishMonth; 

$now->tzName; 

$now->dst;


Subtract one hour

Carbon::now()->subHour();

Subtract more than 1 hour

Carbon::now()->subHours(2);


Add one hour

Carbon::now()->addHour();

Add more than 1 hour

Carbon::now()->addHours(2);


Add one day

Carbon::now()->addDay();

Add more than 1 day

Carbon::now()->addDays(2);


This can also be applied to subWeeks(), addWeeks().


Set to a specific date by altering day or month or year

$currentDateTime = $currentDateTime->setMonth(2);

$currentDateTime = $currentDateTime->setDay(18);

$currentDateTime = $currentDateTime->setYear(2025);

Example that applies a specific day and month.

$currentDateTime = $workDayStart->setDay($calcCreatedDate->format('d'))->setMonth($calcCreatedDate->format('m'))->setYear($calcCreatedDate->format('Y'));


Retrieve the difference between 2 Carbon dates $start and $now.

$start->diff($now); \\ returns DateInterval

$start->diffInMinutes($now); \\ returns difference in minutes

$start->diffInMinutes($now); \\ returns difference in

$start->diffForHumans($now);

Wednesday, October 18, 2023

Configure L5 Swagger and documention for GET and POST

Here, I will describe usage of Swagger, list the L5 Swagger basic configurations, provide templates to document POST and GET API (Application Programming Interface).

What does L5 Swagger provide?

For PHP developers, here in particular those using Laravel framework, L5 Swagger provide the means to document your API and have it presented in the form of a web page for quick browsing of available APIs and testing its results.

Currently here are good to know facts

  1. Its is developed as a wrapper on swagger-php and swagger-api specifically for Laravel framework.
  2. It supports OpenAPI (formerly known as Swagger), a specification for documentation of RESTful API irrespective of technology, like PHP, Java or .Net.
  3. L5-Swagger currently supports OpenAPI version 3.0 and 3.1. Its project page is https://github.com/DarkaOnLine/L5-Swagger
  4. It supports at least PHP version 7.2. PHP 8.1 introduces the use of attributes.
  5. An online swagger editor is available at swagger.io
My example hinges on PHP 7.4 with darkaonline/l5-swagger version 8.5.1. Will just dump example of code and configurations here. Details will be explained at another time. Good luck.

Quick notes to setup of Swagger in a ready Laravel version 7 or 10 project;

composer require "darkaonline/l5-swagger"
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
php artisan l5-swagger:generate

Customisation can be done by editing config/swagger.php, which can be continued in future articles.

Security

Security options are;
  • None - no security is set to access API
  • Basic Auth - Username and password is set for each request
  • API Key - A key is set for each request
  • OATH - An authorisation scheme for each request

Example 1 - API to login

Request 

Headers:
App-Key: SOmeVeryLongKey

Body form-data:
username: example@some.email.example
password: password

API returns with HTTP code 200

{
    "user_id": 4142,
    "token": "173892|HxOQJBfDgDgDgaqgCpSS1rh7UY7HWdurtanHhq7"
}

API returns with HTTP code 400

{
    "message": "These credentials do not match our records."
}

Example 2 - API to retrieve user profile

Request 

Headers:
App-Key: SOmeVeryLongKey

API returns with HTTP code 200

{
  "status": 0,
  "message": null,
  "data": [
    {
      "id": 385,
      "name": "Bintulu",
      "address1": "no.3 River side, Sarawak",
      "address2": null,
      "introduction": "Software architect and Postgresql architect",
      "phone": "1234512345",
      "email": "bintulu@some.email.example",
      "notes": "Call by phone"
    }
  ],
  "timestamp": "2023-03-17T10:00:09"
}

Swagger documentation

Swagger group for Login

Swagger group for login


Swagger for login

Added example to submit with multi/form-data (which is not necessary for login)




Swagger for user profile

Swagger user profile

Swagger user profile


The Code

Our example api.php and UserController.php

routes/api.php

Route::post('/login', 'API\UserController@login');
Route::get('/user/profile', 'API\UserController@login');

Function login in app/Http/Controllers/API/UserController.php

    /**
     * @OA\Post(
     *     path="/api/login",
     *      tags={"Login"},
     *      security={{"appkey":{}}},
     *      @OA\RequestBody( required=true, description="Login",
     *           @OA\MediaType(
     *             mediaType="multipart/form-data",
     *             @OA\Schema(
     *                 required={"username","password"},
     *                 @OA\Property(
     *                     property="username",
     *                     type="string",
     *                     description="user login id of type email"
     *                 ),
     *                 @OA\Property(
     *                     property="password",
     *                     type="password"
     *
     *                ),
     *             ),
     *          ),
     *
     *     ),
     *     @OA\Response(response="200", description="An example endpoint",
     *          @OA\JsonContent(
     *               @OA\Property(property="id", type="number", example="1957"),
     *               @OA\Property(property="token", type="string", example="173892|HxOQJBfDgDgDgaqgCpSS1rh7UY7HWdurtanHhq7"),
     *          ),
     *     ),
     *     @OA\Response(response="400", description="The id or password incorrect.",
     *           @OA\JsonContent(
     *               @OA\Property(property="message", type="string", example="These credentials do not match our records."),
     *           ),
     *     ),
     * )
     */

Function getUserProfile in app/Http/Controllers/API/UserController.php
    /**
     * @OA\Get(
     *     path="/api/user/profile",
     *     tags={"Login"},
     *     summary="Retrieve user profile",
     *     description="Retrieve user profile based on user auth detected. No parameters are required",
     *     operationId="getUserProfile",
     *     security={{"bearer_token":{}}},
     *      @OA\Parameter(
     *         name="App-Key",
     *         in="header",
     *         description="App-Key",
     *         example=L5_SWAGGER_APPKEY
     *      ),
     *
     *     @OA\Response(response=401, description="User not authenticated",
     *           @OA\JsonContent(
     *               @OA\Property(property="status", type="number", example="1"),
     *               @OA\Property(property="message", type="string", example="Not authenticated"),
     *               @OA\Property(property="data", type="string", example=null),
     *               @OA\Property(property="timestamp", type="string", example="2023-03-17T10:00:09"),
     *           ),
     *     ),
     *       @OA\Response(
     *         response=200,
     *         description="Success",
     *         @OA\JsonContent(
     *           @OA\Property(property="status", type="number"),
     *           @OA\Property(property="message", type="string", example=null),
     *           @OA\Property(property="data", type="array",
     *               @OA\Items(
     *                  @OA\Property(property="id", type="number", example=385),
     *                  @OA\Property(property="name", type="string", example="Bintulu"),
     *                  @OA\Property(property="address1", type="string", example="no.3 River side, Sarawak"),
     *                  @OA\Property(property="address2", type="string", example=null),
     *                  @OA\Property(property="introduction", type="string", example="Software architect and Postgresql architect"),
     *                  @OA\Property(property="phone", type="string", example="1234512345"),
     *                  @OA\Property(property="email", type="string", example="bintulu@some.email.example"),
     *                  @OA\Property(property="notes", type="string", example="Call by phone"),
     *              ),
     *           ),
     *           @OA\Property(property="timestamp", type="string", example="2023-03-17T10:00:09"),
     *         ),
     *       ),
     *  ),
     */


Tuesday, October 17, 2023

Laravel Helper class

Programming is made more systematic with a large number of helper classes in Laravel. Examples are the Arr::last, Arr::add, Arr::get, asset, route, secure_url, url, dd, collect, env)

Lots of documentations are available at Laravel (see Laravel 7). 

Example of usage

Helper url( )

Returns a fully qualified URL

$url = url('user/profile');

Creating your first helper class

The following illustrates a function named "courier" that will be available to all controllers. It typically returns data in a predefined format.

Step 1: Create a helper file in app/Helpers with the name myHelpers.php


/app/Helpers/myHelpers.php

Step 2: Create the function in the file myHelpers.php


<?php
use Carbon\Carbon;

if (! function_exists('courier')) {
function courier($status, $message, $data){
$now = carbon::now();
$status = $status??0;
$package = [
'status'=>$status,
'message'=>$message,
'data'=>$data,
'timestamp'=>$now,
];
return $package;
}
}

Step 3: Edit [autoload] in composer.json


"autoload": {
        "files": [
            "app/Helpers/myHelpers.php",
        ],

Step 4: Reload Laravel


composer dump-autoload

Usage of "courier" helper 

In any of the function in Controller classes, call the helper function. Example

public function getUsers( ){
$users = User::where('status','active')->get();
$status=0; // success
$message=null;
if(count($users)>0){
$status=1; // success but not users available
$message="None";
}
return response(courier($status, $message, $users), 200);
}




Thursday, October 12, 2023

Laravel 10 - User API authentication with Sanctum

Laravel 10 and User API authentication with sanctum

Laravel 10 is available to create restful API where it provides (1)process to issue API tokens to users AND (2)authentication of single page applications(SPA).

This tutorial requirements of system;

  • laravel/sanctum version 3.3.1
  • PHP version 8.2.11
  • Node version 18.12.1
  • Composer version 2.6.3
  • Npm version 8.19.2
  • PostgreSQL database version 15

Laravel application is successfully installed will all recommended PHP extensions.

Create the database and assign user hello assign to that database, which I name as demo. Use hello, or any other user you have created in the database.


create database demo;
grant all privileges on database demo to hello;
ALTER DATABASE demo OWNER TO hello;


Lets create the Laravel application and add sanctum support


composer create-project laravel/laravel demo
cd demo


Configure the .env file to access the database that was declared as demo.


DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=demo
DB_USERNAME=hello
DB_PASSWORD=

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"


Identify and inspect the following folders and files;


config/sanctum.php
database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php


Create database for Sanctum and enable Sanctum


php artisan migrate


Edit app/Http/Kernel.php


'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],


Configure sanctum by editing model, service provider and auth config. Edit app/Models/User.php


use Laravel\Sanctum\HasApiTokens;
...
use HasApiTokens;


Add API to register and login


Edit routes/api.php

  

Route::controller(RegisterController::class)->group(function(){
    Route::post('register', 'register');
    Route::post('login', 'login');
});


php artisan make:controller BaseController
php artisan make:controller RegisterController


Edit RegisterController 


use App\Http\Controllers\BaseController as BaseController;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Validator;
use Illuminate\Http\JsonResponse;


public function register(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email',
            'password' => 'required',
            'c_password' => 'required|same:password',
        ]);
   
        if($validator->fails()){
            return $this->sendError('Validation Error.', $validator->errors());       
        }
   
        $input = $request->all();
        $input['password'] = bcrypt($input['password']);
        $user = User::create($input);
        $success['token'] =  $user->createToken('MyApp')->plainTextToken;
        $success['name'] =  $user->name;
   
        return $this->sendResponse($success, 'User register successfully.');
    }
   
    /**
     * Login api
     *
     * @return \Illuminate\Http\Response
     */
    public function login(Request $request): JsonResponse
    {
        if(Auth::attempt(['email' => $request->email, 'password' => $request->password])){ 
            $user = Auth::user(); 
            $success['token'] =  $user->createToken('MyApp')->plainTextToken; 
            $success['name'] =  $user->name;
   
            return $this->sendResponse($success, 'User login successfully.');
        } 
        else{ 
            return $this->sendError('Unauthorised.', ['error'=>'Unauthorised']);
        } 
    }


Retrieve the registration api 


{
    "success": true,
    "data": {
        "token": "1|R8qfygjItwjleo23QwdqqS5ZcVLZwaRH72iJjiEqd4d85583",
        "name": "admin@example.com"
    },
    "message": "User register successfully."
}


Retrieve login api


{
    "success": true,
    "data": {
        "token": "2|IyNnxOU0N1cc0s2bADqzASxzwc8kl7z5UbqZ2oARd68aa58b",
        "name": "admin@example.com"
    },
    "message": "User login successfully."
}


Ref: https://www.itsolutionstuff.com/post/laravel-10-rest-api-authentication-using-sanctum-tutorialexample.html#google_vignette

https://laravel.com/docs/10.x/sanctum#token-ability-middleware


Next, add a appkey token.

https://laravel.com/docs/10.x/middleware

Friday, September 8, 2023

How to read excel data with PhpSpreadsheet

PhpSpreadsheet provides a PHP library of classes to read and write formats such as LibreOffice Calc, Microsoft Office and CSV. The default encoding uses UTF-8.

Ref: https://phpspreadsheet.readthedocs.io/en/latest/faq/

In Laravel 5, 6 and 7, this is available through the Composer. 

composer require phpoffice/phpspreadsheet niklasravnsborg/laravel-pdf mpdf/mpdf:8.1.6 league/csv

OR

composer require phpoffice/phpspreadsheet niklasravnsborg/laravel-pdf mpdf/mpdf league/csv

Here is an example of how to read the first three columns of an xlsx file, where the first row is considered as headers.

Step 1: Import the class for LibreOffice

use PhpOffice\PhpSpreadsheet\Reader\Ods;

Or for Microsoft Excel

use PhpOffice\PhpSpreadsheet\Reader\Xlsx;

Step 2: In the function that is to retrieve the data, declare object for LibreOffice

$reader = new Ods();

OR for Microsoft Excel

$reader = new Xlsx();

Step 3: Load the spreadsheet data 

$spreadsheet = $reader->load($path);
$sheet = $spreadsheet->getActiveSheet();

OR, load a sheet with specified name
$spreadsheet = $reader->setLoadSheetsOnly(["Sheet1"])->load($path);
$sheet = $spreadsheet->getActiveSheet();

Step 4: Process rows, and skip header in first row

$users = new Users();
if(!empty($sheet)) {
    foreach ($sheet->getRowIterator() as $row) {
      if ($row->getRowIndex() === 1) {
         continue; //Skip heading
      }
      $cells = iterator_to_array($row->getCellIterator("A", "H"));
      $data = [
            "Column A" => $cells["A"]->getValue(),
            "Column B" => $cells["B"]->getValue(),
            "Column C" => $cells["C"]->getValue(),
      ];
      $users->add($data);
}


The example can be said to retrieve the first 3 columns of the spreadsheet row as fields to create a new user.

Tuesday, March 21, 2023

How to create a dynamic object from standard class

 PHP provides a class to create a temporary object where no specific class and members are required.


The class stdClass is the empty class in PHP used to cast other types to object. Among the example of stdClass usage;

  1. Directly access the members by calling them
  2. Dynamic objects can be provided for temporary usage
E.g. 
An array can be treated as an object by using stdClass. 

$tmpStudent = array(
"name" => "John Doe"
);

To access data, 
$tmpStudent['name']

$tmpStudent = new stdClass;
$tmpStudent->name = "John Doe";

Using the stdClass, this can be done as an object
$tmpStudent->name


Wednesday, February 15, 2023

Laravel and search for json content in postgresql database

This is a note on usage of JSON in Laravel 8.

Given a Postgresql database with a table name customer, that have a column name status it would look like this

status=[
{"id": 5, "name": "registered", "description": "Registered only","date":"20221002 013600}
]

The function to search for JSON includes;

whereJsonContains

orWhereJsonContains


Laravel sample code to retrieve all rows where status is either 2, 5, or 6 would look like this;

$filter = [2,5,6];

$customers = Customers::where(function ($q) use ($filter) {

    foreach ($filter as $val) {

        $q->orWhereJsonContains('customers.status', [['id' => $val]]);

    }

})->get( );


Friday, January 27, 2023

Tinker and Controller methods

 Tinker is the tool for command line debugging in Laravel. 

To start the tool, type

php artisan tinker

Create an object from Model

In tinker command line, objects can be created and saved to database. E.g.

$user = new App\Model\User

$user->name = "Tboxmy"

$user->email = "tboxmy@yahoo.com"

$user->save()


Then to display the content of object;

$user

Finding data from model

The eloquent function find( ) allows retrieving from the database, given its default index id. e.g.

$user = App\Model\User::find(2)

Then to display the content of object;

$user

Another approach is to use the function where( ) to retrieve based on the table column names. e.g.

$user = App\Model\User::where('name','Tboxmy')->first( )

Or to retrieve all those related values

$user = App\Model\User::where('id', '>', 2)->first( )

Then to display the content of object;

$user


Introduction to app() and how to call a method in a controller?

A helper object app( ) allows access to the different functions to access a model or controller. The call( ) function is one of the app( ) functions, it is used to access a method inside a controller. e.g. in the controller have a method declared as following;

public function methodName($id=0, $name=null){

. . .

}

The function call( ) can then be used this way;

app()->call(' App\Http\Controllers\HomeController@methodName');


When working with different Controllers, its useful to declare these as variables using the make( ) function. Here is how its done;

$controller = app()->make('App\Http\Controllers\HomeController');

app()->call([$controller, 'methodName'] );

How to pass parameters to methods in a controller? 

This is done with the function call( ) where parameter 2 contains the method parameters. Using the method above, here is how its done;

app()->call([$controller, 'methodName'], [ 'id' => 2, 'name' => 'Tboxmy'] );


Monday, June 28, 2021

Pagination within Laravel

One of the most common operations in Laravel involves the index.blade.php file. It displays a list of all the contents that an object retrieves from the database. In situations where there are thousand of rows of data, the view would best display it across many pages.

In Laravel, Pagination by default is compatible with Tailwind CSS framework and Bootstrap 4. When the number of rows are huge, its essential that the database column where the "whereby" clause is used, is index. 

Lets see 2 methods to apply pagination in a Laravel project using Bootstrap 4. 

Edit file App\Providers\AppServiceProvider.php

public function boot(){
  Paginator::useBootstrap();
}

Lets consider ArticleController that retrieves Article objects from the database through the function index(). This will display all the articles through a View called index.blade.php. For simplicity, only a total of 3 items will be displayed per page. 


Method 1: Using Paginate

Users will be able to view pagination with page numbers.

Pagination display


Edit app\Http\Controllers\ArticleController.php

public function index()
{
   $itemsPerPage = 3;
   $notes = Note::latest()->paginate($itemsPerPage);
   return view('notes.index', compact('notes'));
}

Edit resources\views\articles\index.blade.php and below the displayed date listing add the pagination.

<div>
{!! $notes->links() !!}
</div>

Additional functions available to use with Paginator

  • Number of items in current page $notes->count()
  • Current page number $notes->currentPage() 
  • Last page available $notes->lastPage()


Method 2: Using Cursor

Users will be able to view pagination with only the previous, and next navigation.

Cursor pagination


Edit app\Http\Controllers\ArticleController.php

public function index()
{
   $itemsPerPage = 3;
   $notes = Note::latest()->cursorPaginate($itemsPerPage);
   return view('notes.index', compact('notes'));
}

Edit resources\views\articles\index.blade.php and below the displayed date listing add the pagination.

<div>
{!! $notes->links() !!}
</div>


Additional functions available to use with Cursor

  • Number of items in current page, $notes->count()
  • Determine if cursor is on the first page, $notes->onFirstPage() 
  • Returns value of 1 if there are more pages to be split, $notes->hasMorePages()

Customise Pagination

This can be done by generating the required pagination files for customisation.

php artisan vendor:publish

Then select laravel-pagination and press enter. Find the files in the folder vendor/pagination in the project's view folder. Edit default.blade.php for a custom pagination layout. Here is an example of a customised Paginate.




Friday, November 13, 2020

Laravel quick commands for Artisan

 Laravel utilise Artisan to help make working with development faster. This allow programmers to focus more on the development than the management of packages and some repeated workflows.



Here I list the top few commands that I use;

For scaffolding

  • make:controller
    • Create a controller file in app/Http/Controllers
    • php artisan make:controller ArticleController
  • Create a default model
    • php artisan make:model Article
  • Create a model along with migration and factory
    • php artisan make:model Article -crmf
  • Create migration file
    • php artisan make:migration create_articles_table
  • Process all the migration file that haven't been run before
    • php artisan migrate
  • Create Seeder file
    • php artisan make:seeder CustomerTableSeeder
  • Process seeder file
    • php artisan db:seed
    • php artisan db:seed --class=CustomerTableSeeder
  • List command help
    • php artisan list
  • Routing
    • php artisan route:list
  • Debugging
    • php artisan tinker
  • Front-end user interface
    • php artisan ui bootstrap|vue|react
  • Remove Front-end user interface
    • php artisan preset none
  • Create front-end with user authentication
    • php artisan ui vue --auth
  • A stand alone server start, maintenance mode, normal mode
    • php artisan serve
    • php artisan down
    • php artisan up
  • Refresh laravel optimisation files
    • php artisan dump-autoload

Tuesday, November 10, 2020

Laravel and logging of errors

During the course of development, there are many tools and approaches to debug a programme. In Laravel there is a default logging that supports RFC 5424 and here is how to apply it in a Laravel 7 based application.

As a point of reference, this article is for default Laravel 7 on a Apache2 webserver. Which means, if PHP-FPM is used, check the webserver log files. Additionally, there are many more advanced logging by Laravel, for example use of Rollbar, if you have time to explore.

What is Laravel logging? 

Withing Laravel framework, its config/app.php determines classes that does logging. Monolog logging library is used to provide the classes that can be called from the framework. Its flexibility allows logging across different files and even disk. 

The default logging uses a channel known as stack and is configured as

 'default' => env('LOG_CHANNEL', 'stack'),

This provides logging in several levels of severity that range from in the following order;

  • info
  • debug
  • notice
  • warning
  • error
  • critical
  • emergency

Context data is provided for a more consistent format as described in PSR-3.

How to enable default logging? 

Edit the file .env and have this line to enable debug mode;

APP_DEBUG=true

Where is the default logging done? It all in the file laravel.log found in <project_folder>/storage/logs. Ensure the application have correct permission to write app/storage and all its subfolders. When using Selinux, also ensure its allowed to write.

When is logging done?

This can be done practically any where, such as within the controller, views and any other executable.

How to do the logging? 

Taking that you have by now enabled debug mode.

Lets look at how its done within a Controller.

  1. Ensure top of the controller file contains the line
    • use Illuminate\Support\Facades\Log;
  2. Write the code to log your message
    • Log::info('This is where the code runs');

Once when you run the application and the Log::info( ) gets triggered, have a look at the end of file storage/logs/laravel.log to find the message.

Will consider to write further on formatting of the log in order for logging tools to pull data and generate reports or monitoring

Wednesday, November 4, 2020

Laravel ErrorException file_put_contents

After working on any Laravel project for a period of time, its possible that you would change the project folder to another location or drive. Lets say I have a Laravel project called employeeManagement in C:\users\tboxmy\employeeManagement and I make a copy of this to Z:\tboxmy\workspace\project while renaming the project name. 


 

Problem

For some strange reason, the pages are not loading properly and the following error appears.

ErrorException

file_put_contents(Z:\tboxmy\workspace\project\storage\framework/sessions/c34niIDr5qyV8Fyf0qXFLXSF1IlOv3yOulSC9sHI): failed to open stream: No such file or directory 

I have also noticed that the same problem causing that ErrorException SOMETIMES doesn't appear but it is actually loading pages from the original folder location. So when I edit my codes in Z:\tboxmy\workspace\project the changes just doesn't appear in the application. To determine this, just try to rename the original project folder and this error WILL appear.

There are 2 aproches to solve this, which does the same thing.

Solution 1

Step 1

Open a command line and ensure you are in the same folder as the project, in this case Z:\tboxmy\workspace\project\
 

Step 2

Type the command 

php artisan config:cache 
 
 

Step 3 : Update the cache in all installed plugins/libraries

composer install
npm install
npm run dev

Solution 2

Step 1

Using a file manager, or at the command prompt, open the project folder and go to the folder bootstrap\cache
In this case Z:\tboxmy\workspace\project\bootstrap\cache
 

Step 2

Delete the file config.php

Repeat Step 3 above.

Friday, May 15, 2020

Laravel: Getting started

Notes on getting started with Laravel, on Centos Linux 6,7,8.

In order to get started with Laravel, there are required software to run composer and laravel that needs to be installed. This includes;
  1. Web server
  2. PHP
  3. Composer
  4. Node JS

Step 1. Install PHP 7.3 or newer.

If you havent installed PHP, follow notes from previous post.
After installing PHP, install all the regular extensions. Ensure PHP is working with the web browser.
sudo yum install php-cli php-zip wget unzip  php-dom php-posix php-shmop  php-xsl php-xmlrender

Step 2. Web server.

In this case, I am using Apache HTTPD. Install this and test that the php is working.
sudo yum install httpd

Step 3. Composer

Download the installer.
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php' ");
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

Step 4. Node JS


sudo yum install nodejs

You are ready to work with Laravel projects. Here are links to the remainder tutorials
Part 1, Part 2,


Tuesday, May 5, 2020

Laravel 6 Step by Step Tutorial Part 2

Laravel 6 provides an advanced framework for programmers to quickly create enterprise on a wide number of PHP platforms. Part 2 of this Laravel guide can be found at Laravel 6 Part 2

In order to proceed with part 2 of the tutorial, please complete part 1 as posted in
First of Laravel 6 step by step guide.

The tutorial can be followed by having your copy of PHP 7.3 onwards and a database. Notes are provided for MySQL and Postgresql database.

This demonstrates how to implement data relationships of has one. In Laravel this is implemented with the function belongsTo. The idea is to maintain a list of articles where each have one author. That author could essentially have written many articles.

Friday, January 10, 2020

First of Laravel 6 step by step tutorial

Just completed the first part of the step by step tutorial.

Feel free to check it out at https://tboxmy.blogspot.com/p/laravel-6-crud-step-by-step.html

This will complement the Laravel notes found on this blog.

Friday, November 4, 2016

Howto Install Laravel Framework using Composer

Laravel framework provides rapid development of a model-view-controller (MVC) with PHP programming language. Currently is at version 5.3 and is open source using MIT license.

Composer is a tool to easily retrieve packages required for most PHP projects and frameworks. Laravel can be found available in Composer at packagist.org. Detailed documentation for Composer can be found at https://getcomposer.org

This is a quick guide on installing Laravel then creating a page to display hello world!.

Installation environment for this How-to;
MS Windows 10
Apache2
PHP v7.0.12
Composer 1.2.2
Laravel 5.3

By the end of this guide, the following directories should have been generated.


Step 1: Installing Composer

Ensure Apache2 and PHP7 is already installed and working before starting this guide.

Download and install Windows Composer installer https://getcomposer.org/Composer-Setup.exe

Open a terminal and change to the installed PHP folder. Type

 $> composer -V  

(Or with the file composer.phar in same directory, type)
 $> php composer.phar  
This should display the version number of composer.
Update composer and check the version.
 $> php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"  
 $> php -r "if (hash_file('SHA384', 'composer-setup.php') === 'aa96f26c2b67226a324c27919f1eb05f21c248b987e6195cad9690d5c1ff713d53020a02ac8c217dbf90a7eacc9d141d') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"  
 $> php composer-setup.php  
 $> php -r "unlink('composer-setup.php');"  
 $> composer -V  

Step 2: Create an empty composer project 

Create a project folder called myproject.
Open a terminal and enter folder for myproject
Type
 $> composer init  

Choose default values, except following;
Package type: project
dev dependencies: yes
Search for a package: laravel/laravel

Press "Enter" until exit the interactive menu.

This will create a default json file for the composer. This step is only an example of creating a json file. Feel free to ignore this step.

Step 3: Install Laravel Framework and dependencies

Create a project folder called myproject, if you haven't.
Open a terminal and enter folder for myproject, and type.

 $> composer create-project --prefer-dist laravel/laravel hello  


This will do pretty much that same as Step 2 above, except that it will create the workspace called hello and load all the required libraries and plugins.

Laravel is installed in the folder hello.

Step 4: Start local server

Open a terminal and enter folder for hello, and type.

 $> cd hello  
 $> php artisan serve  

Laravel development server started on http://localhost:8000/

Open a web browser and type the URL mentioned above.
http://localhost:8000/

It displays the default Laravel page.

Step 5: Display a Hello World page

Open a terminal and go to the project folder ->hello ->resources ->views
Create a file helloworld.blade.php with following text

 <!DOCTYPE html>  
 <html>  
   <head>  
     <title>Laravel</title>  
   </head>  
   <body>  
  Hello World!  
   </body>  
 </html>  

Tell Laravel that to display this "VIEW" when the URL is /hello.
Edit the file web.php in the project folder ->hello ->routes

 Route::get('/hello', function () {    
      return view('helloworld');  
 });  

Save the file.

Open a web browser and type the URL mentioned above.
http://localhost:8000/hello

This displays the Hello World! page with HTML formatting from the file resources/views/helloworld.blade.php

Step 6: Display a Hello World text (without Views)

Tell Laravel to display some HTML when the URL is /hello2
Edit the file web.php in the project folder ->hello ->routes

 Route::get('/hello2', function () {    
      return 'Hello World 2!';  
 });  

Save the file.

Open a web browser and type the URL mentioned above.
http://localhost:8000/hello2

This displays the Hello World 2! text.

Happy trying out Lavarel.



Blog Archive