Merge pull request #450 from shikorism/feature/312-tag-normalize

正規化したタグ名で検索
This commit is contained in:
shibafu 2020-08-01 16:10:14 +09:00 committed by GitHub
commit 43ed36ccb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 174 additions and 99 deletions

View File

@ -5,8 +5,8 @@ FROM php:7.3-apache
ENV APACHE_DOCUMENT_ROOT /var/www/html/public ENV APACHE_DOCUMENT_ROOT /var/www/html/public
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y git libpq-dev unzip \ && apt-get install -y git libpq-dev unzip libicu-dev \
&& docker-php-ext-install pdo_pgsql \ && docker-php-ext-install pdo_pgsql intl \
&& pecl install xdebug \ && pecl install xdebug \
&& curl -sS https://getcomposer.org/installer | php \ && curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer \ && mv composer.phar /usr/local/bin/composer \

View File

@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\Tag;
use App\Utilities\Formatter;
use DB;
use Illuminate\Console\Command;
class NormalizeTags extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:tag:normalize';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Normalize tags';
private $formatter;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Formatter $formatter)
{
parent::__construct();
$this->formatter = $formatter;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$start = hrtime(true);
DB::transaction(function () {
/** @var Tag $tag */
foreach (Tag::query()->cursor() as $tag) {
$normalizedName = $this->formatter->normalizeTagName($tag->name);
$this->line("{$tag->name} : {$normalizedName}");
$tag->normalized_name = $normalizedName;
$tag->save();
}
});
$elapsed = (hrtime(true) - $start) / 1e+9;
$this->info("Done! ({$elapsed} sec)");
}
}

View File

@ -4,20 +4,30 @@ namespace App\Http\Controllers;
use App\Ejaculation; use App\Ejaculation;
use App\Tag; use App\Tag;
use App\Utilities\Formatter;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class SearchController extends Controller class SearchController extends Controller
{ {
/** @var Formatter */
private $formatter;
public function __construct(Formatter $formatter)
{
$this->formatter = $formatter;
}
public function index(Request $request) public function index(Request $request)
{ {
$inputs = $request->validate([ $inputs = $request->validate([
'q' => 'required' 'q' => 'required'
]); ]);
$q = $this->normalizeQuery($inputs['q']);
$results = Ejaculation::query() $results = Ejaculation::query()
->whereHas('tags', function ($query) use ($inputs) { ->whereHas('tags', function ($query) use ($q) {
$query->where('name', 'like', "%{$inputs['q']}%"); $query->where('normalized_name', 'like', "%{$q}%");
}) })
->whereHas('user', function ($query) { ->whereHas('user', function ($query) {
$query->where('is_protected', false); $query->where('is_protected', false);
@ -41,11 +51,17 @@ class SearchController extends Controller
'q' => 'required' 'q' => 'required'
]); ]);
$q = $this->normalizeQuery($inputs['q']);
$results = Tag::query() $results = Tag::query()
->where('name', 'like', "%{$inputs['q']}%") ->where('normalized_name', 'like', "%{$q}%")
->paginate(50) ->paginate(50)
->appends($inputs); ->appends($inputs);
return view('search.relatedTag')->with(compact('inputs', 'results')); return view('search.relatedTag')->with(compact('inputs', 'results'));
} }
private function normalizeQuery(string $query): string
{
return $this->formatter->normalizeTagName($query);
}
} }

View File

@ -2,6 +2,7 @@
namespace App; namespace App;
use App\Utilities\Formatter;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Tag extends Model class Tag extends Model
@ -15,6 +16,15 @@ class Tag extends Model
'name' 'name'
]; ];
protected static function boot()
{
parent::boot();
self::creating(function (Tag $tag) {
$tag->normalized_name = app(Formatter::class)->normalizeTagName($tag->name);
});
}
public function ejaculations() public function ejaculations()
{ {
return $this->belongsToMany('App\Ejaculation')->withTimestamps(); return $this->belongsToMany('App\Ejaculation')->withTimestamps();

View File

@ -132,4 +132,12 @@ class Formatter
return $bytes . 'B'; return $bytes . 'B';
} }
public function normalizeTagName(string $name)
{
$name = \Normalizer::normalize($name, \Normalizer::FORM_KC);
$name = mb_strtolower($name);
return $name;
}
} }

View File

@ -12,6 +12,12 @@
], ],
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"ext-dom": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"anhskohbo/no-captcha": "^3.0", "anhskohbo/no-captcha": "^3.0",
"doctrine/dbal": "^2.9", "doctrine/dbal": "^2.9",
"erusev/parsedown": "^1.7", "erusev/parsedown": "^1.7",

104
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "2c0bd951a595d4856079c5a13a72e651", "content-hash": "1bba68b609be6a0dcdaf05d72e8eb759",
"packages": [ "packages": [
{ {
"name": "anhskohbo/no-captcha", "name": "anhskohbo/no-captcha",
@ -394,20 +394,6 @@
"sqlserver", "sqlserver",
"sqlsrv" "sqlsrv"
], ],
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
"type": "tidelift"
}
],
"time": "2020-04-20T17:19:26+00:00" "time": "2020-04-20T17:19:26+00:00"
}, },
{ {
@ -1617,12 +1603,6 @@
"transform", "transform",
"write" "write"
], ],
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2020-03-17T15:15:35+00:00" "time": "2020-03-17T15:15:35+00:00"
}, },
{ {
@ -1707,12 +1687,6 @@
"sftp", "sftp",
"storage" "storage"
], ],
"funding": [
{
"url": "https://offset.earth/frankdejonge",
"type": "other"
}
],
"time": "2020-05-18T15:13:39+00:00" "time": "2020-05-18T15:13:39+00:00"
}, },
{ {
@ -1839,16 +1813,6 @@
"logging", "logging",
"psr-3" "psr-3"
], ],
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2020-05-22T08:12:19+00:00" "time": "2020-05-22T08:12:19+00:00"
}, },
{ {
@ -5875,12 +5839,6 @@
"profiler", "profiler",
"webprofiler" "webprofiler"
], ],
"funding": [
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2020-05-05T10:53:32+00:00" "time": "2020-05-05T10:53:32+00:00"
}, },
{ {
@ -5952,12 +5910,6 @@
"phpstorm", "phpstorm",
"sublime" "sublime"
], ],
"funding": [
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2020-04-22T09:57:26+00:00" "time": "2020-04-22T09:57:26+00:00"
}, },
{ {
@ -6063,16 +6015,6 @@
"ssl", "ssl",
"tls" "tls"
], ],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-04-08T08:27:21+00:00" "time": "2020-04-08T08:27:21+00:00"
}, },
{ {
@ -6154,16 +6096,6 @@
"dependency", "dependency",
"package" "package"
], ],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-05-06T08:28:10+00:00" "time": "2020-05-06T08:28:10+00:00"
}, },
{ {
@ -6536,12 +6468,6 @@
"flare", "flare",
"reporting" "reporting"
], ],
"funding": [
{
"url": "https://www.patreon.com/spatie",
"type": "patreon"
}
],
"time": "2020-03-02T15:52:04+00:00" "time": "2020-03-02T15:52:04+00:00"
}, },
{ {
@ -8088,12 +8014,6 @@
"highlight.php", "highlight.php",
"syntax" "syntax"
], ],
"funding": [
{
"url": "https://github.com/allejo",
"type": "github"
}
],
"time": "2020-03-02T05:59:21+00:00" "time": "2020-03-02T05:59:21+00:00"
}, },
{ {
@ -8758,16 +8678,6 @@
"parser", "parser",
"validator" "validator"
], ],
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/seld/jsonlint",
"type": "tidelift"
}
],
"time": "2020-04-30T19:05:18+00:00" "time": "2020-04-30T19:05:18+00:00"
}, },
{ {
@ -9032,8 +8942,8 @@
"authors": [ "authors": [
{ {
"name": "Arne Blankerts", "name": "Arne Blankerts",
"email": "arne@blankerts.de", "role": "Developer",
"role": "Developer" "email": "arne@blankerts.de"
} }
], ],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
@ -9097,7 +9007,13 @@
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^7.2" "php": "^7.2",
"ext-dom": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-pdo": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "1.1.0" "plugin-api-version": "1.1.0"

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNormalizedNameToTags extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tags', function (Blueprint $table) {
$table->string('normalized_name')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tags', function (Blueprint $table) {
$table->dropColumn('normalized_name');
});
}
}

View File

@ -70,4 +70,30 @@ class FormatterTest extends TestCase
$formatter->profileImageSrcSet($profileImageProvider, 128, 2) $formatter->profileImageSrcSet($profileImageProvider, 128, 2)
); );
} }
/**
* @dataProvider provideNormalizeTagName
*/
public function testNormalizeTagName($input, $expected)
{
$formatter = new Formatter();
$normalized = $formatter->normalizeTagName($input);
$this->assertSame($expected, $normalized);
$this->assertSame($expected, $formatter->normalizeTagName($normalized));
}
public function provideNormalizeTagName()
{
return [
'LowerCase' => ['example', 'example'],
'UpperCase' => ['EXAMPLE', 'example'],
'HalfWidthKana' => ['ティッシュ', 'ティッシュ'],
'FullWidthAlphabet' => ['', 'tissue'],
'組み文字1' => ['13㎝', '13cm'],
'組み文字2' => ['13㌢㍍', '13センチメートル'],
'Script' => ['ℬ𝒶𝒷𝓊𝓂𝒾', 'babumi'],
'NFD' => ['オカズ', 'オカズ'],
];
}
} }