The post PHP cURL error 60: SSL certificate problem – Windows & Linux Fix appeared first on Devnote.
]]>The fix is straightforward once you understand what is missing: a trusted CA certificate bundle.
PHP uses cURL to make HTTPS requests. cURL checks server certificates against a list of trusted Certificate Authorities (CA). If PHP cannot find that list, it refuses the connection for security reasons. So the real issue is not your code, but your environment.
Use the official curl project CA bundle:
https://curl.se/ca/cacert.pem
Save it somewhere stable, for example:
C:\xampp\php\extras\ssl\cacert.pem
Open php.ini and find this line:
;curl.cainfo =
Uncomment and set the path:
curl.cainfo = "C:\xampp\php\extras\ssl\cacert.pem"
Also locate:
;openssl.cafile=
Set it too:
openssl.cafile="C:\xampp\php\extras\ssl\cacert.pem"
Restart Apache or PHP.
That’s it. cURL will now trust valid SSL certificates.
Linux usually includes default CA packages. Install or update them:
sudo apt-get update
sudo apt-get install ca-certificates
sudo update-ca-certificates
sudo yum install ca-certificates
sudo update-ca-trust force-enable
sudo update-ca-trust extract
After installation, PHP will automatically use the system’s CA store.
Many CI images are trimmed and do not include SSL bundles.
Simply install them:
apt-get update
apt-get install -y ca-certificates
Then rebuild your image.
You will see developers do this:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
It disables SSL verification and opens huge security risks.
Only use this for debugging local issues, never in production.
If you still get the same error:
php.ini the CLI one or the web one?The post PHP cURL error 60: SSL certificate problem – Windows & Linux Fix appeared first on Devnote.
]]>The post PHP 8.3 Fatal Error: Cannot use string offset as an array – Real World Case appeared first on Devnote.
]]>In simple terms, this error means: you are treating a string like an array.
Imagine you receive a form and accidentally overwrite an array with a string:
$data = $_POST['user']; // you expect an array
$data = trim($data); // now $data is a string
echo $data['email']; // ❌ Fatal error
$data becomes a string after trim, so $data['email'] tries to use a string offset as an array.
Keep array and string variables separate or avoid overwriting:
$user = $_POST['user'] ?? [];
$userEmail = isset($user['email']) ? trim($user['email']) : null;
Or, if the form sends flat fields:
$email = isset($_POST['email']) ? trim($_POST['email']) : null;
Another real case: API or database returns JSON, but you double encode it or forget the second parameter in json_decode.
$json = getOption('settings'); // already a JSON string
$settings = json_decode($json); // returns stdClass by default
echo $settings['theme']; // ❌ Fatal error
json_decode without the second parameter returns an object. Accessing it with ['theme'] treats it like an array.
Decode as an associative array:
$settings = json_decode($json, true); // true = associative array
$theme = $settings['theme'] ?? 'default';
Or keep it as an object and use:
$theme = $settings->theme ?? 'default';
You store settings as an array but later load a single column as a string into the same variable:
$config = [
'mail' => [
'host' => 'smtp.example.com',
],
];
$config = $pdo->query('SELECT value FROM config WHERE name="mail"')->fetchColumn();
// now $config is a string from DB
echo $config['host']; // ❌ Fatal error
Use different variables and convert the DB result back to an array if needed:
$mailConfigJson = $pdo->query(...)->fetchColumn();
$mailConfig = json_decode($mailConfigJson, true) ?? [];
echo $mailConfig['host'] ?? 'localhost';
Real world bug from helper functions:
function addDefaultRoles(array $roles): array
{
$roles[] = 'viewer';
return $roles;
}
$rolesFromDb = 'admin'; // bug: should be array
$roles = addDefaultRoles($rolesFromDb); // ❌ Type + string offset errors
PHP may complain that it cannot use string offset internally while trying to push onto the array.
Normalise types before calling:
$rolesFromDb = $rolesFromDb ? explode(',', $rolesFromDb) : [];
$roles = addDefaultRoles($rolesFromDb);
Or enforce types at the boundary of your application.
To avoid this kind of fatal error in new code:
is_array() before accessing offsets.??) to provide defaults.Example:
declare(strict_types=1);
function getUserEmail(array $user): ?string
{
return isset($user['email']) ? trim((string) $user['email']) : null;
}
$user = $_POST['user'] ?? [];
$email = getUserEmail($user);
Now if $user is not an array, PHP will throw a TypeError at the function boundary instead of failing deep inside your code.
$var['key'].var_dump or dd($var) to see its actual type and value.json_decode($json, true) when you want arrays.Once you treat strings and arrays consistently, this error disappears and your code becomes much easier to reason about in real applications.
The post PHP 8.3 Fatal Error: Cannot use string offset as an array – Real World Case appeared first on Devnote.
]]>The post How to Fix Nginx 413 Request Entity Too Large for Image & Video Uploads appeared first on Devnote.
]]>Let’s fix that safely.
Nginx controls max request size with client_max_body_size. If it is missing, the default is usually 1 MB. That is tiny for modern uploads, so large images or videos will be blocked.
You can set it in:
nginx.conf (global)http, server, or location blockExample, for a single site:
server {
server_name example.com;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
Choose a size that fits your needs, like 20M, 64M, 100M, or more for big video uploads.
After editing the config file:
sudo nginx -t
If it says syntax is ok, reload:
sudo systemctl reload nginx
or on some systems:
sudo service nginx reload
Now Nginx accepts larger requests.
If you only change Nginx, PHP might still reject large uploads with errors like:
POST Content-Length of X bytes exceeds the limit of Y bytes
Check your php.ini (or .user.ini on shared hosting) and set:
upload_max_filesize = 64M
post_max_size = 64M
Rules of thumb:
post_max_size ≥ upload_max_filesizeclient_max_body_sizeAfter editing, restart PHP-FPM:
sudo systemctl restart php8.3-fpm
(adjust version to your PHP version).
For per-site limits, use the server block for that domain:
server {
server_name media.example.com;
client_max_body_size 200M;
...
}
If you put client_max_body_size inside a location that does not match your upload URL, Nginx will still use the low default and return 413.
For a global setting, you can place it in the http block in nginx.conf:
http {
client_max_body_size 64M;
...
}
WordPress
Uploads typically go through /wp-admin/admin-ajax.php or /wp-admin/media-new.php. A simple per-site config:
server {
server_name example.com;
root /var/www/example.com/public;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
}
Laravel
If your app handles uploads at /upload or through a controller, the same pattern applies:
server {
server_name app.example.com;
root /var/www/app/public;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
The app will then just see a normal file upload within PHP limits.
If you do not edit Nginx by hand:
client_max_body_size 64m;upload_max_filesize and post_max_size in the panel’s PHP settings.If you still see 413 after changes:
nginx -T | grep client_max_body_size to see what values are actually active.client_max_body_size directives with smaller values overriding your new one in a more specific block.When Nginx responds with 413 Request Entity Too Large for uploads:
client_max_body_size to a higher value (for example 64M) in the right server or http block.nginx -t.upload_max_filesize and post_max_size in PHP to the same or slightly lower value.With Nginx and PHP aligned on upload size, images and videos will upload successfully without the 413 error.
The post How to Fix Nginx 413 Request Entity Too Large for Image & Video Uploads appeared first on Devnote.
]]>The post PHP file_put_contents(): Failed to open stream in Storage and Cache – Permissions Fix Guide appeared first on Devnote.
]]>Let’s go through how to fix this properly, both for plain PHP and Laravel.
file_put_contents() is a simple wrapper around opening a file, writing, and closing it.
For this to succeed:
Example:
file_put_contents('/var/www/project/storage/logs/custom.log', 'Hello');
Directories required:
/var/var/www/var/www/project/var/www/project/storage/var/www/project/storage/logsThe last one, logs, must be writable.
Laravel writes a lot of files automatically:
storage/logs/laravel.logstorage/framework/cachestorage/framework/sessionsstorage/framework/viewsIf any of these are not writable, you’ll see file_put_contents() errors.
From your project root:
mkdir -p storage/logs
mkdir -p storage/framework/cache
mkdir -p storage/framework/sessions
mkdir -p storage/framework/views
Laravel normally creates them, but on a manual copy or deploy they can be missing.
Typical safe defaults:
chmod -R 775 storage bootstrap/cache
And set the owner to your web server user:
chown -R www-data:www-data storage bootstrap/cache
Replace www-data with apache, nginx, www, or whatever your system uses.
You do not need 777 in almost all cases, and it is not safe on production.
The path in the error is your best clue.
Example:
file_put_contents(/var/www/project/storage/framework/cache/data/33/9f/...):
Failed to open stream: No such file or directory
This tells you:
storage/framework/cache/data/...Fix:
mkdir -p storage/framework/cache/data
chmod -R 775 storage/framework/cache
If the message shows something odd like:
file_put_contents(C:\xampp\htdocs\project\storage\app\public\images):
failed to open stream: Is a directory
it means you passed a directory path instead of a full file path. See the next section.
It’s easy to accidentally point to a folder instead of a file, or build a path incorrectly.
Bad:
$path = storage_path('app/public/images'); // directory
file_put_contents($path, $contents); // will fail
Correct:
$path = storage_path('app/public/images/'.time().'.txt'); // file
file_put_contents($path, $contents);
Also make sure you are not mixing leading slashes:
// This drops the base path if you are not careful
$path = base_path().'/'.'/storage/logs/custom.log';
For Laravel, prefer helpers:
$path = storage_path('logs/custom.log');
In plain PHP, build paths like:
$base = __DIR__; // current script directory
$path = $base . '/storage/logs/custom.log';
On shared hosting, PHP is often restricted to a specific folder using open_basedir.
If you try to write outside that allowed directory, file_put_contents() will fail even if permissions look fine.
You might see errors like:
open_basedir restriction in effect. File(/tmp/cache.txt) is not within the allowed path(s): (/home/username/:/tmp/)
Fix this by:
/home/username/storage/...)open_basedir in php.ini, .user.ini, or the hosting panel (if allowed)For Laravel on cPanel, writing inside storage/ and bootstrap/cache under your account home is usually safe.
Once you repair permissions and paths, it’s a good idea to clear cache so Laravel can rebuild things cleanly.
From project root:
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan route:clear
php artisan optimize:clear
If these commands previously failed with file_put_contents errors, they should now work.
If you want to write a simple log file yourself:
$path = storage_path('logs/custom-info.log');
if (! file_exists(dirname($path))) {
mkdir(dirname($path), 0755, true);
}
file_put_contents($path, '['.now().'] Hello from custom log'.PHP_EOL, FILE_APPEND);
This ensures the parent directory exists and appends safely.
When you see file_put_contents(): Failed to open stream in storage or cache:
storage/ and bootstrap/cache exist and are writablechmod -R 775 storage bootstrap/cache (and chown to the web user)Once paths and permissions are correct, file_put_contents() will quietly do its job and your logs, cache, and uploaded files will be written without errors.
The post PHP file_put_contents(): Failed to open stream in Storage and Cache – Permissions Fix Guide appeared first on Devnote.
]]>The post PHP Class not found in Composer Autoload After Moving Project to New Folder appeared first on Devnote.
]]>The code did not change, only the folder. The usual culprit is Composer’s autoload files and namespaces that still point to the old structure.
Let’s go through what you should check and how to fix it cleanly.
Composer reads composer.json and builds a file called vendor/autoload.php, plus some maps under vendor/composer/.
Most projects use PSR-4 autoloading like this:
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
This means:
App\ namespace live in the app/ directory.App\Services\PaymentService should be in app/Services/PaymentService.php.If you move the project but keep this structure, everything should still work. Problems appear when:
composer.json no longer matches the real folder.So first step is always to refresh Composer’s autoload.
As soon as you move the project to a new folder (or new server), log into that folder and run:
composer dump-autoload
or, for optimized production:
composer dump-autoload -o
This regenerates all autoload maps with the new absolute paths.
If composer is not available globally on shared hosting, use the one in your project:
php composer.phar dump-autoload -o
For many people, this alone fixes the “Class not found” errors.
If errors still appear, check the exact class mentioned in the error.
Example error:
Class "App\Services\PaymentService" not found
Questions to ask:
app/Services/PaymentService.php<?php namespace App\Services; class PaymentService { // ... }PaymentService.php containing class PaymentServiceIf the file is in a different directory after the move, update composer.json or move the file back to the correct PSR-4 path.
If you created a src folder and moved classes there, you must tell Composer:
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
Then run composer dump-autoload again.
If you copied the project manually and skipped the vendor folder, nothing autoloads until dependencies are installed.
Inside the new project folder, run:
composer install
This recreates vendor/ and all autoload files based on composer.lock.
On slow shared hosting, this can take a bit longer, but it is the cleanest way to ensure everything matches the new environment.
In Laravel, moving a project plus changing PHP versions or paths can cause route and container caches to point to the wrong place.
From the project root, run:
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
Then regenerate autoload again:
composer dump-autoload -o
If the error is about a controller in routes:
use App\Http\Controllers\HomeController;
Route::get('/', [HomeController::class, 'index']);
Verify:
app/Http/Controllers/HomeController.php exists.namespace App\Http\Controllers;.class HomeController extends Controller.Sometimes after moving, people rename folders (for example, Controllers to controllers), which breaks on Linux but not on Windows because of case sensitivity.
If you previously added manual includes like:
require '/old/path/to/app/Helpers/helpers.php';
they will break in the new environment.
Better approach is to let Composer autoload helper files:
In composer.json:
"autoload": {
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Helpers/helpers.php"
]
}
After adding this, run:
composer dump-autoload
Now helpers will load automatically, no absolute paths required.
On a new server or folder, sometimes you run artisan or PHP scripts from the wrong directory. Composer then looks for vendor/autoload.php relative to that wrong place.
Always cd into the project root before running commands:
cd /var/www/new-folder/my-project
php artisan migrate
php artisan tinker
If you see errors like:
Warning: require(vendor/autoload.php): failed to open stream
you are probably not in the correct directory, or vendor/ is missing.
/var/www/html to /var/www/projectSymptoms:
Fix:
cd /var/www/projectcomposer dump-autoload -oapp/ and resources/ to a new Laravel installSymptoms:
Fix:
composer.json in the new project has the same autoload section as the old one.app/Services, app/Repositories) as well.composer dump-autoload.You renamed App to MyApp in composer.json:
"psr-4": {
"MyApp\\": "app/"
}
but controllers still use namespace App\Http\Controllers;.
Fix: update namespaces in your PHP files to match MyApp\..., or revert the autoload config.
When you see “Class not found” after moving a PHP or Laravel project:
composer dump-autoload -o in the new folder.vendor folder and run composer install if needed.Once Composer has fresh autoload files and your namespaces match the new directory layout, the “Class not found” errors stop and your moved project behaves like it did before.
The post PHP Class not found in Composer Autoload After Moving Project to New Folder appeared first on Devnote.
]]>The post PHP 8.3 Fatal Error Unsupported operand types – Common Causes and Fixes appeared first on Devnote.
]]>+, -, *, /, ., or += with values that PHP cannot combine anymore as loosely as before. PHP 8.x is stricter than older versions, so code that used to work silently can now break.
Let’s go through the most common situations and how to fix each one.
A very typical example is using + on values that are not numbers.
$a = "10";
$b = "ABC";
$result = $a + $b; // Fatal error in PHP 8.3
Here $b is a string that cannot be safely converted to a number, so PHP throws a fatal error.
Validate or cast your data before you perform arithmetic.
$a = "10";
$b = "ABC";
$a = (int) $a;
$b = is_numeric($b) ? (int) $b : 0;
$result = $a + $b;
Or, if you are getting values from a request or database, sanitize them early:
$qty = (int) ($_POST['qty'] ?? 0);
$price = (float) ($_POST['price'] ?? 0);
$total = $qty * $price;
+ with arrays when you meant to mergeAnother common source of this error is treating arrays like numbers.
$user = ['name' => 'John'];
$extra = ['role' => 'admin'];
$result = $user + 5; // unsupported operand types
$merged = $user + $extra; // works, but not what many expect
Array plus array uses the keys from the left array. It is not the same as merge, and using non arrays on either side will crash.
Use array_merge when you want to combine arrays and never mix arrays with non arrays.
$user = ['name' => 'John'];
$extra = ['role' => 'admin'];
$merged = array_merge($user, $extra); // ['name' => 'John', 'role' => 'admin']
If a value might be null, cast it to an array:
$filters = $requestFilters ?? [];
$defaults = ['status' => 'active'];
$mergedFilters = array_merge($defaults, (array) $filters);
+ instead of .When building strings quickly, it is easy to mix up + and ..
$name = "John";
$msg = "Hello " + $name; // Fatal error
In PHP, + is numeric addition, . is string concatenation.
Use the dot operator for strings.
$name = "John";
$msg = "Hello " . $name;
If you really expect numbers, convert them:
$total = (int) $a + (int) $b;
In many legacy apps a variable might be null and still get used in math. PHP 8.3 is less forgiving.
$discount = null;
$total = 100;
$final = $total - $discount; // unsupported operand types in some cases
Normalize nulls before using them.
$discount = $discount ?? 0;
$final = $total - $discount;
Or use null coalescing assignment:
$discount ??= 0;
$final = $total - $discount;
For strings:
$title = null;
$label = 'Product: ' . ($title ?? 'N/A');
If a function returns an object and you treat it like an integer or float, PHP will complain.
$price = $cart->getTotal(); // returns Money object
$grandTotal = $price + 10; // Fatal error: unsupported operand types
Decide which value you want from the object. Many value objects have methods like getAmount().
$price = $cart->getTotal(); // Money object
$grandTotal = $price->getAmount() + 10;
If this is your own class, consider adding helper methods or converting it to a primitive explicitly.
When you decode JSON or fetch rows from the database, all values may come through as strings. Then this kind of code appears:
$row = [
'qty' => '3',
'price' => 'abc', // corrupted data
];
$total = $row['qty'] * $row['price']; // fatal
Validate and cast before using them.
$qty = (int) ($row['qty'] ?? 0);
$price = (float) ($row['price'] ?? 0);
$total = $qty * $price;
Consider centralizing this logic in DTOs or model accessors, so you do not repeat casting everywhere.
The best long term fix is to be explicit about types.
At the top of your PHP files you can enable strict mode:
<?php
declare(strict_types=1);
Then use type hints:
function calculateTotal(int $qty, float $price): float
{
return $qty * $price;
}
If you accidentally pass an array or string that is not numeric, PHP will throw a helpful TypeError before you get to the unsupported operand error.
In Laravel or other frameworks, add types to your methods, DTOs, and models wherever possible.
+, -, *, ., etc.).+.. for concatenation, not +.?? or explicit casting.Once you get into the habit of being explicit with your types, these fatal errors become rare and much easier to debug when they do happen.
The post PHP 8.3 Fatal Error Unsupported operand types – Common Causes and Fixes appeared first on Devnote.
]]>The post How to Solve Target class controller does not exist in Laravel Route Files appeared first on Devnote.
]]>In this guide, we will walk through all the reasons this happens and how to fix each one.
When you define a route like this:
Route::get('/users', [UserController::class, 'index']);
Laravel tries to find the UserController class. If it cannot load it, you see:
Target class [UserController] does not exist.
So you need to make sure:
Open your controller file, for example:
app/Http/Controllers/UserController.php
You should see something like:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
return view('users.index');
}
}
Pay attention to two things:
UserController.phpUserControllerApp\Http\ControllersIf the namespace does not match the folder path, Laravel will not be able to find the class.
For example, if the file is inside app/Http/Controllers/Admin, then the namespace should be:
namespace App\Http\Controllers\Admin;
And the route should use that full class.
In routes/web.php or routes/api.php, you should reference the controller properly.
use import and ::class (recommended)At the top of web.php:
use App\Http\Controllers\UserController;
Then define the route:
Route::get('/users', [UserController::class, 'index']);
This is the cleanest way and works in Laravel 8, 9, 10, and 11.
Without use, you can write:
Route::get('/users', [\App\Http\Controllers\UserController::class, 'index']);
This also works, but becomes messy with many routes.
In older versions (Laravel 7 and below), you could do:
Route::get('/users', 'UserController@index');
Because Laravel automatically prefixed App\Http\Controllers.
From Laravel 8 onwards, that default namespace was removed. So the old style string syntax without namespace will cause the “Target class does not exist” error.
Wrong in Laravel 8+:
Route::get('/users', 'UserController@index'); // likely to fail
Correct:
use App\Http\Controllers\UserController;
Route::get('/users', [UserController::class, 'index']);
If you migrated an old project to Laravel 11 and kept the old style strings, you will run into this error frequently.
If your controller lives in a subfolder like app/Http/Controllers/Admin/UserController.php, then:
Controller:
<?php
namespace App\Http\Controllers\Admin;
class UserController extends Controller
{
// ...
}
Route file:
use App\Http\Controllers\Admin\UserController;
Route::get('/admin/users', [UserController::class, 'index']);
If the namespace is App\Http\Controllers\Admin, but the route still uses App\Http\Controllers\UserController, Laravel will not find the class and throws the error.
If you created the controller file manually (copy/paste, not using artisan) and Laravel still says the class does not exist, try regenerating Composer’s autoload files:
composer dump-autoload
Then refresh your page.
Using artisan to create controllers is safer because it sets everything up correctly:
php artisan make:controller UserController
If you are using route caching in production, Laravel may still be using an old cached version of your routes.
Clear and rebuild:
php artisan route:clear
php artisan route:cache
During local development you can simply keep caching off and only use:
php artisan route:clear
Then try the route again.
On Linux servers, file names and namespaces are case sensitive.
Make sure:
UserController.php (not userController.php)class UserControlleruse App\Http\Controllers\UserController;If any of these differ by even one letter, you will get the “Target class does not exist” error on the server, even if it worked on Windows locally.
Here is a small working example to compare with your project.
Controller: app/Http/Controllers/PostController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
return 'All posts list';
}
public function show($id)
{
return "Post ID: {$id}";
}
}
Routes: routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/{id}', [PostController::class, 'show'])->name('posts.show');
If this pattern works in your project, adjust your real controllers and routes to match this structure.
app/Http/Controllers/....web.php or api.php using use App\Http\Controllers\YourController;.[YourController::class, 'method'] syntax in routes.composer dump-autoload if you added files manually.php artisan route:clear.Once these pieces are aligned, the Target class controller does not exist error in your Laravel route files will disappear, and your routes will resolve to the correct controllers again.
The post How to Solve Target class controller does not exist in Laravel Route Files appeared first on Devnote.
]]>The post Laravel 11 Queue Jobs Not Running After PHP 8.3 Upgrade – Complete Fix appeared first on Devnote.
]]>php artisan queue:work not processing, or Horizon staying idle. The root cause is usually mismatched PHP binaries, missing extensions, or supervisor still pointing to an old version. Let’s fix them one by one.
.envMake sure you are running the right queue system.
QUEUE_CONNECTION=database
Other valid values:
If you use Redis or database and switch to sync, jobs will run instantly but avoid doing that in production.
After upgrading, php CLI may still point to the old version.
Run:
php -v
If it is not PHP 8.3, workers are running the wrong binary. Use the correct PHP path when starting the worker:
/usr/bin/php8.3 artisan queue:work
On Windows (XAMPP/WAMP/Laragon):
C:\xampp\php\php.exe artisan queue:work
Restart your terminal after changing PATH.
Queue + cache + redis rely on specific modules. Check them:
php -m
For Redis queues you must see:
redis
If not installed:
Ubuntu / Debian:
sudo apt install php8.3-redis
sudo systemctl restart php8.3-fpm
Windows:
Enable in php.ini:
extension=php_redis
Then restart Apache or Nginx.
If using database queue, migrate jobs table:
php artisan queue:table
php artisan migrate
Also verify jobs and failed_jobs tables exist.
Workers don’t auto-update after PHP upgrade. If you use queue:work, stop and restart it:
php artisan queue:restart
This gracefully reloads all workers.
Open your Supervisor file:
/etc/supervisor/conf.d/laravel-worker.conf
Example corrected config:
command=/usr/bin/php8.3 /var/www/html/artisan queue:work --sleep=3 --tries=3
Save and restart:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart all
If you use Laravel Horizon:
php artisan horizon:terminate
Horizon will restart with the new PHP binary.
Also verify Redis connection in .env:
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
On Linux, permission issues can stop workers.
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache
Adjust user accordingly (nginx, ubuntu, ec2-user, etc.).
After switching drivers or upgrading PHP, always clear cache:
php artisan config:clear
php artisan cache:clear
php artisan optimize:clear
.envFollowing these steps will get your Laravel 11 queue system running normally again after upgrading to PHP 8.3.
The post Laravel 11 Queue Jobs Not Running After PHP 8.3 Upgrade – Complete Fix appeared first on Devnote.
]]>The post How to Fix could not find driver Error in Laravel 11 with PHP 8.3 appeared first on Devnote.
]]>php artisan migrate
php artisan tinker
php artisan db
The good news is that nothing is wrong with Laravel itself. The problem is that your PHP installation is missing the required database driver extension, or it is not enabled.
In this guide, we will walk through step-by-step how to fix the could not find driver error for Laravel 11 with PHP 8.3 on different environments.
Laravel uses PDO (PHP Data Objects) to connect to databases. Each database type needs its own driver:
pdo_mysqlpdo_pgsqlpdo_sqlitepdo_sqlsrv (on Windows)If the driver extension is not enabled in PHP, PDO cannot talk to your database, and Laravel shows:
could not find driver
So the fix is simple in theory:
.env settings match the driver you installed.Open your .env file in the root of your Laravel 11 project and look at your database settings:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
The important one is:
DB_CONNECTION=mysql
Common values:
mysqlpgsqlsqlitesqlsrvWhatever is set here is the driver you must enable in PHP.
Run this command in your terminal:
php -m | grep -i pdo
If you are using Windows PowerShell, you can use:
php -m | Select-String "pdo"
You should see something like:
PDO
pdo_mysql
If you only see PDO but not pdo_mysql (for MySQL) or pdo_pgsql (for PostgreSQL), then the specific driver is not enabled. That is why Laravel cannot find the driver.
If you are on Windows, the most common stack is XAMPP, WAMP, or Laragon.
php.ini file used by your CLI: php --ini This shows the path to Loaded Configuration File.php.ini in a text editor (Notepad, VS Code, etc.).;extension=pdo_mysql ;extension=mysqliextension=pdo_mysql extension=mysqliphp -m | grep -i pdo This time you should see pdo_mysql in the list.Sometimes your system has multiple PHP versions installed, and the one in your PATH is not the same as XAMPP/WAMP PHP.
Run:
php -v
Check if the path looks like C:\xampp\php\php.exe or similar. If not, you may need to add the correct PHP folder to your system PATH, or call it with the full path, for example:
"C:\xampp\php\php.exe" artisan migrate
On Linux with PHP 8.3, you need to install the matching extension packages.
For MySQL:
sudo apt update
sudo apt install php8.3-mysql
For PostgreSQL:
sudo apt install php8.3-pgsql
For SQLite, it is usually built in, but if not:
sudo apt install php8.3-sqlite3
After installing, restart your web server and PHP-FPM (if used):
sudo systemctl restart apache2
# or
sudo systemctl restart nginx
sudo systemctl restart php8.3-fpm
Then check:
php -m | grep -i pdo
You should now see pdo_mysql or pdo_pgsql in the list.
If you are using Homebrew PHP on macOS:
php -vwhich php It should be something like /opt/homebrew/bin/php or /usr/local/bin/php.valet restartphp -m | grep -i pdo.env matches the installed driverIf you installed pdo_mysql, then in .env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=your_password
If you installed pdo_pgsql, then:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=your_password
For SQLite:
DB_CONNECTION=sqlite
DB_DATABASE=/full/path/to/database.sqlite
After editing .env, clear the config cache:
php artisan config:clear
php artisan config:cache
Now test your setup:
php artisan migrate
If everything is configured correctly, migrations should run without the could not find driver error.
You can also test quickly using Tinker:
php artisan tinker
>>> DB::connection()->getPdo();
If no error appears, your driver is working.
Here is a quick checklist to fix could not find driver in Laravel 11 with PHP 8.3:
.env:
DB_CONNECTION=mysql or pgsql or sqlite or sqlsrv.php -m | grep -i pdoextension=pdo_mysql (or others) in php.ini.php8.3-mysql, php8.3-pgsql, or php8.3-sqlite3.php artisan config:clear php artisan config:cachephp artisan migrateThe could not find driver error in Laravel 11 with PHP 8.3 is almost always caused by a missing or disabled PDO extension for your chosen database. Once you enable or install the correct driver and make sure Laravel is pointing to the right database and PHP installation, the error disappears.
The post How to Fix could not find driver Error in Laravel 11 with PHP 8.3 appeared first on Devnote.
]]>The post PHP 8.3 new features appeared first on Devnote.
]]>Upgrading isn’t just about new syntax. A new PHP release usually brings:
You don’t need to jump immediately, but if your stack and libraries support 8.3, it’s a good move for maintenance and future-proofing.
Below are the most developer-facing improvements that make day-to-day work nicer. I focus on features that are easy to adopt and that improve code clarity or runtime behavior.
PHP has been moving toward immutability. With the latest releases, readonly properties and class-level readonly declarations let you mark whole classes or individual properties as immutable after construction. That means fewer accidental modifications and clearer intent.
readonly class Settings
{
public function __construct(
public string $appName,
public string $env,
) {}
}
$s = new Settings('devnote', 'production');
// $s->appName = 'changed'; // now disallowed — helps avoid accidental state mutation
Use readonly where object state should not change after creation, config, DTOs, response objects.
Named arguments continue to evolve, making function calls more readable and safer. Expect tighter checks around named arguments and more predictable behavior when arguments change order or optional parameters are added.
sendMail(to: '[email protected]', subject: 'Hello', html: true);
Named args reduce the need for long arrays of options and make calls self-documenting.
Small but useful improvements in error messages and stack traces make debugging faster. You’ll see clearer type-error messages and more actionable stack traces that point to the exact problem line and value.
PHP keeps modernizing random and crypto APIs. Expect simpler interfaces for generating cryptographically secure values and shims that reduce usage of older, error-prone functions.
$token = bin2hex(random_bytes(32));
// or simplified helper in newer versions
$token = random_str(32);
Use the newer APIs where available; they’re safer and more consistent across platforms.
8.3 includes optimizations in the engine and standard library. While microbenchmarks vary, the overall effect is less memory overhead and faster request handling in many workloads, especially for long-running processes and worker jobs.
Here are a few concrete ways you’ll benefit when your projects run on PHP 8.3.
Use readonly objects for request/response DTOs in Laravel to avoid accidental mutation:
readonly class CreatePostDTO
{
public function __construct(
public string $title,
public string $body,
public ?int $authorId = null
) {}
}
Named arguments let you call helper functions and mailers in a way that’s easy to read and refactor-safe:
Mail::to('[email protected]')->send(template: 'welcome', data: $payload);
Random/crypto improvements mean generating job tokens and secure IDs in queue workers is simpler and less error-prone.
composer update on a branch and test. If a third-party lib breaks, either wait or use polyfills/shims.Before you flip PHP version in production:
Upgrading PHP often yields performance gains without code changes, but your mileage depends on workload. Measure real endpoints and background jobs before and after the upgrade. Use flamegraphs or an APM to spot regressions early.
If a critical library you depend on hasn’t declared support for 8.3, or if you’re in the middle of a major release window for your product, wait. Plan a maintenance window and test thoroughly.
PHP 8.3 continues the trend of making PHP code more expressive, safer and easier to maintain. You get better immutability, clearer argument semantics, improved random/crypto helpers and small runtime gains. all of which add up to nicer developer experience and more reliable apps.
The post PHP 8.3 new features appeared first on Devnote.
]]>