How to run composer with different PHP versions in Cpanel?

Question:

When we try to run a composer command, like update, we usually do the following:

composer update

Cpanel has multiple PHP binaries, but in this case, we are unable to select a specific PHP binary to use, instead we have to run it with the default one, how to run composer update with a different php binary in cpanel?

Solution

composer binary file, is a phar file. PHAR is necessarily a PHP Archive and usually automatically detect the running php. But as it is essentially written in php, you may explicitly run it with a different php binary, if you want. To run composer with different php binary, first, you need to find the location of composer. You may do so, using the following:

root@mirage [~]# which composer
/opt/cpanel/composer/bin/composer

Cpanel different php binaries are available under the following kind of directory:

/opt/cpanel/ea-phpXX/usr/bin/php

XX is the version number of PHP. So for example if you need to use PHP 7.4, you would need to run using the following:

/opt/cpanel/ea-php74/usr/bin/php

Now, to run composer update along with PHP 7.4 binary, you may do something like the following:

/opt/cpanel/ea-php74/usr/bin/php /opt/cpanel/composer/bin/composer update

First, make sure you are in the directory where you want to install laravel, for example, something like the following:

cd /home/username/public_html

Then, you may run the above command:

/opt/cpanel/ea-php74/usr/bin/php /opt/cpanel/composer/bin/composer update
or in case, you want to to install
/opt/cpanel/ea-php74/usr/bin/php /opt/cpanel/composer/bin/composer install
or may be, you wan to run update with no-scripts
/opt/cpanel/ea-php74/usr/bin/php /opt/cpanel/composer/bin/composer update --no-scripts

Uncaught ErrorException: require(/home/username/public_html/vendor/composer/../../app/Helpers/helper.php): failed to open stream: No such file or directory in /home/username/public_html/vendor/composer/autoload_real.php:71

Error Details

While trying to run any of the following with Laravel composer installer, you see an error similar to the following:

Uncaught ErrorException: require(/home/username/public_html/vendor/composer/../../app/Helpers/helper.php): failed to open stream: No such file or directory in /home/username/public_html/vendor/composer/autoload_real.php:71

How to fix this?

Solution

The error is appearing, most likely you forgot to add the ‘app’ directory of laravel in your root directory. Make sure, you have the ‘app’ directory in your root directory, then run any of the following:

If this is the first time, you need all laravel packages, run:

composer install

If this is not the first time, you may run the following:

composer update

Good luck.

How to Fix: Facebook\WebDriver\Exception\UnknownErrorException: unknown error: cannot find Chrome binary – Laravel Dusk

When run the following command in your Laravel Automation Tool Dusk:

php artisan dusk

You get something like the following error:

[elastic-keldysh@pl1 blog]$ php artisan dusk
PHPUnit 9.3.11 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 00:00.339, Memory: 18.00 MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: cannot find Chrome binary

/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/php-webdriver/webdriver/lib/Exception/WebDriverException.php:139
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php:370
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php:136
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/tests/DuskTestCase.php:40
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:200
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/framework/src/Illuminate/Support/helpers.php:234
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:201
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:95
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:65
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/tests/Browser/ExampleTest.php:21

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

The error actually appears because you do not have Chrome browser installed on your system. Now, exit from your user shell, and drop back to the root shell. From the root shell, if you are using CentOS 7, you may run the following:

First Download the Latest Chrome RPM:

wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm

Run it with YUM:

yum install ./google-chrome-stable_current_*.rpm

If you are on CentOS 8, you may use dnf installer as following:

dnf localinstall google-chrome-stable_current_x86_64.rpm

Once the installation is done, you may now go back to the user shell and run laravel dusk:

[root@pl1 lib64]# su - elastic-keldysh
Last login: Thu Oct  1 15:39:18 IST 2020 on pts/0
[elastic-keldysh@pl1 ~]$ php artisan dusk
Could not open input file: artisan
[elastic-keldysh@pl1 ~]$ cd blog
[elastic-keldysh@pl1 blog]$ php artisan dusk
PHPUnit 9.3.11 by Sebastian Bergmann and contributors.

R                                                                   1 / 1 (100%)

Time: 00:02.367, Memory: 18.00 MB

There was 1 risky test:

1) Tests\Browser\ExampleTest::testBasicExample
This test did not perform any assertions

/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/tests/Browser/ExampleTest.php:16

OK, but incomplete, skipped, or risky tests!
Tests: 1, Assertions: 0, Risky: 1.

It shall work now. Hope this helps.

How to Fix : Failed to connect to localhost port 9515: Connection refused

If you are looking at this post, chances high that, you are trying to run laravel dusk and seeing the following kind of error:

[elastic-keldysh@pl1 blog]$ php artisan dusk
PHPUnit 9.3.11 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 00:00.331, Memory: 18.00 MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"capabilities":{"firstMatch":[{"browserName":"chrome","goog:chromeOptions":{"binary":"","args":["--disable-gpu","--headless"]}}]},"desiredCapabilities":{"browserName":"chrome","platform":"ANY","chromeOptions":{"binary":"","args":["--disable-gpu","--headless"]}}}

Failed to connect to localhost port 9515: Connection refused

/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php:331
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php:136
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/tests/DuskTestCase.php:40
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:200
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/framework/src/Illuminate/Support/helpers.php:234
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:201
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:95
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:65
/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/blog/tests/Browser/ExampleTest.php:21

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

Chorme/Chromium browser runs it’s on 9515 port and laravel dusk tries to connect to it through the Browser binary, but failing to do so. To resolve the case, we first, need to check if chromedriver for linux can run or not.

First, browse your laravel root directory, in my case, it’s the blog directory and then run the chromedriver for linux as following:

cd blog
vendor/laravel/dusk/bin/chromedriver-linux

This should return something like the following if the driver able to load in memory:

[elastic-keldysh@pl1 blog]$ vendor/laravel/dusk/bin/chromedriver-linux
Starting ChromeDriver 85.0.4183.87 (cd6713ebf92fa1cacc0f1a598df280093af0c5d7-refs/branch-heads/4183@{#1689}) on port 9515
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

But if it can not be loaded, it will return some kind of error like the following:

[elastic-keldysh@pl1 blog]$ vendor/laravel/dusk/bin/chromedriver-linux
vendor/laravel/dusk/bin/chromedriver-linux: error while loading shared libraries: libX11-xcb.so.1: cannot open shared object file: No such file or directory

As the error suggests, it is failing to access the library called libX11-xcb. To install it in CentOS, you may run the following from root shell:

[root@pl1 lib64]# yum install libX11-xcb*

Once you see the following:

Installed:
  libX11-xcb-1.6.8-3.el8.x86_64

Complete!

You may now, go back to the user shell, and run the chromedriver-linux again:

[root@pl1 lib64]# su - elastic-keldysh
Last login: Thu Oct  1 15:28:29 IST 2020 on pts/0
[elastic-keldysh@pl1 ~]$ cd blog
[elastic-keldysh@pl1 blog]$ vendor/laravel/dusk/bin/chromedriver-linux
Starting ChromeDriver 85.0.4183.87 (cd6713ebf92fa1cacc0f1a598df280093af0c5d7-refs/branch-heads/4183@{#1689}) on port 9515
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

Now, you may run the following to start using the laravel dusk:

php artisan dusk

After you are done resolving the 9515 error, you might also have to face another error with Chrome Binary, have a look at the following on how to resolve the error:

How to Install Laravel in Plesk using SSH

In plesk, to install laravel, we need to do two prerequisites first.

  1. Enable SSH for the Plesk User:

2. Add default PHP binary to Plesk Shell to allow using Composer:

Once the above steps are done, now, you may install the laravel installer with the following:

[elastic-keldysh@pl1 ~]$ composer global require laravel/installer
Changed current directory to /var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/.composer
Using version ^4.0 for laravel/installer
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 12 installs, 0 updates, 0 removals
  - Installing symfony/polyfill-php80 (v1.18.1): Downloading (100%)
  - Installing symfony/process (v5.1.6): Downloading (100%)
  - Installing symfony/polyfill-mbstring (v1.18.1): Downloading (100%)
  - Installing symfony/polyfill-intl-normalizer (v1.18.1): Downloading (100%)
  - Installing symfony/polyfill-intl-grapheme (v1.18.1): Downloading (100%)
  - Installing symfony/polyfill-ctype (v1.18.1): Downloading (100%)
  - Installing symfony/string (v5.1.6): Downloading (100%)
  - Installing psr/container (1.0.0): Downloading (100%)
  - Installing symfony/service-contracts (v2.2.0): Downloading (100%)
  - Installing symfony/polyfill-php73 (v1.18.1): Downloading (100%)
  - Installing symfony/console (v5.1.6): Downloading (100%)
  - Installing laravel/installer (v4.0.5): Downloading (100%)
symfony/service-contracts suggests installing symfony/service-implementation
symfony/console suggests installing symfony/event-dispatcher
symfony/console suggests installing symfony/lock
symfony/console suggests installing psr/log (For using the console logger)
Writing lock file
Generating autoload files
10 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
[elastic-keldysh@pl1 ~]$

Once this is done, you may try to install laravel using the laravel command:

[elastic-keldysh@pl1 ~]$ laravel new blog
-bash: laravel: command not found

But, as you can see it is failed. It’s because the laravel binary is installed in the following path:

/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/.composer/vendor/bin

which doesn’t exist in our $PATH variable. Now you may add the above command to your PATH variable using the following tutorial:

I believe, you have already figured it out, it is as simple as the following command:

PATH=$PATH:/var/www/vhosts/elastic-keldysh.139-99-24-82.plesk.page/.composer/vendor/bin

Now, you may run the following to install laravel:

[elastic-keldysh@pl1 ~]$ laravel new blog

 _                               _
| |                             | |
| |     __ _ _ __ __ ___   _____| |
| |    / _` | '__/ _` \ \ / / _ \ |
| |___| (_| | | | (_| |\ V /  __/ |
|______\__,_|_|  \__,_| \_/ \___|_|

Creating a "laravel/laravel" project at "./blog"
Installing laravel/laravel (v8.0.3)
  - Installing laravel/laravel (v8.0.3): Downloading (100%)

So, yeah, as laravel says, Application ready! Build something amazing.

How to Call a Controller Method from Tinker – Laravel

LARAVEL TINKER

If you love to debug and test things in an app shell like me, then you are also a big fan of Tinker in laravel. Tinker is the shell prompt for Laravel and can be used to test and run different commands in php inside the app. You may run the following to hop into the tinker shell in a laravel environment:

php artisan tinker

Once you hop in the tinker, you can call any model or run any php command from the shell.

HOW TO RUN CONTROLLER METHOD FROM TINKER

There are times, you might feel more interest into evaluating a large controller method. To run a controller method, we first need to enter the service container of laravel. Laravel providers a helper method called ‘app()’ to enter the service container. It can then use a method called ‘call’ to access and execute a method inside a controller namespace, like the following:

app()->call('App\Http\Controllers\AdminControllers@yourmethod');

Repace your controller name and the method name after @. One thing, you need to realize is that the method ‘call’ takes the method reference, not the function itself. That means, you can not add brackets () at the end of method name while giving it in the call method.

HOW TO PASS PARAMETERS TO CONTROLLER FROM TINKER

As discussed earlier, you are passing reference only, not the function, hence you can not pass parameters like we usually do in methods/functions. We need to pass this as an argument in array.

Here is a more constructive way to do this:

# let's make an instance of controller first, can be done using make method of service container
$controller = app()->make('App\Http\Controllers\AdminControllers');

# now let's call the method, inside the container, method name is 'getNewsByCatId'
app()->call([$controller, 'getNewsByCatId']);

# pass a parameter called id = 5
app()->call([$controller, 'getNewsByCatId'], ['id' => 5]);

Can You Test Emptiness of Laravel Collection using empty()?

In short, Yes and No. Not the way we commonly do a variable, but in laravel way yes. It’s a common mistake done by almost all the laravel developer once in a lifetime until the bug appears (Well, you are not counted, if you are exceptional :P). So, let’s explore.

Let’s look at how laravel collection is constructed. Go to your laravel tinker console and try this:

php artisan tinker
Psy Shell v0.9.12 (PHP 7.2.31 — cli) by Justin Hileman
>>> $collection = collect([])
=> Illuminate\Support\Collection {#3164
     all: [],
   }

You see, when I create an empty collection, laravel still puts an underlying array called ‘all’. This is the manipulator array and contains all the content inside. This array is accessible through the collection all method:

>>> $collection->all()
=> []

You see, the return is an empty array. But when it’s just the collection, it’s not really empty, it has an underlying content holder.

So, how can we test emptiness of the collection? Well, there are 3 ways.

I) Laravel gives a way to return the number of element in the collection with a collection method calls count(). You can test it against 0 to see if the collection is empty or not

>>> $collection->count()
=> 0

II) You may use the regular php count() method to return that it doesn’t contain any leaf element and test it against 0:

>>> count($collection)
=> 0

III) If you are a big fan of ’empty’ and still would like to follow, then you can grab the content of the collection using all method and test it against empty as following:

>>> empty($collection->all())
=> true

So, yeah, now you know all the ways 🙂

How to Use Sticky Session for CSRF submission on Highly Scalable Cloud App in Haproxy

HINT: If you are a nginx fan and used it in mass scale, then, you must have done this using ip_hash (Nginx Documentation). It follows the same purpose for Haproxy. Difference and benefits of using Haproxy over Nginx for L7 proxy in a highly scalable and reliable cloud app would be a discussion for another day.

Case Discussion:

Suppose, you have a Cloud app, that is load balanced & scaled between multiple servers using Haproxy, for example:

101.101.101.101
202.202.202.202
303.303.303.303

Now, if your app has a submission form, for example, a poll submission from your users, then, there is an issue in this Haproxy setup.

Let’s say, an User A, requests for the app, and gets the data from the server 101.101.101.101, the CSRF token he gets for the poll submission to his browser, also maintains the app hosted on 101.101.101.101. But when he press the submit button, HAProxy puts him on 202.202.202.202 app, and the app hosted on 202.202.202.202 instantly rejects the token for the session as the session is not registered for that app. For such cases, we need to maintain a ‘Sticky’ session based on the cookie set by the right server. That means, if the cookie is set by 101.101.101.101, HAproxy should obey and give the user 101.101.101.101 until the cookie or the session is reset or regenerated.

How To Do That:

What we need to do, let haproxy write the server id in the cookie, and make the directive ‘server’ to follow the cookie. Please remember, there are couple of other way to achieve this. There is another way of doing this is called ‘IP Affinity’, where you make sticky session based on IP of the user. There is another based on PHP session value. Setting sticky session based on php session should also work. I preferred the cookie based sticky session, just on random selection.

So, to write the server id in the cookie, you need to add the following in the haproxy ‘backend’ directive as following:

backend app-main
balance roundrobin
cookie SERVERID insert indirect nocache

In the cookie directive, you can see, we are taking the HAProxy variable ‘SERVERID’ and inserting that to the cookie attribute. Now, all you need to do, is to configure your balancing IPs to follow the cookie, like the following:

backend app-main
balance roundrobin
cookie SERVERID insert indirect nocache
server nginx1 101.101.101.101 cookie S1
server nginx2 202.202.202.202 cookie S2
server nginx3 303.303.303.303 cookie S3

S1, S2, S3 are just 3 different names of the cookies for the specific servers. After the above is done, you can now restart and see Haproxy is following stickiness based on the session you have.

Just to find out, how to test if you are using laravel, try to regenerate the session based on the session() helper method as following:

session()->regenerate()->csrf_token();

You should be able to see the content loading from different web servers when the session regenerates. But it will persists when the regenerate session method is not called.

How to Do Full Page Caching in Laravel / How to Cache Views in Laravel

Most of the developers use Laravel Cache for database query result caching. Although, this is efficient, but the ultimate caching performance enhancement is achieved through FPC or Full Page Caching for web apps. Laravel doesn’t give any hint, neither describe how to do this in their documentation, which is why the article.

What is Full Page Caching?

Technically a full page caching means, to cache the html response from an app. In FPC, it is generally accepted to use the route/view as the cache key concatenating or mixing with the VERB in request header.

When a user requests for a route, we usually pull a controller behind the route to process and prepare several data before sending them to views for response. But what if the data hasn’t changed since the last request? That technically means the response hasn’t changed, right? This essentially says, you can cache the full response and skip the whole controller processing, even pulling the view, instead, only put the Cached data in the response. Theoretically, this is the best form of caching mechanism for ‘Web Based’ solutions like Ecommerce, Newspapers, Blogs etc. This technique is known as FPC or Full Page Caching.

Laravel Cache

Laravel is best known for it’s documentation. Although, the Laravel Cache documentation, only follows how to cache the database queries, not the views. To understand how to do FPC using Laravel, let’s first look at how our views are usually formed.

class NewsController extends Controller {
    public function index() {
        $news = News::all();
        return view('news.index')->with('news', $news);
    }
}

Here the view() helper method, returns a Laravel View instance. It doesn’t return the html or renders one. So who does it? Laravel does it for you under the hood, and pass it to Response class. Now to cache the views, you have to return the html and save it to cache. There are basically two ways of doing it.

The easiest way is to use a function called ‘render()’ that is available to View class which returns the html of the created View instance. Here is how you may convert the above controller method to return from cache:

class NewsController extends Controller {
    public function index() {
        if ( Cache::has('news_index') ) {
            return Cache::get('news_index');
        } else {
            $news = News::all();
            $cachedData = view('news.index')->with('news', $news)->render();
            Cache::put('news_index', $cachedData);                                         
            return $cachedData;           
        }  
    }
}

This should be it, simple, ha!

Here is more! I looked at the laravel documentation a bit more, and I could find there is another way you can do the above. This is using the Response class. view method returns a Views instance, while Response instance is able to return rendered html based on view. Here is how to do this:

Response::view('news.index')->with('news', $news);

This also means our idea that Laravel does the rendering under the hood is a bit wrong, it basically shoots the views instance to a response instance (which it has to) and returns it, that put the rendered html in the final response. We can now cache the above output and serve for future requests without entering the controller’s processing!

TDD: Date Assertions – Laravel 7, PHP 7.* – Carbon 2 – Changes

If you follow a TDD approach to develop your software, and also a Laravel user, working with a JSON API, you might have experienced some date assertion issues while asserting Json with Laravel 7. Previously, when Laravel was using Carbon 1 for date management, it would not return the whole Carbon object for assertJson. Which is why, the following would work in a sample PHPUnit Test:

$contact = factory(Contact::class)->create();
$response = $this->get('/api/contacts' . $contact->id);

$response->assertJson([
'name' => $contact->name,
'email' => $contact->email,
'meeting_date' => $contact->meeting_date,
'company' => $contact->company,
]);

But as of now, Carbon 2, returns the whole Carbon object for $contact->meeting_date here, the assertion will fair, because the $response, didn’t get a Carbon object, instead a Json string here.

If you go through the Carbon documentation here, you can see the following under the section ‘Migrate to Carbon 2’:

$date->jsonSerialize() and json_encode($date) no longer returns arrays but simple strings: "2017-06-27T13:14:15.000000Z". This allows to create dates from it easier in JavaScript. 

This is basically what you need to use if you are on Laravel 7 with Carbon 2. You need to serialize the data as Laravel is not doing it for you automatically here to fix this up. Just change the ‘meeting_date’ to the following:

'meeting_date' => $contact->meeting_date->jsonSerialize(),

and this should let your assertion pass.

Remember, you might not need to explicitly do this in your controller return until you are following a TDD based development where an assertion to pass is important to continue the process. Laravel resource would implicitly serialize your data before returning them from controller.