The master branch is under active development and functionality may change or break at any time!
Getting started

Upgrading



13.0

This portion of the guide takes you through the steps needed to migrate from Mako 12.* to 13.0.

While most applications won't be affected by every change, we recommend reviewing the entire guide to make sure your application is ready for 13.0.

Check out the changelog to view a complete overview of all changes and new features of this release.

Dotenv loader

In Mako 13, we added a dotenv loader for use in development environments. This allows you to define environment variables in a .env file located in the application root directory. Make sure to add the .env file to your .gitignore file (or equivalent ignore file for your VCS) to prevent sensitive values from being committed.

Add the following to the end of your init.php file to enable it:

/*
 * Load .env if it exists.
 */
if (file_exists($dotenv = dirname(__DIR__) . '/.env')) {
	(new DotenvLoader)->load($dotenv);
}

Controller helper

The fileResponse, streamResponse, and jsonResponse methods of the ControllerHelperTrait (used by the optional Mako base controller) have been removed. The underlying functionality remains available, but you must now use the corresponding response classes directly.

Deprecated method Class
fileResponse mako\http\response\senders\File
streamResponse mako\http\response\senders\Stream
jsonResponse mako\http\response\builders\JSON

File streams:

1 - return $this->fileResponse('/files/file.ext'); 
2 + return new File('/files/file.ext'); 

Custom stream responses:

// Before

return $this->streamResponse(function ($stream) {
	$stream->flush('Hello, world!');

	sleep(2);

	$stream->flush('Hello, world!');
}, 'text/plain', 'UTF-8');

// Now

return new Stream(function () {
	yield 'Hello, world!';

	sleep(2);

	yield 'Hello, world!';
}, 'text/plain', 'UTF-8');

Note that calls to the Stream::flush() method have been replaced by yielding.

JSON responses:

1 - return $this->jsonResponse([1, 2, 3]); 
2 + return new JSON([1, 2, 3]); 

Enum cases

All enum cases have been converted from UPPER_SNAKE_CASE to PascalCase to follow the standard PHP convention for naming enum cases. The reason they were previously in UPPER_SNAKE_CASE is that many of them were converted from classes with class constants.

1 - Status::NOT_FOUND 
2 + Status::NotFound 

Here's a list of the affected enums:

  • mako\cli\input\Key
  • mako\database\query\SortDirection
  • mako\database\query\VectorDistance
  • mako\env\Type
  • mako\file\Permission
  • mako\gatekeeper\LoginStatus
  • mako\http\response\Status
  • mako\http\response\senders\stream\event\Type
  • mako\pixel\image\operations\AspectRatio
  • mako\pixel\image\operations\Flip
  • mako\pixel\image\operations\WatermarkPosition
  • mako\pixel\metadata\xmp\properties\Type

Query builder sorting

The option to pass the string values ASC and DESC to the following methods has been removed in favor of the SortDirection enum:

  • Query::orderBy()
  • Query::orderByRaw()
  • Query::orderByVectorDistance()
1 - $query->orderBy('id', 'DESC') 
2 + $query->orderBy('id', SortDirection::Descending) 

The $order parameter of these methods has been renamed to $sortDirection. This is only relevant if you are using named parameters:

1 - $query->orderBy('id', order: SortDirection::Descending) 
2 + $query->orderBy('id', sortDirection: SortDirection::Descending) 

You may still use the following methods without any changes:

  • Query::ascending()
  • Query::descending()
  • Query::ascendingRaw()
  • Query::descendingRaw()
  • Query::ascendingVectorDistance()
  • Query::descendingVectorDistance()

HTTP statuses

You can no longer use an integer to set the response status code. A Status enum case or StatusInterface implementation must be used instead.

1 - $response->setStatus(404) 
2 + $response->setStatus(Status::NotFound) 

The following Status enum cases have been renamed to conform with the HTTP standard:

Old name New name
Status::PayloadTooLarge Status::ContentTooLarge
Status::UnprocessableEntity Status::UnprocessableContent

The Status::UseProxy case has been deprecated per ⁠RFC 7231.

The following non-standard Status enum cases have been removed:

  • Status::InvalidToken
  • Status::TokenRequired

If you still need to use these status codes, you can use the CustomStatus class instead:

1 - $response->setStatus(Status::InvalidToken) 
2 + $response->setStatus(new CustomStatus(498, 'Invalid Token')) 

Uploaded files

The UploadedFile::moveTo() method now returns a FileInfo instance instead of a boolean, allowing you to retrieve information about the file after it has been moved. An UploadException is thrown in cases where the method previously returned false.

try {
    $file = $uploadedFile->moveTo('/mnt/storage/file.ext');
    $size = $file->getSize();
}
catch(UploadException $e) {
    // Handle failure
}

Input validation

The InputValidation middleware now sets the response status code to 422 (unprocessable content) instead of 400 (bad request) when input validation fails.

You can restore the previous behavior by extending the middleware with a custom implementation:

namespace app\http\middleware;

use mako\http\exceptions\BadRequestException;
use mako\http\response\Status;
use mako\validator\input\http\routing\middleware\InputValidation as BaseInputValidation;
use Override;

class InputValidation extends BaseInputValidation
{
    #[Override]
	protected const Status HTTP_STATUS = Status::BadRequest;

    #[Override]
	protected const string HTTP_STATUS_EXCEPTION = BadRequestException::class;
}

Date and time

Everywhere that previously returned a Time instance now returns a TimeImmutable instance. This should not cause any issues unless you type hint Time instead of DateTimeInterface or TimeInterface, which both classes implement.

If you previously mutated returned Time instances directly, update your code to assign the modified instance instead:

1 - $time->modify('+1 day'); 
2 + $time = $time->modify('+1 day'); 

The Time::getImmutable() method has been renamed to Time::toImmutable().

1 - $immutable = $mutable->getImmutable(); 
2 + $immutable = $mutable->toImmutable(); 

The TimeImmutable::getMutable() method has been renamed to TimeImmutable::toMutable().

1 - $mutable = $immutable->getMutable(); 
2 + $mutable = $immutable->toMutable(); 

The TimeInterface::copy() method has been removed. The same functionality can be achieved with the clone keyword:

1 - $copy = $time->copy(); 
2 + $copy = clone $time; 

Image processing

The ImageInterface::getImageBlob() method has been renamed to ImageInterface::toBlob().

1 - $blob = $image->getImageBlob(); 
2 + $blob = $image->toBlob(); 

The Resize operation now takes a Dimensions instance instead of separate width and height integers.

1 - $image->apply(new Resize(200, 200)); 
2 + $image->apply(new Resize(new Dimensions(200, 200))); 

The undocumented percentage-based scaling behavior when only passing a single parameter to the Resize operation has been removed. The Resize operation now always works with explicit dimensions. Use the new Scale operation when you need to scale an image by a percentage.

1 - $image->apply(new Resize(50)); 
2 + $image->apply(new Scale(50)); 

The Crop operation now takes a Dimensions instance and a Point instance instead of separate width, height, x, and y integers.

1 - $image->apply(new Crop(200, 200, 0, 50)); 
2 + $image->apply(new Crop(new Dimensions(200, 200), new Point(0, 50))); 

The ImageInterface::snapshot() and ImageInterface::restore() methods have been removed. The same functionality can now be achieved using the clone keyword or using the ImageInterface::applyOnClone() method.

// Before

$image = new ImageMagick('image.png');
$image->apply(new Resize(new Dimensions(48, 48)));
$image->snapshot();
$image->apply(new Flip());
$image->save('flipped.png');
$image->restore();
$image->save('image.png');

// Now using clone

$image = new ImageMagick('image.png');
$image->apply(new Resize(new Dimensions(48, 48)));
(clone $image)->apply(new Flip())->save('flipped.png');
$image->save('image.png');

// Now using applyOnClone

$image = new ImageMagick('image.png');
$image->apply(new Resize(new Dimensions(48, 48)));
$image->applyOnClone(new Flip())->save('flipped.png');
$image->save('image.png');

The ImageInterface::getTopColors() method has been removed. Use the new TopColors inspector instead.

1 - $topColors = $image->getTopColors(); 
2 + $topColors = $image->inspect(new TopColors); 

The ImageInterface::getDimensions() method previously returned an associative array. Now it returns a Dimensions object.

  1$dimensions = $image->getDimensions();
  2
3 - $width = $dimensions['width']; 
4 - $height = $dimensions['height']; 
5 + $width = $dimensions->width; 
6 + $height = $dimensions->height; 

Signer

The Signer class has been moved from the mako\security namespace to the mako\security\signer namespace.

1 - use mako\security\Signer; 
2 + use mako\security\signer\Signer;