Laravel development on macOS
Tags: php, laravel, webdev, mac
Published: 2023-02-19 09:12:00

In this article I want to give you a guide on how to setup your development environment on macOS and start developing PHP applications with Laravel.

## Contents
- [Setup Valet](#setup-valet)
    - [Install components with Homebrew](#install-components-with-homebrew)
    - [Install Valet](#install-valet)
- [Create a Project](#create-a-project)
    - [Use Valet](#use-valet)
- [Laravel Project Structure](#laravel-project-structure)
    - [MVC](#mvc)
    - [Routes](#routes)
    - [Configuration](#configuration)
- [Blade Templates](#blade-templates)
    - [Passing data to views](#passing-data-to-views)
    - [Displaying data in views](#displaying-data-in-views)
    - [Blade directives](#blade-directives)
- [Database Handling](#database-handling)
    - [MySQL](#mysql)
    - [Migrations](#migrations)
    - [Seed database](#seed-database)
    - [Refresh / Wipe database](#refresh-wipe-database)
    - [Creating models](#creating-models)
    - [Creating factories](#creating-factories)
- [Shared elements](#shared-elements)
    - [Partials](#partials)
    - [Components](#components)

## Setup Valet
**Valet** is a light-weight Laravel development environment for macOS, that comes with Nginx. It provides proxies to point all requests on a configured domain to point to installed sites on your local machine.

### Install components with Homebrew
Go to [Homebrew](https://brew.sh) and copy the installation command and run it in your terminal.

```bash
# Update homebrew
brew update

# Install PHP
brew install php

# Install composer
brew install composer
```

Run the following command and see if it says "composer" anywhere:

```bash
echo $PATH
```
If not, you have to add composer to your path. Add the following line to your shell configuration. I use **zsh** as my shell, so I can add it to *~/.zshrc*

```bash
export PATH=$PATH:$HOME/.composer/vendor/bin
```

Refresh your shells configuration:

```bash
# Refresh zsh configuration
source ~/.zshrc
```

### Install Valet
```bash
# Actually install valet with composer
composer global require laravel/valet
valet install
composer global require laravel/installer
```

## Create a Project
Make sure to navigate to the project folder, where you want to create the Laravel project.

```bash
# Create new project
laravel new PROJECT_NAME
```

### Use Valet
Tell Valet to use the current folder as a Laravel project.

```bash
valet park
```

You can now open your project in your IDE of choice and browse to your projects URL (e.g. [http://PROJECT_FOLDER.test](http://PROJECT_FOLDER.test))

If you want to see all "parked" projects execute the following.

```bash
valet parked
```

## Laravel Project Structure
Laravel is organized in the Model-View-Controller structure.

### MVC
**Models** are found under **/app/Models**. These are the data models that might be generated with the help of the Laravel CLI tools.

**Views** are found under **/resources/views**. The views are usually *.blade.php* templates, but they don't have to.

**Controllers** are found under **/app/Http/Controllers**. The controllers are used to handle the business logic and to get the right data with the help of the models, to pass that down to the requested view.

### Routes
Laravel can configure routes for "normal" web applications as well as for an API.

"Normal" routes are configured under **/routes/web.php** and API routes are configured under **/routes/api.php**.

#### Route configuration
Routes can be configured with absolute paths or with dynamic parameters (wildcards) as follows.

```php
// Get all
Route::get('/', function() {
    return view('listings', [
        'heading' => 'Latest Listings',
        'listings' => Listing::all()
    ]);
});

// Get by id
Route::get('/listings/{id}', function($id) {
    return view('listing', [
        'listing' => Listing::find($id)
    ]);
});
```

Query string parameters might be read with the help of the `Illuminate\Http\Request` class.

```php
use Illuminate\Http\Request;

// Get query string parameters
Route::get('/search', function(Request $request) {
    return view('search', [
        'name' => $request->name,
        'city' => $request->city
    ]);
});
```

It is also possible to add constraints to the route, to only allow specific values.

```php
// Get by id -> id has to be a number
Route::get('/posts/{id}', function($id) {
    return view('post', [
        'post' => Post::find($id)
    ]);
})->where('id', '[0-9]+');
```

In order to return a json from an API route you might do the following.

```php
Route::get('/posts', function() {
    return response()->json([
        'posts' => [
            [
                'title' => 'Post One'
            ]
        ]
    ]);
});
```

#### Calling a controller action from a route
If you want to call a controller action in a route you first need to create a controller.

```bash
php artisan make:controller ListingController
```

In the controller you can create public functions for all your actions.

```php
// Show all listings
public function index() {
    return view('listings', [
        'listings' => Listing::all()
    ]);
}

// Show single listing
public function show(Listing $listing) {
    return view('listing', [
        'listing' => $listing
    ]);
}
```

The route definition can than be changed as follows, where we pass in the controller class and the action/function name.

```php
// Get all
Route::get('/', [ListingController::class, 'index']);

// Get by id
Route::get('/listings/{listing}', [ListingController::class, 'show']);
```

#### Route action naming conventions
Common Resource Routes in Laravel are as follows.

**index** - Show all items
**show** - Show a single item
**create** - Show a form to create a new item
**store** - Store a new item
**edit** - Show a form to edit an existing item
**update** - Update an existing item
**destroy** - Delete an existing item

### Configuration
The different system configuration files can be found under **/config**.

#### Database
In the **/config/database.php** file you can configure what type of database should be used (e.g. mysql or sqlite, etc.).

To configure the database connection you can edit the **.env** file in the projects root folder.

## Blade Templates
Blade is a simple templating engine that is already included in Laravel. Blade templates are compiled into plain PHP code.

### Passing data to views
Blade views (templates) may be returned from routes or controllers using the global *view* helper.

```php
Route::get('/', function() {
    return view('greeting', ['name' => 'Finn']);
});
```

### Displaying data in views
The *name* variable can be displayed in the blade template as follows.

```php
Hello, {{ $name }}
```

### Blade directives
Blade directives are escaped by the *@* symbol. These directives function identically to their PHP counterparts. A detailed documentation may be found under https://laravel.com/docs/9.x/blade

```php
@if (count($records) === 1)

    <p>I have one record!</p>

@elseif (count($records) > 1)

    <p>I have multiple records!</p>

@else

    <p>I dont have any records!</p>

@endif

<h1>{{ $heading }}</h1>

@if(count($listings) == 0)

    <p>No listings found</p>

@endif


@foreach($listings as $listing)

    <h2>    
        <a href="/listings/{{$listing['id']}}">{{$listing['title']}}</a>    
    </h2>
    
    <p>{{$listing['description']}}</p>

@endforeach
```

An alternative to check for the existance of items is the ``@unless`` directive.

```php
@unless(count($listings) == 0)
    @foreach($listings as $listing)
        <h2>{{$listing['title']}}</h2>
        <p>
            {{$listing['description']}}
        </p>
    @endforeach
@else
    <p>No listings found</p>
@endunless
```

## Database Handling
Laravel support different database systems like MySQL, SQLite and others.

### MySQL
```bash
# Start MySQL server
mysql.server start

# Login with root
mysql -u root -p

# Stop MySQL server
mysql.server stop
```

#### 1. You should create a dedicated MySQL user for your project
```sql
CREATE USER 'someuser'@'localhost' IDENTIFIED BY 'somepassword';
```

#### 2. Create a database
```sql
CREATE DATABASE somedatabasename;
```

#### 3. Grant the newly created user access to the database
```sql
GRANT ALL PRIVILEGES ON somedatabasename.* TO 'someuser'@'localhost';
FLUSH PRIVILEGES;
```

#### 4. Update the **.env** file in the project root with the database settings


### Migrations
The database schema / structure is managed with database migrations. The migrations can be found under **/database/migrations**.

#### Create a new migration
To create a new migration to create a table named *listings* execute the following command.

```bash
php artisan make:migration create_listings_table
```

#### Add table columns in migration
Open the newly created migration file (something like 2023_02_17_101605_create_listings_table.php) and add the missing columns in the *up* method.

```php
public function up()
{
    Schema::create('listings', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('tags');
        $table->string('company');
        $table->string('location');
        $table->string('email');
        $table->string('website');
        $table->longText('description');
        $table->timestamps();
    });
}
```

#### Run migrations
In order to apply the migrations to the database, execute the following command.

```bash
php artisan migrate
```

### Seed database
To generate "dummy" data in your database you can update the *run* method found in the **/database/seeders/DatabaseSeeder.php** file. Here you can call a factory method.

To actually execute the database seed, execute the following command.

```bash
php artisan db:seed
```

### Refresh / Wipe database
To refresh / wipe the database you can execute the following command. This will re-run the migrations and re-create all the tables. **Attention: This will delete the table data**

```bash
php artisan migrate:refresh
```

If you want to refresh the database, but also want to execute the database seeding you can do the following.

```bash
php artisan migrate:refresh --seed
```

### Creating models
Laravel uses Eloquent as a default **ORM**. To create an Eloquent model you can execute the following command.

```bash
php artisan make:model ModelName
```

### Creating factories
Factories are stored under **/database/factories** and can be created as follows.

```bash
php artisan make:factory FactoryName
```

In the **definition** function you can create the objects with the help of faker.

```php
public function definition()
{
    return [
        'title' => $this->faker->sentence(),
        'tags' => 'laravel, api, backend',
        'company' => $this->faker->company(),
        'email' => $this->faker->companyEmail(),
        'website' => $this->faker->url(),
        'location' => $this->faker->city(),
        'description' => $this->faker->paragraph(5),
    ];
}
```

## Shared elements
It is possible to extend a view in Laravel. By doing so it is possible to define a general page layout in one file and extend this layout by side specific content.

You may create a *_layout.blade.php* file under **/resources/views/**. Add the following to it, where you want to include the side specific content.

```php
@yield('content')
```

When creating the actual side layout you can extend the general page layout as follows.

```php
@extends('_layout')

@section('content')

<h2>{{$listing['title']}}</h2>
<p>{{$listing['description']}}</p>

@endsection
```

### Partials
Sometimes you want to re-use a UI element on multiple pages. For this you can include a *partial* into your side.

Create the folder **/resources/views/partials** and add for example a file called *_search.blade.php* and add your layout (etc.) to it. Then you can include it in the different pages, where you want it to appear as follows.

```php
@include('partials/_search')
```

### Components
*Components* are similar to *partials* with the difference that you are able to provide data (e.g. a Model) to a component.

Create the folder **/resources/views/components** and add for example a file called *listing-card.blade.php* and add your layout (etc.) to it. In order to receive data that was passed to the component add the following.

```php
@props(['listing'])

<h3>
    <a href="/listings/{{$listing->id}}">{{$listing->title}}</a>
</h3>
```

In order to use a component and pass data to it call it as follows.

```php
@foreach($listings as $listing)
    <x-listing-card :listing="$listing"/>
@enddoreach
```

#### Enclosing component
If you want to use a component to enclose something else like if you would create a *card* component with a specific background and border, you can do the following.

Add a new component and insert a *slot* for the positioning of the content to be enclosed. In order to define default css classes you can use the ``$attributes->merge`` function.

```php
<div {{$attributes->merge(['class' => 'bg-gray-50 border border-gray-200 rounded p-6'])}}>

    {{$slot}}

</div>
```

And use it like a "normal" HTML tag. The provided css class will be added to the default classes that are defined in the component itself.

```php
<x-card class="p-10">
    <p>Something in the card component</p>
</x-card>
```