diceline-chartmagnifierquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

How to install npm packages from your local machine

If you need to manually test a package you are developing and don't want to make a release each time you modify something, you can install it directly from your local machine.

You can achieve this by using the following commands, depending on the package manager you use:

yarn add [path-to-your-package]
npm install [path-to-your-package]

This will have the following equivalent in package.json:

...
"dependencies": {
    "[package]": "file:[relative-path-to-package]",
    ...
},

How to use Laravel's Tappable trait to achieve fluency

Given the following schemas:

Schema::create('skills', function (Blueprint $table) {
    $table->id();
    $table->string("name");
});
//pivot table
Schema::create('category_skill', function (Blueprint $table) {
    $table->id();
    $table->foreignId("skill_id")->constrained();
    $table->foreignId("category_id")->constrained();
});
//the categories table will be omitted for brevity

Suppose you have to write a method which created a skill, attached multiple categories to it and returned the created skill afterwards:

class CreateSkillAction
{
    public function execute(SkillDTO $skillData): Skill
    {
       ...
    }
}

You could easily achieve this with the following code:

public function execute(SkillDTO $skillData): Skill
{
    $skill = Skill::create($skillData->attributes());
    $skill->categories()->attach($skillData>categories);
    return $skill;
}

But if you wanted to make it a bit more fluent - akin to using fluent interfaces - you could do the following:

1. Add the Tappable trait to you model:

class Skill extends Model
{
    use \Illuminate\Support\Traits\Tappable;
    ...
}

2. Rewrite the execute method to use the tap method - now present on the model:

public function execute(SkillDTO $skillData): Skill
{
    return Skill::create($skillData->attributes())
        ->tap(fn($skill)=>$skill->categories()->attach($skillData->categories));
}

How to configure path aliases in Jest

If you use webpack aliases for your imports you need to let Jest know about them before you can test your code. You can do this in a jest configuration file - jest.config.js for instance:

module.exports = {
  moduleNameMapper: {
    //an import like: '@/interfaces/Foo.ts' 
    //becomes '[your-root]/src/interfaces/Foo.ts'
    '@/(.*)$': '<rootDir>/src/$1',
  },
  ...
};

If you want to learn how to configure webpack aliases you can read this post

How to trigger model events on pivot tables in Laravel

Suppose we have an app where we have a couple of tasks and users and we want to be able to assign users to tasks.

But what if at the same time we want to know who assigned each user to each task? We can easily achieve this by taking advantage of Laravel's model events - for pivot tables.

We would need 3 tables: one for tasks, one for users and one for the assigning users to tasks:

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string("title");
    ...
    $table->timestamps();
});

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string("username");
    ...
    $table->timestamps();
});

Schema::create('task_user', function (Blueprint $table) {
    $table->id();
    $table->foreignId("assignee_id")->constrained("users");
    $table->foreignId("skill_id")->constrained();
    $table->foreignId("user_id")->constrained();
    $table->timestamps();
});

1. Create the corresponding TaskUser pivot model.

 php artisan make:model TaskUser --pivot

2. Add your event handlers.

class TaskUser extends Pivot
{
    protected static function booted()
    {
        static::creating(function ($pivot_model) {
            // your implementation here
        });
        ...
    }
}

3. Let Laravel know which model it should use for the pivot table.

This is the key - without this step the callback described above will not be executed!

//in the User model
public function tasks()
{
    return $this->belongsToMany(Task::class)
      ->using(TaskUser::class);
}

Now, you can do:

$user->tasks()->attach($tasks);

That's it! Your event callbacks or corresponding observer methods should now be executed.

How use Laravel's Bootable Eloquent Traits

Given the following bootable trait:

trait WithCreator
{
    //no need to define a 'boot' method
    public static function bootWithCreator()
    {
        self::observe(CreatorObserver::class);
    }
}
class CreatorObserver
{
    public function creating($model)
    {
        $model->creator()->associate(auth()->user());
    }
}

We can use it to register the observer without colliding with existing boot methods inside the model.

class Comment extends Model
{
    use HasFactory;
    use WithCreator;
    
    public static function boot()
    {
        ...
    }
}

How to use Makefiles to boost command-line productivity

Makefiles are an awesome tool which can help you become more productive command-line wise by encapsulating long commands and/or sequences of commands. They also help abstract away complexity.

One practical example of creating a make command would be setting up a Laravel project. All you need to do is create a file called Makefile and type in the following.

.PHONY setup
setup: # Setup project
    composer install
    php artisan migrate:fresh --seed
    npm install
    ...

Now, a new developer who needs to setup his/her project can skip writing down that series of commands by simply typing in the following:

make setup

But this doesn't end here. You can create virtually any command to help you automate certain processes such as creating or deleting files, changing permissions etc.

How to use factories to create relationships which follow a sequence

Factories an extremely useful tool for testing. The more complex the use case, the more awesome you find out Laravel factories are.

For instance, we might need to create a few items, each one with a different measurement of its own:

 Item::factory()
    //...
    ->has(Measurement::factory()->state(new Sequence(
        ['length' => 50, "width" => 110],
        ['length' => 65, "width" => 200],
        ['length' => 190, "width" => 295],
    )))
  ->count(3)
  ->create()

This will result in 3 items, each item having one unique measurement assigned. The first item will have a measurement with values corresponding to the first array in the sequence and so on.

How to extend interfaces declared in external libraries in Typescript

Typescript allows us to easily extend types by using module augumentation.

Let's take a look at one quick example - extending the React Material Ui Library Theme.

All we need to do is create a file ending in .d.ts at the root of our Typescript project - in this case I'll name it material-ui.d.ts:

import {
  Theme as MuiTheme,
} from '@mui/material/styles';

declare module '@mui/material/styles' {
  export interface Theme extends MuiTheme {
    customization?: Record<string, string>;
  }
}

How to set default attribute values for Laravel models

The problem:

Given the following schema:

 Schema::create('posts', function (Blueprint $table) {
      $table->id();
      $table->unsignedBigInteger('number_of_hits')->default(0);
      $table->string('title');
  });

We when we create a new post:

$post = new Post(["title" => "test"]);

we might expect the 'number_of_hits' to be 0, but it is null.

The solution:

To fix this, we can easily tell Laravel what default values we want for the model attributes:

class Post extends Model
{
   ...
   protected $attributes = [
        'number_of_hits' => 0,
    ];
}

How to declare path aliases in Typescript

Defining path aliases using webpack can save you a lot of headache when it comes to imports, but you must also let Typescript know about them.

Following my previous post on declaring path aliases using webpack, you can configure your tsconfig.json file to in order to be able to use those aliases in Typescript like so:

{
  ...
  "paths": {
    "@/*": ["./src/*"],
    "images/*": ["./assets/images/*"],
  },
  "include": [
    ...
  ],
}

Of course, all paths for defined aliases must be reachable by Typescript. You can check this post out if you are not sure how to do that.

Otherwise, we are going to get this error:

Cannot find module 'images/[your module]' or its corresponding type declarations.

How to import images in Typescript

Normally, when Typescript cannot find something we get this error:

Cannot find module [your module] or its corresponding type declarations ts(2307)

Now let's see how we can fix this, with a simple example. Given the following folder structure:

│── src
│   ├── resources
│   │   ├── ts
│   │   │   ├── **/*.ts
│   │   ├── images
│   │   │   ├── logo.svg
├── ...
├── tsconfig.json

In order to be able to achieve something like this:

import Logo from '[path]/images/logo.svg'

without any ** Typescript errors**, we need to follow these steps:

1.Make sure that the images folder is "reachable" by Typescript.

Adding this inside tsconfig.json will do the trick:

{
  "include": [
        ...
        "resources/**/*.ts",
   ],
}

A configuration like this one will not do:

{
  "include": [
      "resources/ts/**/*.ts",
   ],
}

Because images/ is not included in ts/ and we don't have any other folders declared, so Typescript can't "reach" it.

2.Let Typescript know about the .svg type in that folder.

In the root of the images folder create a file called index.d.ts:

...
├── resources
│   ├── ts
│   │   ├── **/*.ts
│   ├── images
│   │   ├── logo.svg
│   │   ├── index.d.ts
...

With the following contents:

declare module '*.svg' {
  const value: any;
  export = value;
}

Now you should be good to go.

SQLite vs MySQL - foreign key checks

Sqlite does not check for foreign key integrity when creating tables, only when inserting records. However, MySQL does check in both cases.

To make this more clear, let's take a look at a simple example: given two tables - todos and users - created in this exact order:

 Schema::create('todos', function (Blueprint $table) {
      $table->id();
      $table->string('title');
      $table->unsignedBigInteger('user_id');
      $table->foreign('user_id')
          ->references('id')
          ->on('users');
  });
 Schema::create('users', function (Blueprint $table) {
      $table->id();
      $table->string('username');
      $table->string('password');
  });

If you run your migrations using the sqlite driver, everything works just fine.

However, if you run your migrations using the mysql driver, you get the following error:

SQLSTATE[HY000]: General error: 1005 Can't create table `[your-app]`.`todos` 
(errno: 150 "Foreign key constraint is incorrectly formed") 
(SQL: alter table `todos` add constraint `todos_user_id_foreign`
 foreign key (`user_id`) references `users` (`id`))

Folder aliases using webpack

In order to define a folder alias using webpack, all you have to do is write the following in webpack.config.js:

const path = require('path');

module.exports = {
  ...
  resolve: {
    alias: {
      '@': path.resolve('src'),
      images: path.resolve('assets/images'),
    },
  },
};

Now , if you want to import something from assets/images, all you need do to is

import image from 'images/[path]'

regardless of the current directory.

If the location of any of the folders defined with aliases ever changes, you no longer need to update all the imports - changing the path in webpack.config.js will take care of everything.

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