Repository: codezero-be/laravel-unique-translation
Branch: master
Commit: a2daae936dbc
Files: 18
Total size: 50.9 KB
Directory structure:
gitextract_gcilwl22/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── SECURITY.md
│ └── workflows/
│ └── run-tests.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src/
│ ├── UniqueTranslationRule.php
│ ├── UniqueTranslationServiceProvider.php
│ └── UniqueTranslationValidator.php
└── tests/
├── Stubs/
│ └── Model.php
├── TestCase.php
├── UniqueTranslationTest.php
├── ValidationMessageTest.php
└── WhereClauseTest.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.yml]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/.scrutinizer.yml export-ignore
/tests export-ignore
================================================
FILE: .github/FUNDING.yml
================================================
ko_fi: ivanvermeyen
custom: https://paypal.me/ivanvermeyen
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
If you discover any security related issues, please email ivan@codezero.be instead of using the issue tracker.
================================================
FILE: .github/workflows/run-tests.yml
================================================
name: Tests
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [ 8.0, 8.1, 8.2, 8.3 ]
laravel: [ 8.*, 9.*, 10.*, 11.* ]
dependency-version: [ prefer-stable ]
exclude:
- laravel: 10.*
php: 8.0
- laravel: 11.*
php: 8.0
- laravel: 11.*
php: 8.1
include:
- laravel: 6.*
php: 7.2
testbench: 4.*
- laravel: 6.*
php: 8.0
testbench: 4.*
- laravel: 7.*
php: 7.2
testbench: 5.*
- laravel: 7.*
php: 8.0
testbench: 5.*
- laravel: 8.*
php: 7.3
testbench: 6.*
- laravel: 8.*
testbench: 6.*
- laravel: 9.*
testbench: 7.*
- laravel: 10.*
testbench: 8.*
- laravel: 11.*
testbench: 9.*
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testing
ports:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.composer/cache/files
key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
coverage: pcov
- name: Install dependencies
run: composer update --with="orchestra/testbench:${{ matrix.testbench }}" --prefer-dist --no-interaction --no-progress
- name: Execute tests
run: vendor/bin/phpunit --coverage-clover=coverage.xml
env:
DB_PORT: ${{ job.services.mysql.ports[3306] }}
================================================
FILE: .gitignore
================================================
/vendor
.idea
composer.lock
phpunit.xml
/.phpunit.result.cache
/.phpunit.cache
================================================
FILE: LICENSE.md
================================================
# The MIT License (MIT)
Copyright (c) 2017 Ivan Vermeyen (<ivan@codezero.be>)
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.
================================================
FILE: README.md
================================================
# Laravel Unique Translation
## IMPORTANT: March 2022
[](https://github.com/hampusborgos/country-flags/blob/main/png100px/ua.png)
It's horrible to see what is happening now in Ukraine, as Russian army is
[bombarding houses, hospitals and kindergartens](https://twitter.com/DavidCornDC/status/1501620037785997316).
Please [check out supportukrainenow.org](https://supportukrainenow.org/) for the ways how you can help people there.
Spread the word.
And if you are from Russia and you are against this war, please express your protest in some way.
I know you can get punished for this, but you are one of the hopes of those innocent people.
---
[](https://github.com/codezero-be/laravel-unique-translation/releases)
[](https://laravel.com)
[](LICENSE.md)
[](https://github.com/codezero-be/laravel-unique-translation/actions)
[](https://app.codacy.com/gh/codezero-be/laravel-unique-translation)
[](https://app.codacy.com/gh/codezero-be/laravel-unique-translation)
[](https://packagist.org/packages/codezero/laravel-unique-translation)
[](https://ko-fi.com/R6R3UQ8V)
#### Check if a translated value in a JSON column is unique in the database.
Imagine you want store a `slug` for a `Post` model in different languages.
The amazing [`spatie/laravel-translatable`](https://github.com/spatie/laravel-translatable) package makes this a cinch!
But then you want to make sure each translation is unique for its language.
That's where this package comes in to play.
This package also supports [`spatie/nova-translatable`](https://github.com/spatie/nova-translatable/) in case you are using [Laravel Nova](https://nova.laravel.com/) and [`filamentphp/spatie-laravel-translatable-plugin`](https://github.com/filamentphp/spatie-laravel-translatable-plugin) in case you are using [Filament](https://filamentphp.com/).
## ✅ Requirements
- PHP ^7.2 or PHP ^8.0
- MySQL >= 5.7
- [Laravel](https://laravel.com/) >= 6
- [spatie/laravel-translatable](https://github.com/spatie/laravel-translatable) ^4.4|^5.0|^6.0
- [spatie/nova-translatable](https://github.com/spatie/nova-translatable/) ^3.0
- [filamentphp/spatie-laravel-translatable-plugin](https://github.com/filamentphp/spatie-laravel-translatable-plugin) ^3.0
## 📦 Installation
Require the package via Composer:
```
composer require codezero/laravel-unique-translation
```
Laravel will automatically register the [ServiceProvider](https://github.com/codezero-be/laravel-unique-translation/blob/master/src/UniqueTranslationServiceProvider.php).
## 🛠 Usage
For the following examples, I will use a `slug` in a `posts` table as the subject of our validation.
### ☑️ Validate a Single Translation
Your form can submit a single slug:
```html
<input name="slug">
```
We can then check if it is unique **in the current locale**:
```php
$attributes = request()->validate([
'slug' => 'required|unique_translation:posts',
]);
```
You could also use the Rule instance:
```php
use CodeZero\UniqueTranslation\UniqueTranslationRule;
$attributes = request()->validate([
'slug' => ['required', UniqueTranslationRule::for('posts')],
]);
```
### ☑️ Validate an Array of Translations
Your form can also submit an array of slugs.
```html
<input name="slug[en]">
<input name="slug[nl]">
```
We need to validate the entire array in this case. Mind the `slug.*` key.
```php
$attributes = request()->validate([
'slug.*' => 'unique_translation:posts',
// or...
'slug.*' => UniqueTranslationRule::for('posts'),
]);
```
### ☑️ Specify a Column
Maybe your form field has a name of `post_slug` and your database field `slug`:
```php
$attributes = request()->validate([
'post_slug.*' => 'unique_translation:posts,slug',
// or...
'post_slug.*' => UniqueTranslationRule::for('posts', 'slug'),
]);
```
### ☑️ Specify a Database Connection
If you are using multiple database connections, you can specify which one to use by prepending it to the table name, separated by a dot:
```php
$attributes = request()->validate([
'slug.*' => 'unique_translation:db_connection.posts',
// or...
'slug.*' => UniqueTranslationRule::for('db_connection.posts'),
]);
```
### ☑️ Ignore a Record with ID
If you're updating a record, you may want to ignore the post itself from the unique check.
```php
$attributes = request()->validate([
'slug.*' => "unique_translation:posts,slug,{$post->id}",
// or...
'slug.*' => UniqueTranslationRule::for('posts')->ignore($post->id),
]);
```
### ☑️ Ignore Records with a Specific Column and Value
If your ID column has a different name, or you just want to use another column:
```php
$attributes = request()->validate([
'slug.*' => 'unique_translation:posts,slug,ignore_value,ignore_column',
// or...
'slug.*' => UniqueTranslationRule::for('posts')->ignore('ignore_value', 'ignore_column'),
]);
```
### ☑️ Use Additional Where Clauses
You can add 4 types of where clauses to the rule.
#### `where`
```php
$attributes = request()->validate([
'slug.*' => "unique_translation:posts,slug,null,null,column,value",
// or...
'slug.*' => UniqueTranslationRule::for('posts')->where('column', 'value'),
]);
```
#### `whereNot`
```php
$attributes = request()->validate([
'slug.*' => "unique_translation:posts,slug,null,null,column,!value",
// or...
'slug.*' => UniqueTranslationRule::for('posts')->whereNot('column', 'value'),
]);
```
#### `whereNull`
```php
$attributes = request()->validate([
'slug.*' => "unique_translation:posts,slug,null,null,column,NULL",
// or...
'slug.*' => UniqueTranslationRule::for('posts')->whereNull('column'),
]);
```
#### `whereNotNull`
```php
$attributes = request()->validate([
'slug.*' => "unique_translation:posts,slug,null,null,column,NOT_NULL",
// or...
'slug.*' => UniqueTranslationRule::for('posts')->whereNotNull('column'),
]);
```
### ☑️ Laravel Nova
If you are using [Laravel Nova](https://nova.laravel.com/) in combination with [`spatie/nova-translatable`](https://github.com/spatie/nova-translatable/), then you can add the validation rule like this:
```php
Text::make(__('Slug'), 'slug')
->creationRules('unique_translation:posts,slug')
->updateRules('unique_translation:posts,slug,{{resourceId}}');
```
### ☑️ Filament
If you are using [Filament](https://filamentphp.com/) in combination with [`filamentphp/spatie-laravel-translatable-plugin`](https://github.com/filamentphp/spatie-laravel-translatable-plugin), then you can add the validation rule like this:
```php
TextInput::make('slug')
->title(__('Slug'))
->rules([
UniqueTranslationRule::for('posts', 'slug')
])
```
```php
TextInput::make('slug')
->title(__('Slug'))
->rules([
fn (Get $get) => UniqueTranslationRule::for('posts', 'slug')->ignore($get('id'))
])
```
## 🖥 Example
Your existing `slug` column (JSON) in a `posts` table:
```json
{
"en":"not-abc",
"nl":"abc"
}
```
Your form input to create a new record:
```html
<input name="slug[en]" value="abc">
<input name="slug[nl]" value="abc">
```
Your validation logic:
```php
$attributes = request()->validate([
'slug.*' => 'unique_translation:posts',
]);
```
The result is that `slug[en]` is valid, since the only `en` value in the database is `not-abc`.
And `slug[nl]` would fail, because there already is a `nl` value of `abc`.
## ⚠️ Error Messages
You can pass your own error messages as normal.
When validating a single form field:
```html
<input name="slug">
```
```php
$attributes = request()->validate([
'slug' => 'unique_translation:posts',
], [
'slug.unique_translation' => 'Your custom :attribute error.',
]);
```
In your view you can then get the error with `$errors->first('slug')`.
Or when validation an array:
```html
<input name="slug[en]">
```
```php
$attributes = request()->validate([
'slug.*' => 'unique_translation:posts',
], [
'slug.*.unique_translation' => 'Your custom :attribute error.',
]);
```
In your view you can then get the error with `$errors->first('slug.en')` (`en` being your array key).
## 🚧 Testing
```
vendor/bin/phpunit
```
## ☕️ Credits
- [Ivan Vermeyen](https://byterider.io)
- [All contributors](../../contributors)
## 🔓 Security
If you discover any security related issues, please [e-mail me](mailto:ivan@codezero.be) instead of using the issue tracker.
## 📑 Changelog
A complete list of all notable changes to this package can be found on the
[releases page](https://github.com/codezero-be/laravel-unique-translation/releases).
## 📜 License
The MIT License (MIT). Please see [License File](https://github.com/codezero-be/laravel-unique-translation/blob/master/LICENSE.md) for more information.
================================================
FILE: composer.json
================================================
{
"name": "codezero/laravel-unique-translation",
"description": "Check if a translated value in a JSON column is unique in the database.",
"keywords": [
"translation",
"json",
"mysql",
"php",
"laravel",
"validation",
"validator",
"unique",
"rule",
"language",
"database"
],
"license": "MIT",
"authors": [
{
"name": "Ivan Vermeyen",
"email": "ivan@codezero.be"
}
],
"require": {
"php": "^7.2|^8.0"
},
"require-dev": {
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0",
"phpunit/phpunit": "^8.0|^9.0|^10.0",
"spatie/laravel-translatable": "^4.4|^5.0|^6.0"
},
"autoload": {
"psr-4": {
"CodeZero\\UniqueTranslation\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"CodeZero\\UniqueTranslation\\Tests\\": "tests"
}
},
"extra": {
"laravel": {
"providers": [
"CodeZero\\UniqueTranslation\\UniqueTranslationServiceProvider"
]
}
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability": "dev",
"prefer-stable": true
}
================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
bootstrap="vendor/autoload.php"
colors="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<testsuites>
<testsuite name="CodeZero">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="mysql"/>
<env name="DB_DATABASE" value="testing"/>
<env name="DB_COLLATION" value="utf8mb4_unicode_ci"/>
<env name="DB_USERNAME" value="root"/>
<env name="DB_PASSWORD" value=""/>
</php>
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit>
================================================
FILE: src/UniqueTranslationRule.php
================================================
<?php
namespace CodeZero\UniqueTranslation;
use Illuminate\Validation\Rules\DatabaseRule;
class UniqueTranslationRule
{
use DatabaseRule;
/**
* The name of the validation rule.
*
* @var string
*/
protected $rule = 'unique_translation';
/**
* The value of the the 'ignoreColumn' to ignore.
*
* @var mixed
*/
protected $ignoreValue;
/**
* The name of the 'ignoreColumn'.
*
* @var string|null
*/
protected $ignoreColumn;
/**
* Create a new rule instance.
*
* @param string $table
* @param string|null $column
*
* @return static
*/
public static function for($table, $column = null)
{
return new static($table, $column);
}
/**
* Create a new rule instance.
*
* @param string $table
* @param string|null $column
*/
public function __construct($table, $column = null)
{
$this->table = $table;
$this->column = $column;
}
/**
* Ignore any record that has a column with the given value.
*
* @param mixed $value
* @param string $column
*
* @return $this
*/
public function ignore($value, $column = 'id')
{
$this->ignoreValue = $value;
$this->ignoreColumn = $column;
return $this;
}
/**
* Generate a string representation of the validation rule.
*
* @return string
*/
public function __toString()
{
return rtrim(sprintf(
'%s:%s,%s,%s,%s,%s',
$this->rule,
$this->table,
$this->column ?: 'NULL',
$this->ignoreValue ?: 'NULL',
$this->ignoreColumn ?: 'NULL',
$this->formatWheres()
), ',');
}
}
================================================
FILE: src/UniqueTranslationServiceProvider.php
================================================
<?php
namespace CodeZero\UniqueTranslation;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
class UniqueTranslationServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Validator::extend('unique_translation', UniqueTranslationValidator::class.'@validate');
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
================================================
FILE: src/UniqueTranslationValidator.php
================================================
<?php
namespace CodeZero\UniqueTranslation;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
class UniqueTranslationValidator
{
/**
* Check if the translated value is unique in the database.
*
* @param string $attribute
* @param string $value
* @param array $parameters
* @param \Illuminate\Validation\Validator $validator
*
* @return bool
*/
public function validate($attribute, $value, $parameters, $validator)
{
list ($name, $locale) = $this->isNovaTranslation($attribute)
? $this->getNovaAttributeNameAndLocale($attribute)
: (
$this->isFilamentTranslation($attribute)
? $this->getFilamentAttributeNameAndLocale($attribute, $validator)
: $this->getArrayAttributeNameAndLocale($attribute)
);
if ($this->isUnique($value, $name, $locale, $parameters)) {
return true;
}
$this->setMissingErrorMessages($validator, $name, $locale);
return false;
}
/**
* Set any missing (custom) error messages for our validation rule.
*
* @param \Illuminate\Validation\Validator $validator
* @param string $name
* @param string $locale
*
* @return void
*/
protected function setMissingErrorMessages($validator, $name, $locale)
{
$rule = 'unique_translation';
$keys = [
"{$name}.{$rule}",
"{$name}.*.{$rule}",
"{$name}.{$locale}.{$rule}",
"translations_{$name}_{$locale}.{$rule}",
];
foreach ($keys as $key) {
if ( ! array_key_exists($key, $validator->customMessages)) {
$validator->customMessages[$key] = trans('validation.unique');
}
}
}
/**
* Check if the attribute is a Nova translation field name.
*
* @param string $attribute
*
* @return bool
*/
protected function isNovaTranslation($attribute)
{
return strpos($attribute, '.') === false && strpos($attribute, 'translations_') === 0;
}
/**
* Get the attribute name and locale of a Filament translation field.
*
* @param string $attribute
*
* @return array
*/
protected function getNovaAttributeNameAndLocale($attribute)
{
$attribute = str_replace('translations_', '', $attribute);
return $this->getAttributeNameAndLocale($attribute, '_');
}
/**
* Check if the attribute is a Filament translation field name.
*
* @param string $attribute
*
* @return bool
*/
protected function isFilamentTranslation($attribute)
{
return strpos($attribute, 'data.') === 0;
}
/**
* Get the attribute name and locale of a Filament translation field.
*
* @param string $attribute
*
* @return array
*/
protected function getFilamentAttributeNameAndLocale($attribute, $validator)
{
$attribute = str_replace('data.', '', $attribute);
$dataValidator = $validator->getData();
@list($name, $locale) = @explode('.', $attribute);
if ($locale === null && Arr::exists($dataValidator, 'activeLocale')) {
$locale = $dataValidator['activeLocale'];
}
return [$name, $locale];
}
/**
* Get the attribute name and locale of an array field.
*
* @param string $attribute
*
* @return array
*/
protected function getArrayAttributeNameAndLocale($attribute)
{
return $this->getAttributeNameAndLocale($attribute, '.');
}
/**
* Get the attribute name and locale.
*
* @param string $attribute
* @param string $delimiter
*
* @return array
*/
protected function getAttributeNameAndLocale($attribute, $delimiter)
{
$locale = $this->getAttributeLocale($attribute, $delimiter);
$name = $this->getAttributeName($attribute, $locale, $delimiter);
return [$name, $locale ?: App::getLocale()];
}
/**
* Get the locale from the attribute name.
*
* @param string $attribute
* @param string $delimiter
*
* @return string|null
*/
protected function getAttributeLocale($attribute, $delimiter)
{
$pos = strrpos($attribute, $delimiter);
return $pos > 0 ? substr($attribute, $pos + 1) : null;
}
/**
* Get the attribute name without the locale.
*
* @param string $attribute
* @param string|null $locale
* @param string $delimiter
*
* @return string
*/
protected function getAttributeName($attribute, $locale, $delimiter)
{
return $locale ? str_replace("{$delimiter}{$locale}", '', $attribute) : $attribute;
}
/**
* Get the database connection and table name.
*
* @param array $parameters
*
* @return array
*/
protected function getConnectionAndTable($parameters)
{
$parts = explode('.', $this->getParameter($parameters, 0));
$connection = isset($parts[1])
? $parts[0]
: Config::get('database.default');
$table = $parts[1] ?? $parts[0];
return [$connection, $table];
}
/**
* Get the parameter value at the given index.
*
* @param array $parameters
* @param int $index
*
* @return string|null
*/
protected function getParameter($parameters, $index)
{
return $this->convertNullValue($parameters[$index] ?? null);
}
/**
* Convert any 'NULL' string value to null.
*
* @param string $value
*
* @return string|null
*/
protected function convertNullValue($value)
{
return strtoupper($value) === 'NULL' ? null : $value;
}
/**
* Check if a translation is unique.
*
* @param mixed $value
* @param string $name
* @param string $locale
* @param array $parameters
*
* @return bool
*/
protected function isUnique($value, $name, $locale, $parameters)
{
list ($connection, $table) = $this->getConnectionAndTable($parameters);
$column = $this->getParameter($parameters, 1) ?? $name;
$ignoreValue = $this->getParameter($parameters, 2);
$ignoreColumn = $this->getParameter($parameters, 3);
$query = $this->findTranslation($connection, $table, $column, $locale, $value);
$query = $this->ignore($query, $ignoreColumn, $ignoreValue);
$query = $this->addConditions($query, $this->getUniqueExtra($parameters));
$isUnique = $query->count() === 0;
return $isUnique;
}
/**
* Find the given translated value in the database.
*
* @param string $connection
* @param string $table
* @param string $column
* @param string $locale
* @param mixed $value
*
* @return \Illuminate\Database\Query\Builder
*/
protected function findTranslation($connection, $table, $column, $locale, $value)
{
// Properly escape backslashes to work with LIKE queries...
// See: https://stackoverflow.com/questions/14926386/how-to-search-for-slash-in-mysql-and-why-escaping-not-required-for-wher
$escaped = DB::getDriverName() === 'sqlite' ? '\\\\' : '\\\\\\\\';
$value = str_replace('\\', $escaped, $value);
// Support PostgreSQL case insensitive queries with ILIKE
$operator = DB::getDriverName() === 'pgsql' ? 'ILIKE' : 'LIKE';
return DB::connection($connection)->table($table)
->where(function ($query) use ($column, $operator, $locale, $value) {
$query->where($column, $operator, "%\"{$locale}\": \"{$value}\"%")
->orWhere($column, $operator, "%\"{$locale}\":\"{$value}\"%");
});
}
/**
* Ignore the column with the given value.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string|null $column
* @param mixed $value
*
* @return \Illuminate\Database\Query\Builder
*/
protected function ignore($query, $column = null, $value = null)
{
if ($value !== null && $column === null) {
$column = 'id';
}
if ($column !== null) {
$query = $query->where($column, '!=', $value);
}
return $query;
}
/**
* Get the extra conditions for a unique rule.
* Taken From: \Illuminate\Validation\Concerns\ValidatesAttributes
*
* @param array $parameters
*
* @return array
*/
protected function getUniqueExtra($parameters)
{
if (isset($parameters[4])) {
return $this->getExtraConditions(array_slice($parameters, 4));
}
return [];
}
/**
* Get the extra conditions for a unique / exists rule.
* Taken from: \Illuminate\Validation\Concerns\ValidatesAttributes
*
* @param array $segments
*
* @return array
*/
protected function getExtraConditions(array $segments)
{
$extra = [];
$count = count($segments);
for ($i = 0; $i < $count; $i += 2) {
$extra[$segments[$i]] = $segments[$i + 1];
}
return $extra;
}
/**
* Add the given conditions to the query.
* Adapted from: \Illuminate\Validation\DatabasePresenceVerifier
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $conditions
*
* @return \Illuminate\Database\Query\Builder
*/
protected function addConditions($query, $conditions)
{
foreach ($conditions as $key => $value) {
$this->addWhere($query, $key, $value);
}
return $query;
}
/**
* Add a "where" clause to the given query.
* Taken from: \Illuminate\Validation\DatabasePresenceVerifier
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $key
* @param string $extraValue
*
* @return \Illuminate\Database\Query\Builder
*/
protected function addWhere($query, $key, $extraValue)
{
if ($extraValue === 'NULL') {
return $query->whereNull($key);
}
if ($extraValue === 'NOT_NULL') {
return $query->whereNotNull($key);
}
$isNegative = Str::startsWith($extraValue, '!');
$operator = $isNegative ? '!=' : '=';
$value = $isNegative ? mb_substr($extraValue, 1) : $extraValue;
return $query->where($key, $operator, $value);
}
}
================================================
FILE: tests/Stubs/Model.php
================================================
<?php
namespace CodeZero\UniqueTranslation\Tests\Stubs;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Spatie\Translatable\HasTranslations;
class Model extends EloquentModel
{
use HasTranslations;
public $translatable = ['slug', 'name'];
protected $table = 'test_models';
protected $guarded = [];
public $timestamps = false;
}
================================================
FILE: tests/TestCase.php
================================================
<?php
namespace CodeZero\UniqueTranslation\Tests;
use CodeZero\UniqueTranslation\UniqueTranslationServiceProvider;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
use Orchestra\Testbench\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
/**
* Database table for the test models.
*
* @var string
*/
protected $table = 'test_models';
/**
* Name of the validation rule.
*
* @var string
*/
protected $rule = 'unique_translation';
/**
* Setup the test environment.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
Config::set('app.key', Str::random(32));
App::setLocale('en');
$this->setupDatabase();
}
/**
* Get the packages service providers.
*
* @param \Illuminate\Foundation\Application $app
*
* @return array
*/
protected function getPackageProviders($app)
{
return [
UniqueTranslationServiceProvider::class,
];
}
/**
* Setup the test database.
*
* @return void
*/
protected function setupDatabase()
{
$this->app['db']->getSchemaBuilder()->dropIfExists($this->table);
$this->app['db']->getSchemaBuilder()->create($this->table, function (Blueprint $table) {
$table->increments('id');
$table->json('slug')->nullable();
$table->text('name')->nullable();
$table->string('other_field')->nullable();
});
$this->beforeApplicationDestroyed(function () {
$this->app['db']->getSchemaBuilder()->drop($this->table);
});
}
}
================================================
FILE: tests/UniqueTranslationTest.php
================================================
<?php
namespace CodeZero\UniqueTranslation\Tests;
use CodeZero\UniqueTranslation\Tests\Stubs\Model;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Validator;
class UniqueTranslationTest extends TestCase
{
/** @test */
public function it_checks_if_the_translation_for_the_current_locale_is_unique()
{
Model::create([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
]);
$rules = [
'slug' => "{$this->rule}:{$this->table}",
'name' => UniqueTranslationRule::for($this->table),
];
// The following validation fails, because the
// current locale is "en", so we actually set
// ['en' => 'existing-slug-en'] etc.
$validation = Validator::make([
'slug' => 'existing-slug-en',
'name' => 'existing-name-en',
], $rules);
$this->assertTrue($validation->fails());
// The following validation passes, because the
// current locale is "en", so we actually set
// ['en' => 'existing-slug-nl'] etc.
$validation = Validator::make([
'slug' => 'existing-slug-nl',
'name' => 'existing-name-nl',
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function search_is_case_insensitive()
{
Model::create([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
]);
$rules = [
'slug' => "{$this->rule}:{$this->table}",
'name' => UniqueTranslationRule::for($this->table),
];
$validation = Validator::make([
'slug' => 'Existing-slug-en',
'name' => 'Existing-name-en',
], $rules);
$this->assertTrue($validation->fails());
}
/** @test */
public function it_checks_if_the_translation_for_a_specific_locale_is_unique()
{
Model::create([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table}",
'name.*' => UniqueTranslationRule::for($this->table),
];
$validation = Validator::make([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
], $rules);
$this->assertTrue($validation->fails());
$validation = Validator::make([
'slug' => ['en' => 'different-slug-en'],
'name' => ['en' => 'different-name-en'],
], $rules);
$this->assertTrue($validation->passes());
$validation = Validator::make([
'slug' => ['nl' => 'existing-slug-nl'],
'name' => ['nl' => 'existing-name-nl'],
], $rules);
$this->assertTrue($validation->fails());
$validation = Validator::make([
'slug' => ['nl' => 'different-slug-nl'],
'name' => ['nl' => 'different-name-nl'],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function a_database_connection_can_be_specified()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
]);
$connection = Config::get('database.default');
$rules = [
'slug' => "{$this->rule}:{$connection}.{$this->table}",
'name' => UniqueTranslationRule::for("{$connection}.{$this->table}"),
];
$validation = Validator::make([
'slug' => 'existing-slug-en',
'name' => 'existing-name-en',
], $rules);
$this->assertTrue($validation->fails());
$validation = Validator::make([
'slug' => 'different-slug-en',
'name' => 'different-name-en',
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function the_models_attribute_name_can_be_specified()
{
Model::create([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
]);
$rules = [
'form_slug' => "{$this->rule}:{$this->table},slug",
'form_name' => UniqueTranslationRule::for($this->table, 'name'),
];
$validation = Validator::make([
'form_slug' => 'existing-slug-en',
'form_name' => 'existing-name-en',
], $rules);
$this->assertTrue($validation->fails());
$rules = [
'form_slug.*' => "{$this->rule}:{$this->table},slug",
'form_name.*' => UniqueTranslationRule::for($this->table, 'name'),
];
$validation = Validator::make([
'form_slug' => ['nl' => 'existing-slug-nl'],
'form_name' => ['nl' => 'existing-name-nl'],
], $rules);
$this->assertTrue($validation->fails());
}
/** @test */
public function it_ignores_the_given_id()
{
$model = Model::create([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
]);
$rules = [
'slug' => "{$this->rule}:{$this->table},null,{$model->id}",
'name' => UniqueTranslationRule::for($this->table)->ignore($model->id),
];
$validation = Validator::make([
'slug' => 'existing-slug-en',
'name' => 'existing-name-en',
], $rules);
$this->assertTrue($validation->passes());
$rules = [
'slug.*' => "{$this->rule}:{$this->table},null,{$model->id}",
'name.*' => UniqueTranslationRule::for($this->table)->ignore($model->id),
];
$validation = Validator::make([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_ignores_a_specific_attribute_with_the_given_value()
{
$model = Model::create([
'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],
'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],
'other_field' => 'foobar',
]);
$rules = [
'slug' => "{$this->rule}:{$this->table},null,{$model->other_field},other_field",
'name' => UniqueTranslationRule::for($this->table)->ignore($model->other_field, 'other_field'),
];
$validation = Validator::make([
'slug' => 'existing-slug-en',
'name' => 'existing-name-en',
], $rules);
$this->assertTrue($validation->passes());
$rules = [
'slug.*' => "{$this->rule}:{$this->table},null,{$model->other_field},other_field",
'name.*' => UniqueTranslationRule::for($this->table)->ignore($model->other_field, 'other_field'),
];
$validation = Validator::make([
'slug' => ['nl' => 'existing-slug-nl'],
'name' => ['nl' => 'existing-name-nl'],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_ignores_null_values()
{
Model::create([
'slug' => ['en' => null, 'nl' => 'existing-slug-nl'],
'name' => ['en' => null, 'nl' => 'existing-name-nl'],
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table}",
'name.*' => UniqueTranslationRule::for($this->table),
];
$validation = Validator::make([
'slug' => ['en' => null],
'name' => ['en' => null],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_validates_nova_translations()
{
Model::create([
'slug' => ['nl' => 'existing-slug-nl'],
'name' => ['nl' => 'existing-name-nl'],
]);
$rules = [
'translations_slug_nl' => "{$this->rule}:{$this->table},slug",
'translations_name_nl' => UniqueTranslationRule::for($this->table, 'slug'),
];
$validation = Validator::make([
'translations_slug_nl' => 'existing-slug-nl',
'translations_name_nl' => 'existing-name-nl',
], $rules);
$this->assertTrue($validation->fails());
$validation = Validator::make([
'translations_slug_nl' => 'different-slug-nl',
'translations_name_nl' => 'different-name-nl',
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_handles_backslashes_in_values()
{
Model::create([
'slug' => ['en' => '\existing-slug-en', 'nl' => '\existing-slug-nl'],
'name' => ['en' => '\existing-name-en', 'nl' => '\existing-name-nl'],
]);
$rules = [
'slug' => "{$this->rule}:{$this->table}",
'name' => UniqueTranslationRule::for($this->table),
];
// The following validation fails, because the
// current locale is "en", so we actually set
// ['en' => '\existing-slug-en'] etc.
$validation = Validator::make([
'slug' => '\existing-slug-en',
'name' => '\existing-name-en',
], $rules);
$this->assertTrue($validation->fails());
// The following validation passes, because the
// current locale is "en", so we actually set
// ['en' => '\existing-slug-nl'] etc.
$validation = Validator::make([
'slug' => '\existing-slug-nl',
'name' => '\existing-name-nl',
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_handles_arabic_language()
{
Model::create([
'slug' => ['ar' => 'جديد'],
'name' => ['ar' => 'جديد'],
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table}",
'name.*' => UniqueTranslationRule::for($this->table),
];
$validation = Validator::make([
'slug' => ['ar' => 'جديد'],
'name' => ['ar' => 'جديد'],
], $rules);
$this->assertTrue($validation->fails());
}
}
================================================
FILE: tests/ValidationMessageTest.php
================================================
<?php
namespace CodeZero\UniqueTranslation\Tests;
use CodeZero\UniqueTranslation\Tests\Stubs\Model;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
use Illuminate\Support\Facades\Validator;
class ValidationMessageTest extends TestCase
{
/** @test */
public function it_returns_a_default_error_message_when_validating_a_single_translation()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
]);
$formAttributes = [
'form_slug' => 'existing-slug-en',
'form_name' => 'existing-name-en',
];
$rules = [
'form_slug' => "{$this->rule}:{$this->table},slug",
'form_name' => UniqueTranslationRule::for($this->table, 'name'),
];
$expectedSlugError = trans('validation.unique', ['attribute' => 'form slug']);
$expectedNameError = trans('validation.unique', ['attribute' => 'form name']);
$this->assertNotEmpty($expectedSlugError);
$this->assertNotEmpty($expectedNameError);
$validation = Validator::make($formAttributes, $rules);
$this->assertEquals([
'form_slug' => [$expectedSlugError],
'form_name' => [$expectedNameError],
], $validation->errors()->messages());
}
/** @test */
public function it_returns_a_default_error_message_when_validating_an_array()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
]);
$formAttributes = [
'form_slug' => ['en' => 'existing-slug-en'],
'form_name' => ['en' => 'existing-name-en'],
];
$rules = [
'form_slug.*' => "{$this->rule}:{$this->table},slug",
'form_name.*' => UniqueTranslationRule::for($this->table, 'name'),
];
$expectedSlugError = trans('validation.unique', ['attribute' => 'form_slug.en']);
$expectedNameError = trans('validation.unique', ['attribute' => 'form_name.en']);
$this->assertNotEmpty($expectedSlugError);
$this->assertNotEmpty($expectedNameError);
$validation = Validator::make($formAttributes, $rules);
$this->assertEquals([
'form_slug.en' => [$expectedSlugError],
'form_name.en' => [$expectedNameError],
], $validation->errors()->messages());
}
/** @test */
public function it_returns_a_custom_error_message_when_validating_a_single_translation()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
]);
$formAttributes = [
'form_slug' => 'existing-slug-en',
'form_name' => 'existing-name-en',
];
$rules = [
'form_slug' => "{$this->rule}:{$this->table},slug",
'form_name' => UniqueTranslationRule::for($this->table, 'name'),
];
$messages = [
"form_slug.{$this->rule}" => 'Custom slug message for :attribute.',
"form_name.{$this->rule}" => 'Custom name message for :attribute.',
];
$expectedSlugError = 'Custom slug message for form slug.';
$expectedNameError = 'Custom name message for form name.';
$validation = Validator::make($formAttributes, $rules, $messages);
$this->assertEquals([
'form_slug' => [$expectedSlugError],
'form_name' => [$expectedNameError],
], $validation->errors()->messages());
}
/** @test */
public function it_returns_a_custom_error_message_when_validating_an_array()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
]);
$formAttributes = [
'form_slug' => ['en' => 'existing-slug-en'],
'form_name' => ['en' => 'existing-name-en'],
];
$rules = [
'form_slug.*' => "{$this->rule}:{$this->table},slug",
'form_name.*' => UniqueTranslationRule::for($this->table, 'name'),
];
$messages = [
"form_slug.*.{$this->rule}" => 'Custom slug message for :attribute.',
"form_name.*.{$this->rule}" => 'Custom name message for :attribute.',
];
$expectedSlugError = 'Custom slug message for form_slug.en.';
$expectedNameError = 'Custom name message for form_name.en.';
$validation = Validator::make($formAttributes, $rules, $messages);
$this->assertEquals([
'form_slug.en' => [$expectedSlugError],
'form_name.en' => [$expectedNameError],
], $validation->errors()->messages());
}
/** @test */
public function it_returns_a_default_error_message_when_validating_a_nova_translation()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
]);
$formAttributes = [
'translations_form_slug_en' => 'existing-slug-en',
'translations_form_name_en' => 'existing-name-en',
];
$rules = [
'translations_form_slug_en' => "{$this->rule}:{$this->table},slug",
'translations_form_name_en' => UniqueTranslationRule::for($this->table, 'name'),
];
$expectedSlugError = trans('validation.unique', ['attribute' => 'translations form slug en']);
$expectedNameError = trans('validation.unique', ['attribute' => 'translations form name en']);
$this->assertNotEmpty($expectedSlugError);
$this->assertNotEmpty($expectedNameError);
$validation = Validator::make($formAttributes, $rules);
$this->assertEquals([
'translations_form_slug_en' => [$expectedSlugError],
'translations_form_name_en' => [$expectedNameError],
], $validation->errors()->messages());
}
}
================================================
FILE: tests/WhereClauseTest.php
================================================
<?php
namespace CodeZero\UniqueTranslation\Tests;
use CodeZero\UniqueTranslation\Tests\Stubs\Model;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
use Illuminate\Support\Facades\Validator;
// * * *
// You can use any method defined in the DatabaseRule
// trait, except the whereIn and whereNotIn methods.
//
// https://laravel.com/api/5.8/Illuminate/Validation/Rules/DatabaseRule.html
//
// This is because it uses a closure which cannot be converted into a string.
// We need to convert the rule into a string to use it with the UniqueTranslationValidator.
// The reason we use this kind of validator is because it has access to the Validator instance.
// We need that instance to add custom error messages.
// * * *
class WhereClauseTest extends TestCase
{
/** @test */
public function it_accepts_where_clause()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
'other_field' => 'foobar',
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table},null,null,null,other_field,!foobar",
'name.*' => UniqueTranslationRule::for($this->table)->where('other_field', 'not foobar'),
];
$validation = Validator::make([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_accepts_where_not_clauses()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
'other_field' => 'foobar',
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table},null,null,null,other_field,!foobar",
'name.*' => UniqueTranslationRule::for($this->table)->whereNot('other_field', 'foobar'),
];
$validation = Validator::make([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_accepts_where_null_clause()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
'other_field' => 'foobar',
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table},null,null,null,other_field,NULL",
'name.*' => UniqueTranslationRule::for($this->table)->whereNull('other_field'),
];
$validation = Validator::make([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
], $rules);
$this->assertTrue($validation->passes());
}
/** @test */
public function it_accepts_where_not_null_clause()
{
Model::create([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
'other_field' => null,
]);
$rules = [
'slug.*' => "{$this->rule}:{$this->table},null,null,null,other_field,NOT_NULL",
'name.*' => UniqueTranslationRule::for($this->table)->whereNotNull('other_field'),
];
$validation = Validator::make([
'slug' => ['en' => 'existing-slug-en'],
'name' => ['en' => 'existing-name-en'],
], $rules);
$this->assertTrue($validation->passes());
}
}
gitextract_gcilwl22/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── SECURITY.md
│ └── workflows/
│ └── run-tests.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src/
│ ├── UniqueTranslationRule.php
│ ├── UniqueTranslationServiceProvider.php
│ └── UniqueTranslationValidator.php
└── tests/
├── Stubs/
│ └── Model.php
├── TestCase.php
├── UniqueTranslationTest.php
├── ValidationMessageTest.php
└── WhereClauseTest.php
SYMBOL INDEX (57 symbols across 8 files)
FILE: src/UniqueTranslationRule.php
class UniqueTranslationRule (line 7) | class UniqueTranslationRule
method for (line 40) | public static function for($table, $column = null)
method __construct (line 51) | public function __construct($table, $column = null)
method ignore (line 65) | public function ignore($value, $column = 'id')
method __toString (line 78) | public function __toString()
FILE: src/UniqueTranslationServiceProvider.php
class UniqueTranslationServiceProvider (line 8) | class UniqueTranslationServiceProvider extends ServiceProvider
method boot (line 15) | public function boot()
method register (line 25) | public function register()
FILE: src/UniqueTranslationValidator.php
class UniqueTranslationValidator (line 11) | class UniqueTranslationValidator
method validate (line 23) | public function validate($attribute, $value, $parameters, $validator)
method setMissingErrorMessages (line 51) | protected function setMissingErrorMessages($validator, $name, $locale)
method isNovaTranslation (line 76) | protected function isNovaTranslation($attribute)
method getNovaAttributeNameAndLocale (line 88) | protected function getNovaAttributeNameAndLocale($attribute)
method isFilamentTranslation (line 102) | protected function isFilamentTranslation($attribute)
method getFilamentAttributeNameAndLocale (line 114) | protected function getFilamentAttributeNameAndLocale($attribute, $vali...
method getArrayAttributeNameAndLocale (line 136) | protected function getArrayAttributeNameAndLocale($attribute)
method getAttributeNameAndLocale (line 149) | protected function getAttributeNameAndLocale($attribute, $delimiter)
method getAttributeLocale (line 165) | protected function getAttributeLocale($attribute, $delimiter)
method getAttributeName (line 181) | protected function getAttributeName($attribute, $locale, $delimiter)
method getConnectionAndTable (line 193) | protected function getConnectionAndTable($parameters)
method getParameter (line 214) | protected function getParameter($parameters, $index)
method convertNullValue (line 226) | protected function convertNullValue($value)
method isUnique (line 241) | protected function isUnique($value, $name, $locale, $parameters)
method findTranslation (line 269) | protected function findTranslation($connection, $table, $column, $loca...
method ignore (line 295) | protected function ignore($query, $column = null, $value = null)
method getUniqueExtra (line 316) | protected function getUniqueExtra($parameters)
method getExtraConditions (line 333) | protected function getExtraConditions(array $segments)
method addConditions (line 355) | protected function addConditions($query, $conditions)
method addWhere (line 374) | protected function addWhere($query, $key, $extraValue)
FILE: tests/Stubs/Model.php
class Model (line 8) | class Model extends EloquentModel
FILE: tests/TestCase.php
class TestCase (line 12) | abstract class TestCase extends BaseTestCase
method setUp (line 33) | protected function setUp(): void
method getPackageProviders (line 51) | protected function getPackageProviders($app)
method setupDatabase (line 63) | protected function setupDatabase()
FILE: tests/UniqueTranslationTest.php
class UniqueTranslationTest (line 10) | class UniqueTranslationTest extends TestCase
method it_checks_if_the_translation_for_the_current_locale_is_unique (line 13) | public function it_checks_if_the_translation_for_the_current_locale_is...
method search_is_case_insensitive (line 49) | public function search_is_case_insensitive()
method it_checks_if_the_translation_for_a_specific_locale_is_unique (line 70) | public function it_checks_if_the_translation_for_a_specific_locale_is_...
method a_database_connection_can_be_specified (line 112) | public function a_database_connection_can_be_specified()
method the_models_attribute_name_can_be_specified (line 142) | public function the_models_attribute_name_can_be_specified()
method it_ignores_the_given_id (line 175) | public function it_ignores_the_given_id()
method it_ignores_a_specific_attribute_with_the_given_value (line 208) | public function it_ignores_a_specific_attribute_with_the_given_value()
method it_ignores_null_values (line 242) | public function it_ignores_null_values()
method it_validates_nova_translations (line 263) | public function it_validates_nova_translations()
method it_handles_backslashes_in_values (line 291) | public function it_handles_backslashes_in_values()
method it_handles_arabic_language (line 327) | public function it_handles_arabic_language()
FILE: tests/ValidationMessageTest.php
class ValidationMessageTest (line 9) | class ValidationMessageTest extends TestCase
method it_returns_a_default_error_message_when_validating_a_single_translation (line 12) | public function it_returns_a_default_error_message_when_validating_a_s...
method it_returns_a_default_error_message_when_validating_an_array (line 44) | public function it_returns_a_default_error_message_when_validating_an_...
method it_returns_a_custom_error_message_when_validating_a_single_translation (line 76) | public function it_returns_a_custom_error_message_when_validating_a_si...
method it_returns_a_custom_error_message_when_validating_an_array (line 110) | public function it_returns_a_custom_error_message_when_validating_an_a...
method it_returns_a_default_error_message_when_validating_a_nova_translation (line 144) | public function it_returns_a_default_error_message_when_validating_a_n...
FILE: tests/WhereClauseTest.php
class WhereClauseTest (line 21) | class WhereClauseTest extends TestCase
method it_accepts_where_clause (line 24) | public function it_accepts_where_clause()
method it_accepts_where_not_clauses (line 46) | public function it_accepts_where_not_clauses()
method it_accepts_where_null_clause (line 68) | public function it_accepts_where_null_clause()
method it_accepts_where_not_null_clause (line 90) | public function it_accepts_where_not_null_clause()
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (55K chars).
[
{
"path": ".editorconfig",
"chars": 337,
"preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.o"
},
{
"path": ".gitattributes",
"chars": 361,
"preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
},
{
"path": ".github/FUNDING.yml",
"chars": 59,
"preview": "ko_fi: ivanvermeyen\ncustom: https://paypal.me/ivanvermeyen\n"
},
{
"path": ".github/SECURITY.md",
"chars": 130,
"preview": "# Security Policy\n\nIf you discover any security related issues, please email ivan@codezero.be instead of using the issue"
},
{
"path": ".github/workflows/run-tests.yml",
"chars": 2298,
"preview": "name: Tests\n\non: [ push, pull_request ]\n\njobs:\n test:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: true\n "
},
{
"path": ".gitignore",
"chars": 79,
"preview": "/vendor\n.idea\ncomposer.lock\nphpunit.xml\n/.phpunit.result.cache\n/.phpunit.cache\n"
},
{
"path": "LICENSE.md",
"chars": 1135,
"preview": "# The MIT License (MIT)\n\nCopyright (c) 2017 Ivan Vermeyen (<ivan@codezero.be>)\n\n> Permission is hereby granted, free of "
},
{
"path": "README.md",
"chars": 9673,
"preview": "# Laravel Unique Translation\n\n## IMPORTANT: March 2022\n\n[. The extraction includes 18 files (50.9 KB), approximately 13.4k tokens, and a symbol index with 57 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.