diceline-chartmagnifierquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

Extracting closures to their own classes

Laravel has a lot of functions that accept a closure as a callback. But sometimes the callback function is just too large to be done inline - so our becomes very unreadable.

Let's take a look at an example of using such a function:

class MyScope
{
    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            function (Builder $query) {
                //really long function
            }
        );
    }
}

If you are used to writing Javascript you might first attempt something like this in order to refactor:

class MyScope
{

    public function callback(Builder $query)
    {
       //really long method
    }

    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            $this->callback
        );
    }
}

Here we extracted the callback to a method and we attempt to pass it as a parameter instead of writing the closure inline.

Unfortunately, this would not work and we would get the following error:

Undefined property: App\Scopes\MyScope::$callback

One valid approach would be to have our method return the function we need instead:

class MyScope
{

    public function callback()
    {
       return function (Builder $query) {
          //really long function
       }

    }

    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            $this->callback()
        );
    }
}

This would do just fine, but we can take this one step further by making use of a powerful PHP feature - invokable classes:

class InvokableClass
{
    //this method will automatically be called and
    //passed the query parameter when the time comes
    public function __invoke($query)
    {
        //really long method
    }
}

This approach is very beneficial because it also allows us to break up the long method into smaller, more understandable pieces (because an invokable class is still a class and we can take full advantage of it) so we are not just sweeping the unreadable code under the rug.

We can now use our freshly defined invokable class in the following way:

class MyScope
{
    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            \Closure::fromCallable(new InvokableClass())
        );
    }
}

Please notice that we called Closure::fromCallable() first - this is necessary because we have to convert our class to a closure to avoid getting an error such as:

Argument 2 passed to Illuminate\Database\Eloquent\Builder::whereHas() must be an instance of Closure or null, instance of App\InvokableClass given

Conditionally loading packages based on environment.

Based on the environment of the project (production or development), there is a way to load packages conditionally. We may want to load packages conditionally to keep an easier to run development server, for instance.

In order to achieve this, we may use the following technique, if the used packages do not already feature a parameter to be used accordingly to the environment.

For instance we may provide an APP_ENVIRONMENT parameter in the .env file of the project. Based on the value of the parameter, we may load or not a package.

.env

APP_ENVIRONMENT=development

nuxt.config.js

let modules = {
  "@nuxtjs/axios",
}

if (process.env.APP_ENVIRONMENT !== "development")
  modules.push("@nuxtjs/component-cache")

Vue + TypeScript App architecture with autonomous service layer implementation

Goal: having an autonomous, central layer for the different services defined in their own modules that will provide all registered services through a custom hook

Step 1: in a new TypeScript file, import all needed services from their modules

import { Service1 } from '@/location1/Service1'
import { Service2 } from '@/location2/Service2'
import { Service3 } from '@/location3/Service3'

Step 2: define the object containing the services, so that we will be able to retrieve the services from it. New services will be imported and registered in this object. After that, extract its type for further need.

const services = {
   service1: Service1,
   service2: Service2
   service3: Service3,
};

type AvailableServices = typeof services

Step 3: define a ServiceProvider class that will internally hold the services object

class ServiceProvider {
  constructor(protected services: AvailableServices) {}

  public provide( serviceName: keyof AvailableServices)
  :InstanceType<AvailableServices[keyof AvailableServices]> {
    return new this.services[serviceName]();
  }
}

Note 1: constructor automatically initialising protected class member "services", no explicit initialiser needed

Note 2: the generic return type of the provide method. It creates the returned type instance based on the constructor function of the service returned

Step 4: create a service provider instance

const serviceProvider = new ServiceProvider(services);

Step 5: define the magic hook that we will be calling from all components for making use of the above defined logic

export function useService( serviceName: keyof AvailableServices,)
:InstanceType<AvailableServices[keyof AvailableServices]> {
  return serviceProvider.provide(serviceName);
}

How to use it inside components:

import { useService } from '@/itsLocation';

const service1 = useService('service1');

Nice to have as a future implementation: each service as a singleton

Customize web page using query parameters with help of $route.query

VueJS offers the possibility to access query parameters so that you can customise a certain's page content based on the given parameters.

For instance, we have the following method and the following query parameters:

https://*link*?message=Custom+Message
    getQuery() {
      if (this.$route.query.message) {
        this.queryMessage = this.$route.query.message
      }
    },

This method will set a variable according to the query parameter "message", that can be further used in the content of the page. For example in a heading:

    <h1>{{ queryMessage }}</h1>

Also, by using the functionality of $router combined with "v-if", the user can render components conditionally.

    getQuery() {
      if (this.$route.query.icon) {
        this.queryIcon = this.$route.query.icon === 'true'
      }
    },
    <svg v-if="queryIcon">

Therefore, when parsing a query containing:

?icon=true

only then, the svg will be rendered.

Conditionally add properties to objects in JavaScript

This comes in handy when you want to completely omit a property from an object in case the value is undefined or null.

{prop1, prop2, ...(condition && prop3)}

The trick being here:

...(condition && prop3)

In case the condition evaluates to false, the property will be completely omitted from the object, otherwise, both the value and its corresponding key will show up in the end result.

Correctly grouping members within modules in JSDoc v3

JSDoc is great! It's documentation not so much. I needed a way to logically group together global methods and I was unsuccessful in finding a straightforward way in its official documentation.

Eventually, I've come across the following method, using @module and @member.

First, we define the module name, usually at the top of the file:

@module MyModule

We then proceed to annotate the target function with @memeber MyModule. One other important aspect is to also include @function along with the name of the function in order to appear in the module section.

/**
 * @member MyModule
 * @function myFunction
 ....
 */
 export const myFunction = () => ....

How to set srcset and sizes attributes for responsive images

The default src attribute acts as fallback for a default image size for browsers that have no support for srcset:

<img src="/images/image.jpg">

The srcset attribute gives the browser a bunch of different sizes of the same image to choose from.

The name of the image doesn't matter, you could name them image-small, image-medium and so on, but the 375w part should match the real width of the image:

<img
    src="/images/image.jpg"
    srcset="/images/image-375.jpg 375w,
            /images/image-768.jpg 768w,
            /images/image-1440.jpg 1440w,
            /images/image-1920.jpg 1920w,
            /images/image-2560.jpg 2560w"
>

Adding the sizes attribute specifies what image size to choose at a specific breakpoint and above (min-width) or below (max-width) it:

<img
    src="/images/image.jpg"
    srcset="/images/image-375.jpg 375w,
            /images/image-768.jpg 768w,
            /images/image-1440.jpg 1440w,
            /images/image-1920.jpg 1920w,
            /images/image-2560.jpg 2560w"
    sizes="(min-width: 1920px) 1400px, (min-width: 1600px) 1000px, (min-width: 768px) 700px, 300px"
>

One thing to note here is that the browser will first calculate the device width and then look at the sizes attribute and choose the first matching condition.

If you set for example sizes="(max-width: 1920px) 1500px, (max-width: 768px) 600px, 300px", and the device width is 640px, the first matching condition will be max-width: 1920px, therefore it will choose the 1500px image width suggestion, rather than the 600 you most probably expected. The same can happen with the min-width if the order is not right.

Notice also that we are asking the browser to choose a 1000px wide image between 1600px and 1919px, but in the srcset sources, there's no image at 1000px. Here, the browser will choose the image with the exact size, if there is one, or the closest one if not, but higher in size than specified, in this case, 1000px. Therefore, the 1440px will be selected.

It can take a while to grasp how this works, but it's worth taking the time to understand it.