DC Blog Blog RSS Feed https://dcblog.dev Laravel how to test CSV download When you have a CSV generated how do you test it runs. Take this extract:

public function export()
{
    $actions = $this->getData()->get();

    $columns = [
        'User',
        'Start Date',
        'End Date',
    ];

    $data = [];
    foreach ($actions as $action) {
        $data[] = [
            $action->user->name ?? '',
            $action->due_at,
            $action->completed_at,
        ];
    }

    $now      = Carbon::now()->format('d-m-Y');
    $filename = "actions-{$now}";

    return csv_file($columns, $data, $filename);
}

The above collects data and sends it to a helper function called csv_file which in turn will export a CSV file.

For completeness it contains:

if (! function_exists('csv_file')) {
    function csv_file($columns, $data, string $filename = 'export'): BinaryFileResponse
    {
        $file      = fopen('php://memory', 'wb');
        $csvHeader = [...$columns];

        fputcsv($file, $csvHeader);

        foreach ($data as $line) {
            fputcsv($file, $line);
        }

        fseek($file, 0);

        $uid = unique_id();

        Storage::disk('local')->put("public/$uid", $file);

        return response()->download(storage_path('app/public/'.$uid), "$filename.csv")->deleteFileAfterSend(true);

    }
}

Now there are 2 tests I want to confirm first, I get 200 status response to ensure the endpoint does not throw an error and secondly, the response contains the generated file name for the CSV.

To cater for the 200 status code run the endpoint and assertOk() which is a shortcut for a 200 instead of assertStatus(200)

Next check the header of the response and read the content-disposition header this will contain an attachment followed by the filename. Doing an assert true and comparing the header with the expected response;

test('can export actions', function () {
    $this->authenticate();

    $response = $this
        ->get('admin/reports/actions/csv')
        ->assertOk();

    $this->assertTrue($response->headers->get('content-disposition') == 'attachment; filename=actions-'.date('d-m-Y').'.csv');
});

Another way would be to use Pest's expect API:

$header = $response->headers->get('content-disposition');
$match = 'attachment; filename=actions-'.date('d-m-Y').'.csv';

expect($header)->toBe($match);

 

]]>
Mon, 11 Jul 2022 09:41:00 GMT https://dcblog.dev/laravel-how-to-test-csv-download https://dcblog.dev/laravel-how-to-test-csv-download
Laravel organise migrations into folders When a project grows the migrations folder can contain a lot of migration, ever wanted to desperate them into folders? turns out it's easy to so. All you need to do is tell Laravel where to read the migrations from.

In your AppServiceProvider.php boot call, you can call $this->loadMigrationsFrom() and give it a path of all the folder locations:

$migrationsPath = database_path('migrations');
$directories    = glob($migrationsPath.'/*', GLOB_ONLYDIR);
$paths          = array_merge([$migrationsPath], $directories);

$this->loadMigrationsFrom($paths);

Now when you run

php artisan migrate

all folders will be scanned.

To migrate specific folders use --path for example for all migration in a folder called posts

php artisan migrate --path=/database/migrations/posts

or to make migration in a folder:

php artisan make:migration create_posts_table --path=/database/migrations/posts

 

]]>
Sun, 03 Jul 2022 12:23:00 GMT https://dcblog.dev/laravel-organise-migrations-into-folders https://dcblog.dev/laravel-organise-migrations-into-folders
Laravel how to set app environment during tests

If you need to set an environment to be a specific one such as staging you can override the environment by changing the config value app_env

config(['app.env' => 'staging']);

Then doing a dd on config(‘app.env’) will return that environment you’ve just set.

I haven’t found a way to set environments with app()->environment() so instead, I recommend using this in_array function and passing in the config(‘app.env’) and then specifically defining the environments. 

in_array(config('app.env'), ['local', 'staging'])

For example, say you have this in a command:

if (! in_array(config('app.env'), ['local', 'staging'])) {
    $this->error(‘Will only run on local and staging environments’);
    return true;
}

Test this runs when the environment is set to production

test(‘cannot run on production’, function () {

    config(['app.env' => 'production']);

    $this->artisan('db:production-sync')
        ->expectsOutput(‘DB sync will only run on local and staging environments’)
        ->assertExitCode(true);
});

 

]]>
Sun, 26 Jun 2022 22:59:00 GMT https://dcblog.dev/laravel-how-to-set-app-environment-during-tests https://dcblog.dev/laravel-how-to-set-app-environment-during-tests
Laravel Sync Remote Database Package

I often want to sync a production database into a local database and manually exporting and importing, so I wrote a package to automate it!

A word of warning you should only sync a remote database into a local database if you have permission to do so within your organisation's policies. I'm syncing during early phases of development where the data is largely test data and not actual customer data.

I wrote a package called Laravel DB Sync

Install the package:

composer require dcblogdev/laravel-db-sync

Publish the config file

php artisan vendor:publish --provider="Dcblogdev\DbSync\DbSyncServiceProvider" --tag="config"

Set the remote database credentials in your .env file

When using SSH Add:

REMOTE_USE_SSH=true
REMOTE_SSH_PORT=22
REMOTE_SSH_USERNAME=
REMOTE_DATABASE_HOST=

REMOTE_DATABASE_USERNAME=
REMOTE_DATABASE_NAME=
REMOTE_DATABASE_PASSWORD=
REMOTE_DATABASE_IGNORE_TABLES=''

REMOTE_REMOVE_FILE_AFTER_IMPORT=true
REMOTE_IMPORT_FILE=true

For only MySQL remote connections:

REMOTE_DATABASE_HOST=
REMOTE_DATABASE_USERNAME=
REMOTE_DATABASE_NAME=
REMOTE_DATABASE_PASSWORD=
REMOTE_DATABASE_IGNORE_TABLES=''

REMOTE_REMOVE_FILE_AFTER_IMPORT=true
REMOTE_IMPORT_FILE=true

if you want to exclude certain tables you can add them to REMOTE_DATABASE_IGNORE_TABLES for example to ignore users and jobs being exported

REMOTE_DATABASE_IGNORE_TABLES='users,jobs'

now when you want to export the remote database into the local database run:

php artisan db:production-sync

https://github.com/dcblogdev/laravel-db-sync

]]>
Sun, 26 Jun 2022 17:50:00 GMT https://dcblog.dev/laravel-sync-remote-database-package https://dcblog.dev/laravel-sync-remote-database-package
How to Drive Traffic to Your Website in 2022: Tips for Web Developers As any web developer knows, getting traffic to your site is essential. Without visitors, your site will never achieve its potential. One way to drive traffic to your site is through copywriting.

Everyone knows the importance of SEO which involves including the right keywords in titles, main body copy and alt tags - while avoiding keyword stuff.

A lesser-discussed strategy is copywriting, which is actually a very valuble tool in SEO.

Why is copywriting important and overcoming its challenges

Copywriting is the art of writing compelling and engaging content that encourages people to visit your site. By crafting compelling headlines and descriptions, you can entice people to click through to your site. Once they're on your site, it's important to keep them engaged with well-written and informative content.

As the internet continues to evolve, search engines are getting smarter and more sophisticated. They are now able to more accurately match people with the content they are looking for. This means that publishers need to create content that is not only keyword-rich but also engaging and interesting.

Gone are the days of simply stuffing a webpage with keywords in hopes of attracting traffic.

In recent years, one of the biggest changes has been the introduction of RankBrain. This artificial intelligence system analyses how people interact with search results, and uses this information to improve the ranking of pages.

One of the things that RankBrain looks at is how long people spend on a page. If people click on a result and then quickly return to the search results, it's a sign that they didn't find what they were looking for. This tells Google that the page isn't relevant or engaging, and so it will be ranked lower in future. 

Now, more than ever, it is important to create content that people will actually want to read.

This means including compelling images, interesting facts, and helpful information.

The process of copywriting can be a challenge for those who are not used to writing large amounts of text. However, the copywriting industry is moving at a fast pace, and there are now tools available that website owners can use to express themselves clearly and concisely. These tools can help to transform the process of copywriting, making it easier and more efficient. By using these tools, website owners can save valuable time and energy, while still producing high-quality content that will appeal to their target audience.

Examples include:

Jasper AI

A writing assistant that takes your basic premise for content and transforms it into marketable web content suitable for blogs and social media.

Grammarly

Ensure your web content meets a good standard of English for grammar and punctuation.

Surfer SEO

Use AI to analyse trends in what people are searching for and find paths to ranking for highly competitive keywords.

Tactics that work in 2022

Here are some copywriting tactics that you can use to drive traffic to your site in 2022:

Use short, punchy descriptions

Make your content easy to read and digest. In copywriting, word choice is everything. Short, punchy descriptions are easy for readers to digest and understand, making them more likely to engage with the text. In contrast, long-winded descriptions can be off-putting and difficult to follow.

When writing copy, it is therefore important to strike the right balance between conciseness and clarity. Using active language and specific detail can help to make complex concepts more accessible, without compromising on precision.

Include images and videos

In an age where visual media is increasingly prevalent, it's important to know how to incorporate images and videos into your copywriting. When used effectively, visuals can help to break up text and add intrigue for the reader. In addition, carefully chosen visuals can also help to underscore key points and add impact to your writing. However, it's important to use visuals thoughtfully - too many images can be overwhelming, and poorly chosen visuals can actually detract from your message. 

Focus on quality over quantity

In an age of ever-decreasing attention spans, it is more important than ever to focus on quality over quantity in copywriting. Gone are the days when readers had the patience to wade through pages of text; today's reader wants information that is clear, concise, and to the point. A well-written piece of copy will deliver this information in an engaging and persuasive manner, without resorting to cheap tricks or filler content.

Infuse a human touch

In a world that is increasingly driven by technology, it is easy to forget the importance of human connection. However, recent studies have shown that businesses that infuse a human touch into their communications are more likely to succeed.

As we move into 2022, this trend is only likely to continue. Consumers are becoming more and more aware of the importance of supporting businesses that treat them like people, not just faceless customers. In order to stay ahead of the curve, businesses need to start rethinking their approach to communication.

Instead of relying on technology to do the work for them, they need to focus on creating messages that resonate with their audience on a personal level. Only then will they be able to build the strong and lasting relationships that are essential for success in today's competitive marketplace.

Create thoughtful content

Today's consumers are more thoughtful in their decision-making process. They care about finding the right product or service to meet their needs, rather than making a quick decision.

As a result, copywriting needs to reflect the benefits of your service in a way that shows people why they should choose you, without putting added pressure into the decision-making process. By doing so, you will be more likely to convert prospects into customers.

Produce Informative copy

Copywriting should share knowledge freely, without using excessive gateways. People want to associate with businesses that properly understand them, so businesses need to become concise about who they can help, how they will help, and why it matters to the people they want to serve.

The goal is not to "trick" someone into buying something they don't need, but rather to educate them about a solution that can make their life better. Good copywriting will be clear, interesting, and easy to digest. It should answer the reader's questions and leave them feeling informed and confident about the product or service being offered. When done well, copywriting can be an invaluable tool for businesses of all sizes.

]]>
Sun, 26 Jun 2022 17:36:00 GMT https://dcblog.dev/how-to-drive-traffic-to-your-website-in-2022-tips-for-web-developers https://dcblog.dev/how-to-drive-traffic-to-your-website-in-2022-tips-for-web-developers
Test Laravel Packages with PestPHP

PestPHP is a testing framework with a focus on simplicity, in this post I'll explain how to use PestPHP within a Laravel package to test its functionality.

Installation

In order to test a Laravel package, a package called testbench is required. Testbench ready more about testbench at https://packages.tools/testbench/getting-started/introduction.html#installation

Install testbench and PestPHP package

composer require orchestra/testbench --dev
composer require pestphp/pest --dev
composer require pestphp/pest-plugin-laravel --dev

Setup test environment

Inside composer.json autoload the tests directory I'm using Dcblogdev\\PackageName to represent the vendor username and repo name.

"autoload": {
    "psr-4": {
        "Dcblogdev\\PackageName\\": "src/",
        "Dcblogdev\\PackageName\\Tests\\": "tests"
    }
},

This will auto-load the tests directory at the root of the package.

Next, create a file called phpunit.xml. 

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  bootstrap="vendor/autoload.php"
  backupGlobals="false"
  backupStaticAttributes="false"
  colors="true"
  verbose="true"
  convertErrorsToExceptions="true"
  convertNoticesToExceptions="true"
  convertWarningsToExceptions="true"
  processIsolation="false"
  stopOnFailure="false"
  xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
  <coverage>
    <include>
      <directory suffix=".php">src/</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.php">./tests/Unit</directory>
    </testsuite>
  </testsuites>
  <php>
    <env name="DB_CONNECTION" value="testing"/>
    <env name="APP_KEY" value="base64:2fl+Ktvkfl+Fuz4Qp/A75G2RTiWVA/ZoKZvp6fiiM10="/>
  </php>
</phpunit>

Here an APP_KEY is set, feel free to change this. Also, the Database connection is set to testing. This also sets the tests inside tests/Unit to be loaded. Add tests/Feature as needed.

Testing

Create a tests folder inside it create 2 files Pestp.php and TestCase.php

TestCase.php

<?php

namespace Dcblogdev\PackageName\Tests;

use Orchestra\Testbench\TestCase as Orchestra;
use Dcblogdev\PackageName\PackageNameServiceProvider;

class TestCase extends Orchestra
{
    protected function getPackageProviders($app)
    {
        return [
            PackageNameServiceProvider::class,
        ];
    }
}

 This loads the package service provider.

Pest.php

<?php

use Dcblogdev\PackageName\Tests\TestCase;

uses(TestCase::class)->in(__DIR__);

In Pest.php set any custom test helpers. This would load the TestCase and test in in the current tests directory using __DIR__ this allows pest to run in sub folders of tests ie Unit and Feature are the most common folders.

Next, create a folder called Unit inside tests and create a test file I'll use DemoTest.php

tests/Unit/DemoTest.php

test('confirm environment is set to testing', function () {
    expect(config('app.env'))->toBe('testing');
});

No imports or classes are required. That's what's great about Pest you can concentrate on writing the test with minimal setup required.

From here you can write your tests using Pest in the same manner as you would writing PestPHP tests in a Laravel application.

]]>
Sun, 26 Jun 2022 10:42:00 GMT https://dcblog.dev/test-laravel-packages-with-pestphp https://dcblog.dev/test-laravel-packages-with-pestphp
Generate PDF and Epub files using Pandoc I write my books using Markdown. Using a tool called Pandoc you can convert Markdown files into PDFs and Epub files. Let's take a look at the commands.

Install Pandoc with Homebrew

brew install pandoc

Converte a .md file to .pdf aka generate a PDF

pandoc demo.md --pdf-engine=xelatex -o demo.pdf

Note when generating a PDF the option --pdf-engine is required.

The syntax is pandoc followed by the source file add any options with the flag. Set the output location and filename with the -o flag.

if you encounter this error:

pandoc: /Library/TeX/texbin/pdflatex: createProcess: posix_spawnp: illegal operation (Inappropriate ioctl for device)

It means xelatex is not installed on the machine.

To install xelatex:

brew tap homebrew/cask
brew install basictex
eval "$(/usr/libexec/path_helper)"
sudo tlmgr update --self
sudo tlmgr install texliveonfly
sudo tlmgr install xelatex
sudo tlmgr install adjustbox
sudo tlmgr install tcolorbox
sudo tlmgr install collectbox
sudo tlmgr install ucs
sudo tlmgr install environ
sudo tlmgr install trimspaces
sudo tlmgr install titling
sudo tlmgr install enumitem
sudo tlmgr install rsfs

Running the command again:

pandoc demo.md --pdf-engine=xelatex -o demo.pdf

You may see:

[WARNING] Missing character: There is no (U+251C) (U+251C) in font [lmmono10-regular]:!

You can either install a font that supports the symbols, often caused by emojis.

Once the above has been corrected you will be able to generate PDF from Markdown files using:

pandoc demo.md --pdf-engine=xelatex -o demo.pdf

To add a table of contents use the option --toc in the command

Make PDF:

pandoc demo.md --pdf-engine=xelatex --toc -o demo.pdf

Make Epub:

pandoc demo.md --toc -o demo.epub

Front Matter

When working with PDF/Markdown you can specify YAML tags in the markdown to set the book title, author and event the cover image with working with EPUB

This should be at the top of the file, it will not be printed.


---
title: Demo Book
creator:
 - role: author
 text: David Carr
cover-image: cover.jpg
---

Write a book

Here is a basic markdown file; this has chapters designated by # (h1) sub headings can be added by using ## (h2)


---
title: Demo Book
creator:
- role: author
 text: David Carr
cover-image: cover.jpg
---

# Chapter 1

An example chapter.....

# Chapter 2

This is super basic

## Sub chapter that belongs to chapter 2

>Markdown is awesome!

# Chapter 3

Convert this to a PDF or Epub file to produce:

Ebook

]]>
Fri, 24 Jun 2022 09:27:00 GMT https://dcblog.dev/generate-pdf-and-epub-files-using-pandoc https://dcblog.dev/generate-pdf-and-epub-files-using-pandoc
Released Laravel TALL AdminTW theme

Laravel TALL AdminTW is a minimal Livewire theme styled with TailwindCSS.

Laravel AdminTW supports both light and dark mode based on the user's OS.

Provided are blade and Laravel Livewire components for common layout / UI elements and a complete test suite (Pest PHP).

Out of the box AdminTW provides:

  • UUID all ids uses UUID's instead of primary keys
  • Multiple Users with an invite system
  • 2FA user opt in and a system wide 2FA enforcement option
  • Audit Logs - track every action
  • Global Search - search any model
  • Livewire components
  • Blade components
  • Roles & Permissions - set that a user can and cannot do
  • Unit and Feature Tests
  • Dark mode support

Read more at the official website https://laraveladmintw.com/

 

2FA

Add additional security to your account using two-factor authentication.

Why do I need this?

Passwords can get stolen – especially if you use the same password for multiple sites. Adding Two-Step Verification means that even if your password gets stolen, your account will remain secure.

Laravel Admin Two Factor Authentication Setup

Dashboard

The dashboard contains a single card ready to be customised as needed.

Laravel Admin Two Factor Authentication Setup

Audit Trails

Record ever action, then review all actions in the audit trail.

Filter by user, action, type or a date range.

Laravel Admin Two Factor Authentication Setup

Sent Emails

All emails sent are recorded and can be viewed and filtered by To, CC, BCC, Subject and created date range.

Laravel Admin Two Factor Authentication Setup

Settings

From the settings change the following:

  • Application Name
  • Force 2FA for all users
  • Change application logo for light and dark mode
  • Change login logo for light and dark mode
  • Lock application down to set IP addresses

Laravel Admin Two Factor Authentication Setup

Roles

Laravel AdminTW comes with role based permissions.

Set what users can and cannot do.

Assign multiple roles to a user.

Laravel Admin Two Factor Authentication Setup

Users

Laravel AdminTW comes with multiple users support, you can add as many users as needed.

Users can be invited to via an email invitation.

Users have their own profile which shows their activity.

Users can be locked down to set IP addresses. Meaning they would only be able to login from those IP addresses listed in the settings.

Laravel Admin Two Factor Authentication Setup

Tests

Laravel AdminTW comes with a suite of tests using PestPHP.

There are 74 tests out the box that ensured the application works as expected.

Laravel Admin Two Factor Authentication Setup

Read more at the official website https://laraveladmintw.com/

]]>
Tue, 07 Jun 2022 16:38:00 GMT https://dcblog.dev/released-laravel-tall-admintw-theme https://dcblog.dev/released-laravel-tall-admintw-theme
My Termial Aliases

What is a terminal alias?

A terminal alias is a shortcut for a terminal command. For example, as a Laravel user, I would type PHP artisan often so I've created an alias of a so I can then type a route:list to get a list of routes instead of having to type out PHP artisan route:list.

Aliases are a great way to be more efficient at using a terminal for common commands.

How to set up terminal aliases

To create an alias you need to edit a .bash_profile or .profile or event a .bash_aliases or a .zshrc when using ZSH 

When using bash I'll use .bash_profile.

Use VIM to open .bash_profile in the home directory if the file doesn't exist it will be created.

vim ~/.bash_profile

To create an alias use the keyword alias followed by the alias name="" then inside the quotes place the command to be aliased.

For example create an alias called ls that will use the command ls -ls -a which will list all files in a vertical list and show hidden files.

alias ls="ls -ls -a"

My Aliases

The following are the alises I use on my machine.

Tools

Open phpstorm or vscode in the current directory, to open VSCode in a folder navigate to the folder and type code . to open the root folder in VSCode.

#tools
alias storm='open -a "/Users/dave/Applications/JetBrains Toolbox/PhpStorm.app" "`pwd`"'
alias code='open -a "/Applications/Visual Studio Code.app" "`pwd`"'

Running Tests

These will run either pest or PHPUnit depending which if pest is installed. Activate by pressing p.

To run a filter for a specific test use the alias pf followed by the method or file name.

# Run tests
function p() {
   if [ -f vendor/bin/pest ]; then
      vendor/bin/pest "$@"
   else
      vendor/bin/phpunit "$@"
   fi
}

function pf() {
   if [ -f vendor/bin/pest ]; then
      vendor/bin/pest --filter "$@"
   else
      vendor/bin/phpunit --filter "$@"
   fi
}

Laravel

alias a="php artisan"
alias t="clear && php artisan test"
alias tp="clear && php artisan test --parallel"
alias phpunit="vendor/bin/phpunit"
alias pest="vendor/bin/pestphp"

Mac show / hide files

# Show/hide hidden files in Finder
alias show="defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder"
alias hide="defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder"

PHP version switch

When PHP is installed via homebrew use these aliases to switch between PHP 8.1 and 7.4

# PHP switcher
alias switch-php8="brew unlink php@7.4 && brew link --overwrite --force php@8.1"
alias switch-php74="brew unlink php && brew link --overwrite --force php@7.4"

Laravel Valet

alias vs='valet secure'
alias tunnel='valet share -subdomain=dc -region=eu'

Edit hosts file

#host file
alias hostfile="sudo vi /etc/hosts"

Composer

#composer
alias cu='composer update'
alias ci='composer install'
alias cda='composer dump-autoload -o'
alias cr='composer require'

GIT

use gac function to GIT Add and Commit files use it like gac . to commit all unstaged files. or use gac a-file-that-changed to commit a specific file.

I alias git to use HUB from GitHub

#git
function gac()
{
   #usage gac . 'the message'
   git add $1 && git commit -m "$2"
}

alias git=hub
alias g="hub"
alias gc="git checkout"
alias gm="git merge"
alias gl="git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
alias gs="git status"
alias gp="git push"
alias gpu="git pull"
alias gno="git reset --hard HEAD"
alias glog='git log --oneline --decorate --graph --all'

IP Lookup

# IP addresses
alias ip="curl https://diagnostic.opendns.com/myip ; echo"
alias localip="ifconfig -a | grep -o 'inet6\? \(addr:\)\?\s\?\(\(\([0-9]\+\.\)\{3\}[0-9]\+\)\|[a-fA-F0-9:]\+\)' | awk '{ sub(/inet6? (addr:)? ?/, \"\"); print }'"

Directory paths

use aliases to make shortcuts for folder locations

#directories
alias ls="ls -ls -a"
alias projects="cd ~/Dropbox\ \(dcblog\)/local/projects"
alias personal="cd ~/Dropbox\ \(dcblog\)/local/personal"

SSH

#ssh
alias sshkey="cat ~/.ssh/id_rsa.pub"
alias sshconfig="vi ~/.ssh/config"
alias copykey='command cat ~/.ssh/id_rsa.public | pbcopy'

Complete collection

#tools
alias storm='open -a "/Users/dave/Applications/JetBrains Toolbox/PhpStorm.app" "`pwd`"'
alias code='open -a "/Applications/Visual Studio Code.app" "`pwd`"'

# Run tests
function p() {
   if [ -f vendor/bin/pest ]; then
      vendor/bin/pest "$@"
   else
      vendor/bin/phpunit "$@"
   fi
}

function pf() {
   if [ -f vendor/bin/pest ]; then
      vendor/bin/pest --filter "$@"
   else
      vendor/bin/phpunit --filter "$@"
   fi
}

#laravel
alias a="php artisan"
alias t="clear && php artisan test"
alias tp="clear && php artisan test --parallel"
alias phpunit="vendor/bin/phpunit"
alias pest="vendor/bin/pestphp"

# Show/hide hidden files in Finder
alias show="defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder"
alias hide="defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder"

# PHP switcher
alias switch-php8="brew unlink php@7.4 && brew link --overwrite --force php@8.1"
alias switch-php74="brew unlink php && brew link --overwrite --force php@7.4"

#valet
alias vs='valet secure'
alias tunnel='valet share -subdomain=dc -region=eu'

#host file
alias hostfile="sudo vi /etc/hosts"

#composer
alias cu='composer update'
alias ci='composer install'
alias cda='composer dump-autoload -o'
alias cr='composer require'

#git
function gac()
{
   #usage gac . 'the message'
   git add $1 && git commit -m "$2"
}

alias git=hub
alias g="hub"
alias gc="git checkout"
alias gm="git merge"
alias gl="git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
alias gs="git status"
alias gp="git push"
alias gpu="git pull"
alias gno="git reset --hard HEAD"
alias glog='git log --oneline --decorate --graph --all'

# IP addresses
alias ip="curl https://diagnostic.opendns.com/myip ; echo"
alias localip="ifconfig -a | grep -o 'inet6\? \(addr:\)\?\s\?\(\(\([0-9]\+\.\)\{3\}[0-9]\+\)\|[a-fA-F0-9:]\+\)' | awk '{ sub(/inet6? (addr:)? ?/, \"\"); print }'"

#directories
alias ls="ls -ls -a"
alias projects="cd ~/Dropbox\ \(dcblog\)/local/projects"
alias personal="cd ~/Dropbox\ \(dcblog\)/local/personal"

#ssh
alias sshkey="cat ~/.ssh/id_rsa.pub"
alias sshconfig="vi ~/.ssh/config"
alias copykey='command cat ~/.ssh/id_rsa.public | pbcopy'

Restart the terminal then type your shortcut and press enter to be taken to the aliased location.

Aliases are really useful and simple to set up.

]]>
Sun, 05 Jun 2022 08:44:00 GMT https://dcblog.dev/my-termial-aliases https://dcblog.dev/my-termial-aliases
Mockery 1 Illuminate Console OutputStyle askQuestion() but no expectations were specified When running a PESTPHP test if you come across this error:

Received Mockery_1_Illuminate_Console_OutputStyle::askQuestion(), but no expectations were specified

Usually caused by caching, clear your cache by running:

php artisan optimize:clear

 

]]>
Sat, 04 Jun 2022 08:46:00 GMT https://dcblog.dev/mockery-1-illuminate-console-outputstyle-askquestion-but-no-expectations-were-specified https://dcblog.dev/mockery-1-illuminate-console-outputstyle-askquestion-but-no-expectations-were-specified
Laravel Security Headers

This weekend, I changed the design of this blog whilst doing so I wanted to add the security headers for content security policies, these tell the application what it can and cannot run, There's a great website called https://securityheaders.com which will scan a URL and tell you what your level is.

If you have no headers set up you'll get an F grade, which is bad! 

Before I started my rating:

an F grade website

Once I finished I have an A rating:

A rating

For detailed information about security headers read Daniel Dušek blog that explains this really well https://danieldusek.com/enabling-security-headers-for-your-website-with-php-and-laravel.html 

Still here? great I'll go over what I've done.

I created a middleware class called SecurityHeaders.php inside App\Http\Middleware of my Laravel application

Add this middleware to the Middleware group inside App\Http\Kernal.php 

protected $middleware = [
    // \App\Http\Middleware\TrustHosts::class,
    \App\Http\Middleware\TrustProxies::class,
    \Fruitcake\Cors\HandleCors::class,
    \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\SecurityHeaders::class,
];

Set the headers to be turned off, this provide would be attackers information about the server, you don't need to advertise these to better to turn them off.

private $unwantedHeaders = ['X-Powered-By', 'server', 'Server'];

Referrer Policy

Sets how much information should be sent with requests, in my case I chose the option to not send the referer header for requests to less secure destinations.

$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');

XSS Protection

Stops loading of pages when they detect reflected cross-site scripting (XSS) attacks

I set: Enables XSS filtering. Rather than sanitizing the page, the browser will prevent rendering of the page if an attack is detected.

$response->headers->set('X-XSS-Protection', '1; mode=block');

Strict Transport Security

informs browsers that the site should only be accessed using HTTPS

$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

Content Security Policy

The content security policy sets weather a browser can run JS / CSS on page load.

You will want to either det a URL specifically or a wildcard like *.domain to allow the subdomain of the given domain to run

$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' platform.twitter.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' * data:; font-src 'self' data: ; connect-src 'self'; media-src 'self'; frame-src 'self' platform.twitter.com github.com *.youtube.com *.vimeo.com; object-src 'none'; base-uri 'self'; report-uri ");

Expect-CT

The Expect-CT header lets sites opt in to reporting and/or enforcement of Certificate Transparency requirements

$response->headers->set('Expect-CT', 'enforce, max-age=30');

Permissions-Policy

Sets what permissions the device load the page is given

$response->headers->set('Permissions-Policy', 'autoplay=(self), camera=(), encrypted-media=(self), fullscreen=(), geolocation=(self), gyroscope=(self), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=(self), usb=()');

The complete class:

With this class in place upload your changes to your server and re-run https://securityheaders.com

<?php

namespace App\Http\Middleware;

use Closure;

class SecurityHeaders
{
    private $unwantedHeaders = ['X-Powered-By', 'server', 'Server'];

    /**
     * @param $request
     * @param  Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (!app()->environment('testing')) {
            $response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
            $response->headers->set('X-XSS-Protection', '1; mode=block');
            $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
            $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' platform.twitter.com plausible.io utteranc.es *.cloudflare.com 'unsafe-inline' 'unsafe-eval' plausible.io/js/plausible.js utteranc.es/client.js; style-src 'self' *.cloudflare.com 'unsafe-inline'; img-src 'self' * data:; font-src 'self' data: ; connect-src 'self' plausible.io/api/event; media-src 'self'; frame-src 'self' platform.twitter.com plausible.io utteranc.es github.com *.youtube.com *.vimeo.com; object-src 'none'; base-uri 'self';");
            $response->headers->set('Expect-CT', 'enforce, max-age=30');
            $response->headers->set('Permissions-Policy', 'autoplay=(self), camera=(), encrypted-media=(self), fullscreen=(), geolocation=(self), gyroscope=(self), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=(self), usb=()');
            $response->headers->set('Access-Control-Allow-Origin', '*');
            $response->headers->set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
            $response->headers->set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With,X-CSRF-Token');

            $this->removeUnwantedHeaders($this->unwantedHeaders);
        }

        return $response;
    }

    /**
     * @param $headers
     */
    private function removeUnwantedHeaders($headers): void
    {
        foreach ($headers as $header) {
            header_remove($header);
        }
    }
}

 

]]>
Sat, 04 Jun 2022 07:50:00 GMT https://dcblog.dev/laravel-security-headers https://dcblog.dev/laravel-security-headers
Livewire clear modal on close When you have a model that's been partially filled and a modal is closed using Alpine ie:

<button type="button" @click="on = false">Close</button>

The modal will close but anything inputted into a form inside the modal will remain.

To clear the form out when a model closes call a method on the component instead:

<button type="button" wire:click="close">Close</button>

Next, create the method in the class and either call $this->reset() to reset all properties on the class or specify the properties to be cleared.

Finally fire an event called close-modal

public function close()
{
    $this->reset(['name', 'email']);
    $this->dispatchBrowserEvent('close-modal');
}

 

]]>
Tue, 24 May 2022 09:58:00 GMT https://dcblog.dev/livewire-clear-modal-on-close https://dcblog.dev/livewire-clear-modal-on-close
Livewire confirm Sweet Alert Using Sweet alerts with Livewire is possible by triggering events from Livewire / AlpineJs and having javascript listen for the event and act when it's triggered.

On a link or button using wire:click we can trigger sending an event using $emit it takes 2 params 

1) the event name
2) the value to pass

<button wire:click="$emit('triggerDelete',{{ $contact->id }})" type="button">Delete</button>

Then in a view when the event is triggered a closure will file. From there a normal Sweet Alert is executed. When a confirmation runs the if result.value runs then triggers a method on the Livewire controller called delete and pass in the value.

@push('scripts')
<script type="text/javascript">
    document.addEventListener('DOMContentLoaded', function () {

        @this.on('triggerDelete', id => {
            Swal.fire({
                title: 'Are You Sure?',
                html: "You won't be able to revert this!",
                icon: 'warning',
                showCancelButton: true,
            }).then((result) => {
                if (result.value) {
                    @this.call('delete',id)
                }
            });
        });
    })
</script>
@endpush

This approach is dead simple and allows Livewire and Sweet Alerts to work together.

You could trigger the event from a Livewire controller if you prefer.

Thanks to BezhanSalleh for posting the solution on Laracasts

]]>
Tue, 24 May 2022 09:36:00 GMT https://dcblog.dev/livewire-confirm-sweet-alert https://dcblog.dev/livewire-confirm-sweet-alert
Image intervention - Image source not readable When using intervention package for uploading images you may come across this error when uploading an image:

Image source not readable

This means the image path cannot be read by intervention. This can happen as intervention uploads to storage when the disk isn't specified.

If you want to use storage then ensure Laravel created a symbolic link:

php artisan storage:link

If you want to use the public disk set your filesystem in .env to public

FILESYSTEM_DRIVER=public

Then in config/filesystems.php:

'public' => [
    'driver'     => 'local',
    'root'       => public_path(),
    'url'        => env('APP_URL').'/images',
    'visibility' => 'public',
],

This will ensure the public disk points to the public folder and set the URL to the application URL followed by an images path.

]]>
Mon, 23 May 2022 11:30:00 GMT https://dcblog.dev/image-intervention-image-source-not-readable https://dcblog.dev/image-intervention-image-source-not-readable
Fatal: Authentication failed (on git push) Problem

Lost authentication from BitBucket resulting in invalid credentials. Pushing to BitBucket and received this response:

remote: Invalid credentials
fatal: Authentication failed for **repo**

Solution

  1. Go to https://bitbucket.org/account/settings/app-passwords/ and create a new app password

  2. Tick all the permissions required, copy the password

In terminal:

On an already-cloned project:

git remote set-url origin https://bitbucket-username:app-password@bitbucket.org/repo-name.git

To clone a new project:

git clone https://bitbucket-username:app-password@bitbucket.org/repo-name.git

Why is it happening?

Beginning March 1, 2022, you will no longer be able to use your Atlassian account password when using Basic authentication with the Bitbucket Cloud REST API or Git over HTTPS.

Solution has been provided from StackOverflow - https://stackoverflow.com/questions/71378839/how-can-i-solve-this-invalid-credentials-problem-on-bitbucket

]]>
Sat, 21 May 2022 17:55:00 GMT https://dcblog.dev/fatal-authentication-failed-on-git-push https://dcblog.dev/fatal-authentication-failed-on-git-push
50% off Modular Laravel book until April 17th

It's my birthday on April 15th. To celebrate, I'm giving 50% off my book Modular Laravel until Monday 17th April from now!

https://dcblogdev.gumroad.com/l/laravel-the-modular-way/DC50

My book explains how to build modular applications using Laravel Modules

You will learn not only the basics of working with modules but how to write tests to ensure everything works within the modules, customise the structure of modules, and write your own base structure that can be generated.

In this book, you will learn

How to generate custom modules with your own structure

How to convert a module to a package

How to install a module package

How to write Test-Driven module development with PestPHP

Thorough walk through & explanations

https://dcblogdev.gumroad.com/l/laravel-the-modular-way/DC50

]]>
Sun, 10 Apr 2022 20:12:00 GMT https://dcblog.dev/50-off-modular-laravel-book-until-april-17th https://dcblog.dev/50-off-modular-laravel-book-until-april-17th
Testing dynamic file uploads with Laravel When working with file uploads where the file name is generated, how do you know what the file name is and then test it exists in a test.

First in a controller doing a file upload with a store call which will store the file into a files folder in the public disk, if you don't need to specify the desk the leave off the second param.

$path = $request->file('name')->store(
    'files',
    'public'
);

//when no disk is required

$path = $request->file('name')->store('files');

Now this method would return the $path as its return.

Then In a test use the storage fake which tells Laravel to fake a file upload

Storage::fake('files');

To fake an upload you can use:

UploadedFile::fake()->image('avatar.jpg')

 To assert the file exists you can check the file in storage:

Storage::assertExists('files/avator.jpg');

But if the file is stored dynamically you won't know the file name in which case you will need to look inside the response from the test. The response is not JSON but a test response. In order to covert this to json use decodeResponseJson on the response. Then you can use json as normal.

Putting it all together:

test('can upload file', function () {

    Storage::fake('files');

    $response = post('api/v1/file', [
        'name' => UploadedFile::fake()->image('avatar.jpg'),
    ])
        ->assertStatus(201)
        ->assertSessionHasNoErrors();

    $path = $response->decodeResponseJson()['data']['path'];

    Storage::assertExists($path);
});

 

 

]]>
Fri, 18 Mar 2022 15:17:00 GMT https://dcblog.dev/testing-dynamic-file-uploads-with-laravel https://dcblog.dev/testing-dynamic-file-uploads-with-laravel
New Laravel Package: Laravel Module Generator

I've been working on a new package for a few weeks, this goes hand in hand with Laravel Modules, to generate modules based on a template module.

https://github.com/dcblogdev/laravel-module-generator/

The idea is you create a module containing everything you want for a starter module. Typically your index, add, edit and delete pages along with migrations, factories and tests.

Once you've for a module you like then replace every reference to the name of the module with placeholders such as {Module} and {Model} these placeholders get replaced when a new module is generated. Once you have a base module building new modules is really fast!

Placeholders:

These placeholders are replaced with the name provided when running php artisan module:build

Used in filenames:

Module = Module name ie Contacts

module = Module name in lowercase ie contacts

Model = Model name ie Contact

model = Model name in lowercase ie contact

For a folder called Models rename it to Entities it will be renamed when back to Models when generating a new module.

Only used inside files:

{Module} = Module name ie PurchaseOrders

{module} = Module name in lowercase ie purchaseOrder

{Model} = Model name ie PurchaseOrder

{model} = Model name in lowercase ie purchaseOrder

{module_} = module name in lowercase ie purchase_orders

{module-} = module name in lowercase ie purchase-orders

Demo

Watch a video demo at https://www.youtube.com/watch?v=DDjAcQolzwM

]]>
Thu, 03 Mar 2022 09:00:00 GMT https://dcblog.dev/new-laravel-package-laravel-module-generator https://dcblog.dev/new-laravel-package-laravel-module-generator
Laravel Modules new docs website

I've recently started helping Nicolas Widart with his awesome package Laravel Modules

I've now released a dedicated documentation website https://docs.laravelmodules.com and added versions 7, 8 & 9 These new versions include details for advanced usages:

  • Artisan Commands
  • Facade Methods
  • Module Console Commands
  • Module Methods
  • Module Resources
  • Languages
  • Tests
  • Publishing Modules
  • Registering Module Events
  • Livewire
  • Spatie Laravel Permission
  • Custom Module Generator

Also a new Twitter account @laravel_modules

[tweet https://twitter.com/laravel_modules/status/1494610708515987465]

]]>
Tue, 22 Feb 2022 08:57:00 GMT https://dcblog.dev/laravel-modules-new-docs-website https://dcblog.dev/laravel-modules-new-docs-website
Best productivity app for mac: Alfred

One of the first apps I install on a fresh mac is an app called Alfred https://www.alfredapp.com/. For me, this is a complete Spotlight replacement.

In this post I'm not going to cover everything that Alfred does... spoiler it's a lot! check out their website for all the details. Instead, I'm covering the features I use most.

Replace Spotlight

The first thing I do on a fresh Mac is to remove Spotlight shortcuts by going into system preferences -> Spotlight -> Keyboard Shortcuts -> Shortcuts then untick both options for searching using Spotlight.

Remove shortcuts from system preferences

I don't use Spotlight at all instead I change the keyboard shortcuts for Alfred to be cmd+space

Alfred keyboard shortcut

Now, whenever I need to search, open an app, perform a workflow or search Google I opt for Alfred, it's very fast to respond.

Search files/images and search engines

Alfred comes with a lot of handy search terms built-in take this list: Search Engines

This allows you to search different search engines for different types of searches for example type maps then press the space bar. The promo changes to search Google and Apple maps then enter a search type to search either option.

Map Search

Use Gmail? you can prompt a search query by typing Gmail followed by a search term press enter and Gmail will open and perform your search.

All of these things you can do manually but Alfred makes it much faster to do.

Another great example: enter a search on Amazon by prefixing your search term with amazon:

Amazon search

Which takes you to https://www.amazon.com/s?k=laravel+the+modular+way

Snippets

I love the snippets feature on Alfred I use this hundreds of times a day no joke! You can create groups of snippets of have one large list. Snippets allows you to store a word, phrase or code that can be pasted when typing a keyword. For instance how many times do you fill in your email address in forms? its a lot I setup a shortcut of :mm which is replaced with my email address.

Anything I use often gets converted into a snippet. I've got a few coding snippets that I can use regardless of what editor I am using at the time.

Snippets

Clipboard

I absolutely love this feature, a history of every time you've copied a piece of text. When enabled Alfred can store a massive amount of past copied so you can then reuse them from your clipboard. This does need turning on and does require the paid version.

Clipboard Settings

You can use the keyboard shortcut to open your clipboard history. This list is your most recent copy. It's searchable and when selected will be in your clipboard memory so it can be pasted.

Clipboard History

Calculator

I'll often use Alfed to perform quick calculations:

calculation

Type a calculation and press enter the value is already copied to your clipboard so it can be pasted anywhere. Its such a simple thing but a big time saver.

Workflows:

Alfred also supports workflows with is a collection of actions that can be performed at once to generate passwords, look up documentation amongst other things.

Generate a password using Alfred using this workflow:

Secure Password Generator | Packal

demo.gif

Lookup docs for PHP, Laravel or Tailwind using the workflows below:

PHP Docs | Packal

Laravel Docs | Packal

Tailwind CSS Documentaion | Packal

Conclusion

This nearly scratched the surface of what Alfred can do, I'm a massive fan. Give it a try it may change how you use your Mac! https://www.alfredapp.com/

]]>
Sun, 02 Jan 2022 20:54:00 GMT https://dcblog.dev/best-productivity-app-for-mac-alfred https://dcblog.dev/best-productivity-app-for-mac-alfred