Why Laravel Is Not Always Fun Anymore Due To Its Upgrading Cycle

I used to develop projects with PHP only, without a framework.

Different classes, functions, etc.

Until it became a mess and I was looking for something more structural.

Laravel came onto my journey and I love(d) it.

Finally having a structured way of writing projects with Model, View and Controller structures with the Blade engine and the elegant Eloquent Model.

The problem

The problem is: the projects that I built before 2015 with PHP4 and PHP5, they basically still run and work (only having switched from mysql to mysqli or PDO). Even if I upgrade them to PHP7 or PHP8.

My Laravel projects have turned into monsters that require self-care every six months in the tight update schemes of Laravel and PHP.

  • Updating to a new version of Laravel takes 15 minutes to 1-2 hours, according to Laravel.
  • In reality, things break. Especially, third-party packages can’t keep up with the tight upgrade schedule of Laravel.
  • An example is when upgrading from Laravel 6 to Laravel 7. Laravel 7 supports PHP8, while some first-party packages like Telescope version 3.* only work with PHP7. And some third-party packages only work with Laravel 7 and with PHP8. This makes the hopping from version to version very hard.
  • Even first party packages phase out quickly. The required substitute is not always the best. For example Laravel Breeze instead of the laravel/ui package.

This is not sustainable for an indie or sole entrepreneur.

It is also probably one of the reasons that this Laravel developer estimates that more than 60% of Laravel sites run a version that is older than 2 years.

Everything running with subfunctionalities that change.

E.g. compiling your stylesheets needs to happen with: Gulp/Elixir in 2015, then with Mix, now with Laravel Vite.

When running npm run, it usually gives an error that you need to debug.

I see the point, Laravel is still fun for big projects and organizations.

And yes, for most projects it is probably still easier to build on a great boilerplate, than the start from scratch.

But for small organizations that have little developers, it is pretty tough to update everything.

Is Laravel Shift the solution?

Laravel Shift could help, but with a price of $29 per upgrade, this is not too expensive if you take expensive western business programming prices into account.

For less developed countries and people that have less financial resources, upgrading an app a few versions to the newest Laravel version can easily cost a few hundred dollars.

The awesome of Laravel

I don’t want to let this article sound too negative.

It is just a desire for Laravel users and developers to perhaps start a brainstorm if there is a way to keep the core less variable or more resilient to future PHP-upgrades, that would hopefully result in more up-to-date sites with the latest PHP (and Laravel) version.

Anyway, I do not see the perfect solution. I understand that a web app takes some care.

Perhaps an easier long-term support (LTS) version that doesn’t break every half year with more robust core functionalities.

Just to recap, there are many pros about Laravel:

  • Less spaghetti-code
  • Great structure
  • So much options and first party packages. Like Vapor for serverless, Cashier for billing, etc.
  • Large community

Have any other suggestions? Don’t hesitate to dropping them in the comment.

Setting a Cookie on a Subdomain from an AJAX Request with PHP

Cross-domain requests have always been a challenging aspect of web development. When it comes to sending AJAX requests from a subdomain to another subdomain, managing cookies can be even more complex. However, with the right techniques and configurations, it’s possible to set cookies on subdomains securely and efficiently. In this article, we’ll explore how to achieve this using PHP, while ensuring proper security measures are in place.

Understanding the Cross-Domain Cookie Challenge

Cross-domain requests can pose a security risk, as they might allow malicious websites to access sensitive data. To mitigate this risk, web browsers enforce a security mechanism known as the “Same-Origin Policy.” This policy restricts web pages from making requests to a domain that is different from the one that served the web page. However, there are legitimate use cases where cross-domain requests are necessary.

In this scenario, we want to set a cookie on a subdomain (e.g., app.yourdomain.com) from an AJAX request originating from another subdomain (e.g., www.yourdomain.com). To achieve this, we need to configure both the server-side and client-side components correctly.

Server-Side Configuration with PHP

On the server side, we need to handle incoming requests and respond with the appropriate headers to allow cross-origin requests and cookie setting. Here’s a PHP code snippet that demonstrates the necessary server-side configuration:

<?php // Enable CORS (Cross-Origin Resource Sharing) to allow requests from other domains.
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Origin: https://www.yourdomain.com');
// Replace with your domain. // Set the cookie on the subdomain.
$cookieName = 'myCookie'; 
$cookieValue = 'Hello from the subdomain!';
$cookieDomain = '.yourdomain.com'; 
// Notice the leading dot for subdomains.
$cookiePath= '/';
$cookieSecure = true;
// Set to true if using HTTPS.
$cookieHttpOnly = true; // Improve security by preventing JavaScript access.
setcookie($cookieName, $cookieValue, 0, $cookiePath, $cookieDomain, $cookieSecure, $cookieHttpOnly);
// Return a response to the AJAX request.
echo 'Cookie set on subdomain!';

This PHP code snippet does the following:

  1. Enables CORS by setting appropriate headers to allow cross-origin requests.
  2. Sets a cookie with the specified name, value, and configuration.
  3. Responds with a message to confirm that the cookie has been set.

Client-Side Configuration with JavaScript/jQuery

On the client side, you must configure your AJAX request to handle credentials and cross-origin requests properly. Below are two examples: one using jQuery and the other using the Fetch API.

Using jQuery:

$.ajax({ url: 'https://app.yourdomain.com/api.php', 
// Replace with your subdomain API URL.
xhrFields: { 
withCredentials: true // Allow cookies in cross-origin requests. 
},
success: function(response) { console.log(response); // Output: "Cookie set on subdomain!" } });

Using the Fetch API:

fetch('https://app.yourdomain.com/api.php', { credentials: "include" }) .then(response => response.text()) .then(data => {console.log(data); });

In both examples, the key is to include the withCredentials option in your AJAX request configuration. This tells the browser to include any cookies associated with the subdomain when making the request.

Security Considerations

When working with cookies and cross-origin requests, security should be a top priority. Here are some security considerations to keep in mind:

  1. Secure Communication: Ensure that your subdomain uses HTTPS to encrypt data in transit.
  2. HTTP-Only Cookies: Set the HttpOnly flag on cookies to prevent client-side JavaScript access, which can help mitigate XSS (Cross-Site Scripting) attacks.
  3. Domain Scope: Be cautious when setting cookies at a broad domain scope (e.g., .yourdomain.com). Only do this if necessary, as it can expose cookies to other subdomains.
  4. Access Control: Restrict the origins allowed to access your subdomain using the Access-Control-Allow-Origin header.
  5. Authentication and Authorization: Implement proper authentication and authorization mechanisms to ensure that sensitive data is not exposed to unauthorized parties.

By following these best practices, you can safely set cookies on subdomains from AJAX requests, allowing your web applications to work seamlessly while maintaining security.

Laravel Tinker remove history

Do you want to remove the Laravel Tinker history when you are in a Laravel Tinker session with

php artisan tinker

You can then run the command:

>>> history --clear

Within Laravel Tinker.

Method 2 to remove Tinker History

There are also traces left behind in the following folder

~/.config/psysh

The file is called psysh_history

If you remove it, you will also remove the Laravel Tinker history of other Laravel instances on your server of Homestead environment. Be aware of this.

Laravel Homestead: 502 Bad Gateway after update

When updating Laravel Homestead to a newer version, in some case, there can be a Nginx 502 Bad Gateway error.

You can solve this by:

  1. Go to /var/log/nginx and view your test domain’s .log file.
  2. In our case we saw an error like:

    *10 connect() to unix:/var/run/php/php7.4-fpm.sock failed (2: No such file or directory) while connecting to upstream.
  3. In our case, just a restart of PHP was enough:

    sudo service php7.4-fpm restart

    Replace php7.4 with your PHP version.

This solved it for us.

Did you find another cause and solution? Let us know in the comments.

How to Fix: PHP Fatal error: Uncaught RedisException: ERR AUTH called without any password configured for the default user. Are you sure your configuration is correct?

When setting up PHP Redis in a way like Digital Ocean on a localhost, you might get this error:

Fatal error: Uncaught RedisException: ERR AUTH called without any password configured for the default user. Are you sure your configuration is correct?

We assume your PHP script starts like this:

<?php

$redisPassword = "";

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth($redisPassword);

Because this is a localhost, we do not need a password for Redis. That is why we also do not need to use the $redis->auth() part. We can leave this out.

Or if you are using environment variables, you can use:

<?php

$redisPassword = "";

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

if (!empty($redisPassword)
{
    $redis->auth($redisPassword);
}

If you are on a production server, it is wise to use a password, of course and not leave the $redis->auth() part out.

Happy coding!

Embedding OpenStreetMap in a Privacy-Friendly Way with Leaflet JS

Embedding OpenStreetMap (OSM) on your website can provide valuable location information to your users. However, due to the General Data Protection Regulation (GDPR), concerns may arise regarding the collection of data like IP addresses by OSM. To address these concerns and try to create GDPR compliance regarding OSM, this developer blog explores an approach to proxy OSM in a privacy-friendly manner using a caching proxy and Leaflet JS.

Using a Caching Proxy:

To mitigate data collection concerns and ensure GDPR compliance, you can set up a caching proxy, such as TileCache, which acts as an intermediary between your website and OSM. The caching proxy allows you to store the map tiles locally, reducing the need for direct data requests to OSM servers.

Setting up TileCache:

To implement TileCache, follow these steps:

  1. Install TileCache: Begin by installing TileCache on your server. You can find detailed instructions in the TileCache GitHub repository (https://github.com/cyclestreets/tilecache). Configure the caching proxy to create a directory, such as /tilecache, within your website’s public_html domain.
  2. Replace the OpenStreetMap URL in Leaflet JS: Locate the JavaScript code where the OSM map is embedded using Leaflet JS. Replace the original OSM URL with the URL pointing to your TileCache setup. For example:
L.tileLayer('YOURDOMAIN.com/tilecache/?layer=mapnik&z={z}&x={x}&y={y}', { attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);

So the setup script of LeafLet.JS becomes (don’t forget including the JS + CSS scripts):

var map = L.map('map').setView([51.505, -0.09], 13);

L.tileLayer('YOURDOMAIN.com/tilecache/?layer=mapnik&z={z}&x={x}&y={y}', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

L.marker([51.5, -0.09]).addTo(map)
    .bindPopup('A pretty CSS popup.<br> Easily customizable.')
    .openPopup();

Ensure that the caching script is configured to store the cached tiles securely in a protected directory.

Alternative Approaches:

In addition to TileCache, there are other simpler proxy solutions available, such as ProxySimplePHP (https://wiki.openstreetmap.org/wiki/ProxySimplePHP). However, it is essential to note that these alternative approaches have not been tested by us.

Disclaimer and Legal Considerations:

It is important to understand that while implementing a caching proxy and replacing the OpenStreetMap URL with Leaflet JS can enhance privacy and mitigate data collection concerns, we cannot guarantee complete GDPR compliance. Every website’s GDPR compliance requirements may vary, and there might be other requests or considerations to address. Therefore, it is recommended to seek legal advice or consult with a GDPR expert to ensure full compliance.

Also be sure to understand that this measure will highly increase bandwidth to your server/hosting as your server/hosting is displaying all the tile images know.

Conclusion:

Embedding OpenStreetMap in a privacy-friendly manner is crucial for maintaining GDPR compliance and respecting user privacy. By implementing a caching proxy like TileCache and replacing the OpenStreetMap URL with your TileCache URL in Leaflet JS, you can reduce data collection concerns and enhance privacy protection.

However, it is essential to understand that legal requirements can vary, and additional measures might be necessary to achieve full GDPR compliance. It is always advisable to consult with legal experts to ensure your website adheres to all applicable data protection regulations while providing a seamless and privacy-conscious user experience.

Using PHP LinkedIn SDK to fetch Company and Profile Information via API

LinkedIn has an API that makes it possible to:

  1. Fetch Profile Information like name, email, and updates
  2. Fetch Company Information like name, email and updates
  3. Post to a Profile
  4. Post to a Company
  5. And more

There’s little written about a PHP implementation for the LinkedIn API. We’ll try to give the best articles and SDKs for PHP to kickstart your LinkedIn API implementation.

One important note: LinkedIn needs an access token that you’ll receive via OAuth2. It needs to be refreshed every 60 days. And this tutorial is (of course) on your own risk.

Create an app: Client ID and Secret

In order to start, you’ll need to create an app in LinkedIn Developers. You can do that here: https://www.linkedin.com/developers/apps . Make sure that you’re entering the redirect URL’s from where you’re starting the OAuth2 process, else the app won’t work.

Then you’ll receive a Client ID and Secret. Save those two.

Install the LinkedIn SDK

Install the LinkedIn SDK, we’re using this one: https://github.com/zoonman/linkedin-api-php-client. You can install it via Composer:

composer require zoonman/linkedin-api-php-client

After that, it’s installed. Then, you can use the example script here: https://github.com/zoonman/linkedin-api-php-client/blob/master/examples/index.php. You don’t need getenv, you can change these values to your client id and secret values:

$client = new Client(
    'YOUR_CLIENT_ID',
    'YOUR_CLIENT_SECRET'
);

The demo script also posts some things to your LinkedIn profile page + company page, so make sure to comment that out, so that it doesn’t happen. Then you can sign in and do your first OAuth request.

If everything works, you’ll get some profile + company info. Check your LinkedIn profile + company profile to make sure that nothing has been shared.

Saving the token

You can save the token somewhere (safely stored). And call it again. You can then modify the demo script that it initializes with the token:

// add Composer autoloader
include_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor/autoload.php';

// import client class
use LinkedIn\Client;
use LinkedIn\Scope;
use LinkedIn\AccessToken;

// instantiate the Linkedin client

$client = new Client(
    'YOUR_CLIENT_ID',
    'YOUR_CLIENT_SECRET'
);

// load token from the file
$token = 'YOUR_TOKEN';
$expires = 'EXPIRY';
// instantiate access token object from stored data
$accessToken = new AccessToken($token, $expires);

// set token for client
$client->setAccessToken($accessToken);

if (!empty($token))
{
	// Do the client magic here!
}

You can put this information in a separate file as well and run it. Then you’re good to go!

Relevant resources

  1. https://medium.com/@ellesmuse/how-to-get-a-linkedin-access-token-a53f9b62f0ce
  2. https://www.linkedin.com/developers/
  3. https://github.com/zoonman/linkedin-api-php-client

Good luck. Got any tips? Leave a comment below.

Laravel 5: simple multi-tenant/multi-site model setup (many-to-many relation covered as well)

Laravel multi site multi tenant navigation bar illusatrationLaravel 5 is a brand new version of the popular framework. Unfortunately, there are no good multi-tenant setups for Laravel 5 yet. That’s why I’d decided to create my own simple version as well, based on Eloquent Global Scopes.

In my case I wanted a sites table, with categories that have a many to many relationship. So like:

sites
id | name

categories
id | name

site_category
id | site_id | category_id

So this table structure is a many to many relation. With this setup you can create a multi-site setup with different categories. Site A has categories 1 and 2, Site B has category 3 etc. It’s defined in the model as follows:

App\Site.php

belongsToMany('App\Category', 'site_category');
    }
}

And the category like:

App\Category.php

belongsToMany('App\Site', 'site_category');
    }

Notice that this category has the trait TenantableTrait assigned. That is like:

App\Traits\TenantableTrait.php

And finally, don't forget the global Eloquent scope:

App\TenantScope.php

<?php namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ScopeInterface;
use Session;

class TenantScope implements ScopeInterface
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        if (Session::has('siteId'))
        {
            $siteId = session('siteId');
          
            $builder->whereHas('sites', function($query) use($siteId)
            {
                $query->where('sites.id', $siteId);
            });
        }
    }

    /**
     * Remove the scope from the given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function remove(Builder $builder, Model $model)
    {
        
        dd('remove called');
    }
}

Now, if you set the site id by session (e.g.) in a controller with:

Session::set('siteId', 1)

A all categories that have a site_category link with site_id=1 will be called. As you see, the remove method still has to be specified, so if you'd like to finish up this script, leave it in the comments.

You can repeat this with site_products or probably even site_category_product (with some finetuning, if that works, let me know below 🙂 ).

Magento A/B testing or Multivariate testing – Php script

Split test A/B PHP Script

Magento is quite limited in tools like conversion optimalisation like A/B testing or multivariate testing. That is why I created a very simple php script that you can put directly into a phtml file. It’s a bit hacky, that it’s not via the core, but it works though and is fast. This script has its limitation, see the note on the bottom.

If you want to variate the text of a button on the product page and go to a file like /app/design/frontend/default/default/catalog/product/view.phtml and insert this into the header (in the php-code):

srand((double)microtime()*1000000);

$var = array();
$var[1]['name'] = 'direct';
$var[1]['value'] = 'Buy direct';
$var[2]['name'] = 'purchase';
$var[2]['value'] = 'Purchase product';
$var[3]['name'] = 'invest';
$var[3]['value'] = 'Invest in product';

$choice = cookieCheck($var);

function cookieCheck($var)
{
	$cookie = Mage::getSingleton('core/cookie');
	$cookievalue = $cookie->get('variation_test');
	if (isset($cookievalue) && ($cookievalue > 0))
	{
		$choice = $cookievalue;
	}
	else
	{
		$choice = rand(1,count($var));
		$cookie->set('variation_test', $choice ,time()+30*86400,'/');
	}
	return $choice;
}

Then create a piece of text like a link or a button. That is where the variation takes place:

<? echo $var[$choice]['value']; ?>

If you want to track the variable in Google Analytics, you can edit the template file /app/design/frontend/default/default/googleanalytics/ga.phtml , so it gets like:

 srand((double)microtime()*1000000);

$var = array();
$var[1]['name'] = 'direct';
$var[1]['value'] = 'Buy direct';
$var[2]['name'] = 'purchase';
$var[2]['value'] = 'Purchase product';
$var[3]['name'] = 'invest';
$var[3]['value'] = 'Invest in product';

$choice = cookieCheck();

function cookieCheck()
{
	$cookie = Mage::getSingleton('core/cookie');
	$cookievalue = $cookie->get('variation_test');
	if (isset($cookievalue) && ($cookievalue > 0))
	{
		$choice = $cookievalue;
	}
	else
	{
		$choice = rand(1,count($var));
		$cookie->set('variation_test', $choice ,time()+30*86400,'/');
	}
	return $choice;
}
 
 
?>
isUserNotAllowSaveCookie()): ?>

  


Note: This script could not work properly if you’re using Full Page Cache. Also it could not work with the page block html cache. So, work in progress …

jQuery Address and History Forms – Remembering form choices in hash / browserstate (updated version)

For jQuery hash support I like the jQuery Address plugin: http://www.asual.com/jquery/address/ . Especially, if you want to make a form or product filter, you can use this variant: http://www.asual.com/jquery/address/samples/form/ .

Though, it doesn’t support checkboxes in the form of arrays like check[]=1&check[]=2 , etc. I updated the plugin with some other libraries and code and now it works.

You can download it here.