Added full support for emojis

This commit is contained in:
Alejandro Celaya 2022-01-10 13:04:16 +01:00
parent b941ee9aa9
commit ce47d8c591
6 changed files with 107 additions and 11 deletions

View file

@ -21,6 +21,13 @@ return (static function (): array {
'mssql' => '1433',
default => '3306',
};
$resolveCharset = static fn () => match ($driver) {
// This does not determine charsets or collations in tables or columns, but the charset used in the data
// flowing in the connection, so it has to match what has been set in the database.
'maria', 'mysql' => 'utf8mb4',
'postgres' => 'utf8',
default => null,
};
$resolveConnection = static fn () => match ($driver) {
null, 'sqlite' => [
'driver' => 'pdo_sqlite',
@ -34,7 +41,7 @@ return (static function (): array {
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
'port' => env('DB_PORT', $resolveDefaultPort()),
'unix_socket' => $isMysqlCompatible ? env('DB_UNIX_SOCKET') : null,
'charset' => 'utf8',
'charset' => $resolveCharset(),
],
};

View file

@ -55,6 +55,7 @@ $buildDbConnection = static function (): array {
'user' => 'postgres',
'password' => 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8',
],
'mssql' => [
'driver' => 'pdo_sqlsrv',
@ -70,6 +71,7 @@ $buildDbConnection = static function (): array {
'user' => 'root',
'password' => 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8mb4',
],
};
};

View file

@ -11,7 +11,7 @@ use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
use function get_class;
use function is_subclass_of;
/**
* Auto-generated Migration: Please modify to your needs!
@ -24,13 +24,15 @@ class Version20160819142757 extends AbstractMigration
*/
public function up(Schema $schema): void
{
$platformClass = get_class($this->connection->getDatabasePlatform());
$platformClass = $this->connection->getDatabasePlatform();
$table = $schema->getTable('short_urls');
$column = $table->getColumn('short_code');
match ($platformClass) {
MySQLPlatform::class => $column->setPlatformOption('collation', 'utf8_bin'),
SqlitePlatform::class => $column->setPlatformOption('collate', 'BINARY'),
match (true) {
is_subclass_of($platformClass, MySQLPlatform::class) => $column
->setPlatformOption('charset', 'utf8mb4')
->setPlatformOption('collation', 'utf8mb4_bin'),
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
default => null,
};
}

View file

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220110113313 extends AbstractMigration
{
private const CHARSET = 'utf8mb4';
private const COLLATIONS = [
'short_urls' => [
'original_url' => 'unicode_ci',
'short_code' => 'bin',
'import_original_short_code' => 'unicode_ci',
'title' => 'unicode_ci',
],
'domains' => [
'authority' => 'unicode_ci',
'base_url_redirect' => 'unicode_ci',
'regular_not_found_redirect' => 'unicode_ci',
'invalid_short_url_redirect' => 'unicode_ci',
],
'tags' => [
'name' => 'unicode_ci',
],
'visits' => [
'referer' => 'unicode_ci',
'user_agent' => 'unicode_ci',
'visited_url' => 'unicode_ci',
],
'visit_locations' => [
'country_code' => 'unicode_ci',
'country_name' => 'unicode_ci',
'region_name' => 'unicode_ci',
'city_name' => 'unicode_ci',
'timezone' => 'unicode_ci',
],
];
public function up(Schema $schema): void
{
$this->skipIf(! $this->isMySql(), 'This only sets MySQL-specific database options');
foreach (self::COLLATIONS as $tableName => $columns) {
$table = $schema->getTable($tableName);
foreach ($columns as $columnName => $collation) {
$table->getColumn($columnName)
->setPlatformOption('charset', self::CHARSET)
->setPlatformOption('collation', self::CHARSET . '_' . $collation);
}
}
}
public function down(Schema $schema): void
{
// No down
}
public function isTransactional(): bool
{
return ! $this->isMySql();
}
private function isMySql(): bool
{
return $this->connection->getDatabasePlatform() instanceof MySQLPlatform;
}
}

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace <namespace>;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@ -21,6 +22,6 @@ final class <className> extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View file

@ -315,11 +315,22 @@ class CreateShortUrlTest extends ApiTestCase
yield ['https://mobile.twitter.com/shlinkio/status/1360637738421268481'];
}
/** @test */
public function canCreateShortUrlsWithEmojis(): void
{
[$statusCode, $payload] = $this->createShortUrl([
'longUrl' => 'https://emojipedia.org/fire/',
'title' => '🔥🔥🔥',
'customSlug' => '🦣🦣🦣',
]);
self::assertEquals(self::STATUS_OK, $statusCode);
self::assertEquals('🔥🔥🔥', $payload['title']);
self::assertEquals('🦣🦣🦣', $payload['shortCode']);
self::assertEquals('http://doma.in/🦣🦣🦣', $payload['shortUrl']);
}
/**
* @return array {
* @var int $statusCode
* @var array $payload
* }
* @return array{int $statusCode, array $payload}
*/
private function createShortUrl(array $body = [], string $apiKey = 'valid_api_key'): array
{