diff --git a/.gitattributes b/.gitattributes index 08f809af71..1d3d767fd3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Autodetect text files -* text=auto +* text=auto eol=lf # ...Unless the name matches the following overriding patterns diff --git a/docs/internals-fa/core-code-style.md b/docs/internals-fa/core-code-style.md index e63e2b7795..3becc5802b 100644 --- a/docs/internals-fa/core-code-style.md +++ b/docs/internals-fa/core-code-style.md @@ -1,49 +1,52 @@ رعایت اصول و سبک کدنویسی فریمورک Yii2 =============================== -

-سبک کدنویسی که در نسخه 2 فریمورک و extension های رسمی استفاده میشه دارای اصول، قواعد و قانون های خودش هست. پس اگر تصمیم دارید چیزی به هسته اضافه کنید باید این قواعد رو در نظر بگیرید حتی در غیر این صورت هم رعایت این موارد خالی از لطف نیست و توصیه میکنم این کارُ انجام بدین. در حالی که میتونید راحت باشید، شما مجبور به رعایت این سبک در application خودتون نیستید... +

+سبک کدنویسی که در نسخه 2 فریمورک و extension های رسمی استفاده میشه دارای اصول، قواعد و قانون های خودش هست. پس اگر تصمیم دارید چیزی به هسته اضافه کنید باید این قواعد رو در نظر بگیرید حتی در غیر این صورت هم رعایت این موارد خالی از لطف نیست و توصیه می‌کنم این کارو انجام بدین. + +البته که نیاز نیست حتما این موارد رو در برنامه‌های خودتون رعایت کنید و می تونید در این مورد راحت باشید...

-

-میتونید برای دریافت پیکره بندی CodeSniffer اینجا رو مطالعه کنید: https://github.com/yiisoft/yii2-coding-standards +

+می‌تونید برای دریافت پیکربندی CodeSniffer اینجا رو مطالعه کنید: https://github.com/yiisoft/yii2-coding-standards

## 1. نگاه کلی -

-به طور کلی ما از سبک PSR-2 استفاده میکنیم و هر چیزی که در این سبک وجود داره اینجا هم هست. -(https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)

+

+به طور کلی ما از سبک کدنویسی PSR-2 پیروی می‌کنیم: -

در فایل ها باید از برچسب های php?> و =?> استفاده شود.

-

در پایان هر فایل باید یک خط جدید(newline) داشته باشید.

-

encoding فایل برای کد های php باید UTF-8 without BOM باشد.

-

به جای tab از 4 فضای خالی(space) استفاده کنید.

-

نام کلاس ها باید به صورت StudlyCaps تعریف شوند.

-

ثابت های داخل کلاس تماما باید با حروف بزرگ و گاهی با جداکننده "_" تعریف شوند.

-

نام متد ها و پراپرتی ها باید به صورت camelCase تعریف شوند.

-

پراپرتی های خصوصی(private) باید با "_" شروع شوند.

-

همیشه از elseif جای else if استفاده کنید.

+https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md

-## 2. فایل ها +

در فایل‌ها باید از برچسب‌های php?> و =?> استفاده شود.

+

در پایان هر فایل باید یک خط جدید (newline) داشته باشید.

+

encoding فایل برای کدهای php باید UTF-8 without BOM باشد.

+

به جای tab از 4 فضای خالی (space) استفاده کنید.

+

نام کلاس‌ها باید به صورت StudlyCaps تعریف شوند.

+

ثابت‌های داخل کلاس تماما باید با حروف بزرگ و گاهی با جداکننده "_" تعریف شوند.

+

نام متدها و پراپرتی‌ها باید به صورت camelCase تعریف شوند.

+

پراپرتی‌های خصوصی (private) باید با "_" شروع شوند.

+

همیشه از elseif جای else if استفاده کنید.

-

در فایل ها باید از برچسب های php?> و =?> استفاده کرد نه از ?> .

-

در انتهای فایل های php نباید از تگ -

در انتهای هر خط نباید space وجود داشته باشد

-

پسوند فایل هایی که شامل کد php هستند باید php. باشد.

-

encoding فایل برای کد های php باید UTF-8 without BOM باشد.

+## 2. فایل‌ها + +

در فایل‌ها باید از برچسب های php?> و =?> استفاده کرد نه از ?> .

+

در انتهای فایل‌های php نباید از تگ ?> استفاده کنید.

+

در انتهای هر خط نباید space وجود داشته باشد.

+

پسوند فایل‌هایی که شامل کد php هستند باید php. باشد.

+

encoding فایل برای کدهای php باید UTF-8 without BOM باشد.

-## 3. نام کلاس ها -

-نام کلاس ها باید به صورت StudlyCaps تعریف شوند. به عنوان مثال, `Controller`, `Model`.

+## 3. نام کلاس‌ها +

+نام کلاس‌ها باید به صورت StudlyCaps تعریف شوند. به عنوان مثال، `Controller` و `Model`.

-## 4. کلاس ها +## 4. کلاس‌ها -

نام کلاس ها باید به صورت CamelCase تعریف شوند.

-

آکولاد باز باید در خط بعدی، زیر نام کلاس نوشته شود.

-

تمام کلاس ها باید بلاک مستندات مطابق استاندارد PHPDoc داشته باشند.

-

برای تمام کد های داخل کلاس باید با 4 space فاصله ایجاد کنید.

-

فقط یک کلاس داخل هر فایل php باید موجود باشد.

-

تمام کلاس ها باید namespaced داشته باشند.

-

نام کلاس باید معال نام فایل و namespace باید مطابق مسیر آن باشد.

+

نام کلاس‌ها باید به صورت CamelCase تعریف شوند.

+

آکولاد باز باید در خط بعدی، زیر نام کلاس نوشته شود.

+

تمام کلاس‌ها باید بلاک مستندات مطابق استاندارد PHPDoc داشته باشند.

+

برای تمام کدهای داخل کلاس باید با 4 space فاصله ایجاد کنید.

+

فقط یک کلاس داخل هر فایل php باید موجود باشد.

+

تمام کلاس‌ها باید namespaced داشته باشند.

+

نام کلاس باید معادل نام فایل و namespace باید مطابق مسیر آن باشد.

```php /** @@ -55,9 +58,9 @@ class MyClass extends \yii\base\BaseObject implements MyInterface } ``` -### 4.1. ثابت ها -

-ثابت های داخل کلاس تماما باید با حروف بزرگ و گاهی با جداکننده "_" تعریف شوند.

+### 4.1. ثابت‌ها +

+ثابت‌های داخل کلاس تماما باید با حروف بزرگ و گاهی با جداکننده "_" تعریف شوند.

```php از کلید واژه های public، protected و private استفاده کنید.

-

پراپرتی های public و protected باید در بالای کلاس و قبل از متد ها تعریف شوند. private هم همینطور اما ممکن هست کاهی قبل از متدی که با آن مرتبط هست آورده شود.

-

ترتیب تعریف پراپرتی ها باید به صورت اول public، دوم protected و سپس private باشد! هیچ قانون سختی برای رعایت این مورد نیست...

-

برای خوانایی بهتر میتونید از خط خالی بین گروه های public، protected و private استفاده کنید. -

متغییر های private باید مثل varName_$ باشند.

-

اعضای عمومی داخل کلاس باید به صورت camelCase تعریف شوند.(حرف اول کوچک، با CamelCase فرق میکنه).

-

بهتره از نام هایی مثل i$ و j$ استفاده نکنید.

+

از کلید واژه های public ،protected و private استفاده کنید.

+

پراپرتی‌های public و protected باید در بالای کلاس و قبل از متدها تعریف شوند. private هم همینطور اما ممکن هست گاهی قبل از متدی که با آن مرتبط هست آورده شود.

+

ترتیب تعریف پراپرتی‌ها باید به صورت اول public، دوم protected و سپس private باشد! کار بسیار ساده‌ایست :)

+

برای خوانایی بهتر می‌تونید از خط خالی بین گروه‌های public، protected و private استفاده کنید. +

متغیر های private باید مثل varName_$ باشند.

+

اعضای عمومی داخل کلاس باید به صورت camelCase تعریف شوند. (حرف اول کوچک، با CamelCase فرق میکنه)

+

بهتره از نام‌هایی مثل i$ و j$ استفاده نکنید.

```php توابع و متد ها باید camelCase باشند.

-

نام باید هدف رو نشون بده.

-

از کلید واژه های public، protected و private استفاده کنید.

-

آکولاد باز باید در خط بعدی یعنی زیر نام متد قرار بگیره.

+

توابع و متدها باید camelCase باشند.

+

نام باید هدف رو نشون بده.

+

از کلید واژه های public، protected و private استفاده کنید.

+

آکولاد باز باید در خط بعدی یعنی زیر نام متد قرار بگیره.

```php /** @@ -120,14 +123,14 @@ class Foo } ``` -### 4.4 بلوک های PHPDoc +### 4.4 بلوک‌های PHPDoc -

برای متد ها باید مستندات بنویسید(PHPDoc).

-

در PHPDoc نوع param@, var@, property@ و return@ باید مشخص شود(bool, int, string, array یا null).

-

برای تایپ آرایه در PHPDoc از []ClassName استفاده کنید.

-

خط اول PHPDoc باید هدف یک متد رو شرح بده.

-

اگر متد ها چیزی رو بررسی میکنن مثل isActive بخش PHPDoc رو باید با عبارت Checks whether شروع کنید.

-

return@ در PHPDoc یاید دقیقا مشخص کنه چی بازگردانده میشود.

+

برای متدها باید مستندات بنویسید (PHPDoc).

+

در PHPDoc نوع param@ ،var@ ،property@ و return@ باید مشخص شود (bool, int, string, array یا null).

+

برای تایپ آرایه در PHPDoc از []ClassName استفاده کنید.

+

خط اول PHPDoc باید هدف یک متد رو شرح بده.

+

اگر متدها چیزی رو بررسی میکنن مثل isActive بخش PHPDoc رو باید با عبارت Checks whether شروع کنید.

+

return@ در PHPDoc یاید دقیقا مشخص کنه چی بازگردانده میشه.

```php /** @@ -146,14 +149,14 @@ class Foo ### 4.5 Constructors -

`__construct` باید به جای استایل PHP 4 constructors استفاده شود.

+

`__construct` باید به جای استایل PHP 4 constructors استفاده شود.

## 5 PHP -### 5.1 نوع ها +### 5.1 نوع‌ها -

تمام انواع و مقادیر باید با حروف کوچک نوشته شوند مثل true, false, null و array.

-

تغییر نوع یک متغییر خیلی بده، به این مثال توجه کنید:

+

تمام انواع و مقادیر باید با حروف کوچک نوشته شوند مثل true ،false ،null و array.

+

تغییر نوع یک متغیر خیلی بده، به این مثال توجه کنید:

```php @@ -164,22 +167,22 @@ public function save(Transaction $transaction, $argument2 = 100) } ``` -### 5.2 رشته ها +### 5.2 رشته‌ها -

اگر رشته ی شما شامل متغییر های دیگه این نیست از تک کوتیشن جای دابل کوتیشن استفاده کنید.

+

اگر رشته‌ی شما شامل متغیرهای دیگه‌ای نیست از تک‌کوتیشن جای دابل‌کوتیشن استفاده کنید.

```php $str = 'Like this.'; ``` -

دو روش زیر مناسب برای جایگزینی هستند:

+

دو روش زیر مناسب برای جایگزینی هستند:

```php $str1 = "Hello $username!"; $str2 = "Hello {$username}!"; ``` -

-حالت زی مجاز نیست:

+

+حالت زیر مجاز نیست:

```php $str3 = "Hello ${username}!"; @@ -187,13 +190,13 @@ $str3 = "Hello ${username}!"; #### الحاق -

برای الحاق قبل و بعد کاراکتر dot فاصله بذارید

+

برای الحاق قبل و بعد کاراکتر dot فاصله بذارید:

```php $name = 'Yii' . ' Framework'; ``` -

و اگر رشته ی شما بلند بود میتونید اینطور عمل کنید:

+

و اگر رشته ی شما بلند بود می‌تونید اینطور عمل کنید:

```php $sql = "SELECT *" @@ -201,11 +204,11 @@ $sql = "SELECT *" . "WHERE `id` = 121 "; ``` -### 5.3 آرایه ها +### 5.3 آرایه‌ها -

برای تعریف آرایه ها از نحوه ی کوتاه اون یعنی [] استفاده کنید.

-

از ایندکس منفی در آرایه ها استفاده نکنید.

-

روش های زیر قابل قبول و مناسب هستند:

+

برای تعریف آرایه‌ها از ساختار کوتاه اون یعنی [] استفاده کنید.

+

از ایندکس منفی در آرایه‌ها استفاده نکنید.

+

روش‌های زیر قابل قبول و مناسب هستند:

```php $arr = [3, 14, 15, 'Yii', 'Framework']; @@ -228,10 +231,10 @@ $config = [ ### 5.4 دستورات کنترلی -

در دستورات کنترلی قبل و بعد پرانتز space بذارید.

-

آکولاد باز در همان خط دستور قرار میگیرد.

-

آکولاد بسته در خط جدید.

-

برای دستورات یک خطی همیشه از پرانتز استفاده کنید.

+

در دستورات کنترلی قبل و بعد پرانتز space بذارید.

+

آکولاد باز در همان خط دستور قرار میگیرد.

+

آکولاد بسته در خط جدید.

+

برای دستورات یک خطی همیشه از پرانتز استفاده کنید.

```php if ($event === null) { @@ -248,7 +251,7 @@ if (!$model && null === $event) throw new Exception('test'); ``` -

بعد از return از else استفاده نکنید

+

بعد از return از else استفاده نکنید:

```php $result = $this->getResult(); @@ -258,7 +261,7 @@ if (empty($result)) { // process result } ``` -

اینطوری بهتره

+

اینطوری بهتره:

```php @@ -272,8 +275,8 @@ if (empty($result)) { #### switch -

از فرمت زیر برای switch استفاده کنید -

+

از فرمت زیر برای switch استفاده کنید:

+ ```php switch ($this->phpType) { case 'string': @@ -291,9 +294,9 @@ switch ($this->phpType) { } ``` -### 5.5 function calls +### 5.5 صدا زدن فانکشن‌ها -

روش مناسب صدا زدن توابع همراه با پارامتر ها هم اینطور صحیحه

+

روش مناسب صدا زدن فانکشن‌ها همراه با پارامتر هم به این صورته:

```php doIt(2, 3); @@ -308,7 +311,7 @@ doIt('a', [ ### 5.6 تعریف Anonymous functions (lambda) -

در توابع بی نام بین function/use فضای خالی(space) بذارید.

+

در توابع بی نام بین function/use فضای خالی (space) بذارید:

```php // good @@ -328,14 +331,14 @@ $mul = array_reduce($numbers, function($r, $x) use($n) { }); ``` -مستند نویسی +مستند نویسی ------------- -

[phpDoc](https://phpdoc.org/) رو بخونید و موارد اونُ رعایت کنید.

-

کد بدون مستندات مجاز نیست.

-

تمام کلاس ها باید شامل بلاک مستندات در ابتدای فایل باشند.

-

نیازی به نوشتن return@ ندارید اگر متد شما اگر چیزی را برنمیگرداند.

-

به مثال های زیر توجه کنید:

+

https://phpdoc.org رو بخونید و موارد اون رو رعایت کنید.

+

کد بدون مستندات مجاز نیست.

+

تمام کلاس‌ها باید شامل بلاک مستندات در ابتدای فایل باشند.

+

نیازی به نوشتن return@ ندارید اگر متد شما چیزی برنمی‌گرداند.

+

به مثال‌های زیر توجه کنید:

```php getEventHandlers($eventName)->insertAt(0, $eventHandler); - * ``` - * - * @param string $name the event name - * @return Vector list of attached event handlers for the event - * @throws Exception if the event is not defined - */ -public function getEventHandlers($name) -{ - if (!isset($this->_e[$name])) { - $this->_e[$name] = new Vector; - } - $this->ensureBehaviors(); - return $this->_e[$name]; -} +* $component->getEventHandlers($eventName)->insertAt(0, $eventHandler); +* ``` +* +* @param string $name the event name +* @return Vector list of attached event handlers for the event +* @throws Exception if the event is not defined + */ + public function getEventHandlers($name) + { + if (!isset($this->_e[$name])) { + $this->_e[$name] = new Vector; + } + $this->ensureBehaviors(); + return $this->_e[$name]; + } ``` #### نظرات -

از // برای کامنت گذاری استفاده کنید نه از #.

-

در خطوطی که کامنت گذاشتین نباید کد بنویسید، یعنی اون خط برای اون کامنت باید باشه.

+

از // برای کامنت گذاری استفاده کنید نه از #.

+

در خطوطی که کامنت گذاشتین نباید کد بنویسید، یعنی اون خط برای اون کامنت باید باشه.

قوانین بیشتر ---------------- -

تا جایی که میتونید از تابع empty به جای === استفاده کنید.

-

اگر شرایط تو در تویی در کد شما وجود نداره return زود هنگام یا ساده تر بگم return وسط متد مشکلی نخواهد داشت.

-

همیشه از static جای self به جز موارد زیر استفاده کنید:

-

1) دسترسی به ثابت ها باید با self انجام بشه.

-

2) دسترسی به پراپرتی های خصوصی باید با self انجام بشه.

-

3) مجاز به استفاده از self برای صدا زدن توابع در مواقعی مثل فراخوانی بازگشتی هستید.

+

تا جایی که می‌تونید از تابع empty به جای === استفاده کنید.

+

اگر شرایط تو در تویی در کد شما وجود نداره return زود هنگام یا ساده تر بگم return وسط متد مشکلی نخواهد داشت.

+

همیشه از static جای self به جز موارد زیر استفاده کنید:

+

1) دسترسی به ثابت‌ها باید با self انجام بشه.

+

2) دسترسی به پراپرتی‌های خصوصی باید با self انجام بشه.

+

3) مجاز به استفاده از self برای صدا زدن توابع در مواقعی مثل فراخوانی بازگشتی هستید.

-namespace ها +نیم‌اسپیس‌ها ---------------- -

از حرف کوچک استفاده کنید.

-

از فرم جمع اسم ها برای نشان دادن یک شی استفاده کنید مثل validators.

-

از فرم منفرد اسم ها برای قابلیت ها و امکانات استفاده کنید مثل web.

-

بهتره فضای نام تک کلمه ای باشه در غیر این صورت از camelCase استفاده کنید.

- +

از حرف کوچک استفاده کنید.

+

از فرم جمع اسم‌ها برای نشان دادن یک شی استفاده کنید مثل validators.

+

از فرم مفرد اسم‌ها برای قابلیت‌ها و امکانات استفاده کنید مثل web.

+

بهتره فضای نام تک‌کلمه‌ای باشه در غیر این صورت از camelCase استفاده کنید.

diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index a975e77a18..b99a3202fb 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -11,13 +11,19 @@ Yii Framework 2 Change Log 2.0.50 under development ------------------------ - +- Bug #20045: Fix type `boolean` in `MySQL` (terabytesoftw) +- Bug #20040: Fix type `boolean` in `MSSQL` (terabytesoftw) - Bug #20005: Fix `yii\console\controllers\ServeController` to specify the router script (terabytesoftw) - Bug #19060: Fix `yii\widgets\Menu` bug when using Closure for active item and adding additional tests in `tests\framework\widgets\MenuTest` (atrandafir) - Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher) - Bug #19927: Fixed `console\controllers\MessageController` when saving translations to database: fixed FK error when adding new string and language at the same time, checking/regenerating all missing messages and dropping messages for unused languages (atrandafir) - Bug #20002: Fixed superfluous query on HEAD request in serializer (xicond) - Enh #12743: Added new methods `BaseActiveRecord::loadRelations()` and `BaseActiveRecord::loadRelationsFor()` to eager load related models for existing primary model instances (PowerGamer1) +- Enh #20030: Improve performance of handling `ErrorHandler::$memoryReserveSize` (antonshevelev, rob006) +- Enh #20042: Add empty array check to `ActiveQueryTrait::findWith()` (renkas) +- Enh #20032: Added `yii\helpers\BaseStringHelper::mask()` method for string masking with multibyte support (salehhashemi1992) +- Enh #20034: Added `yii\helpers\BaseStringHelper::findBetween()` to retrieve a substring that lies between two strings (salehhashemi1992) + 2.0.49.2 October 12, 2023 ------------------------- diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index 81a20f0a3c..393438ede5 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -94,7 +94,7 @@ abstract class ErrorHandler extends Component set_error_handler([$this, 'handleError']); } if ($this->memoryReserveSize > 0) { - $this->_memoryReserve = str_pad('', $this->memoryReserveSize, 'x'); + $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize); } // to restore working directory in shutdown handler if (PHP_SAPI !== 'cli') { diff --git a/framework/db/ActiveQueryTrait.php b/framework/db/ActiveQueryTrait.php index e2de1dd12d..d49fa0fba8 100644 --- a/framework/db/ActiveQueryTrait.php +++ b/framework/db/ActiveQueryTrait.php @@ -135,6 +135,10 @@ trait ActiveQueryTrait */ public function findWith($with, &$models) { + if (empty($models)) { + return; + } + $primaryModel = reset($models); if (!$primaryModel instanceof ActiveRecordInterface) { /* @var $modelClass ActiveRecordInterface */ diff --git a/framework/db/mssql/Schema.php b/framework/db/mssql/Schema.php index a3fe3e3294..e20e6aa342 100644 --- a/framework/db/mssql/Schema.php +++ b/framework/db/mssql/Schema.php @@ -375,6 +375,7 @@ SQL; */ protected function loadColumnSchema($info) { + $isVersion2017orLater = version_compare($this->db->getSchema()->getServerVersion(), '14', '>='); $column = $this->createColumnSchema(); $column->name = $info['column_name']; @@ -395,7 +396,7 @@ SQL; $column->type = $this->typeMap[$type]; } - if ($type === 'bit') { + if ($isVersion2017orLater && $type === 'bit') { $column->type = 'boolean'; } @@ -406,6 +407,10 @@ SQL; if (isset($values[1])) { $column->scale = (int) $values[1]; } + + if ($isVersion2017orLater === false) { + $column->type = $this->booleanTypeLegacy($column->size, $type); + } } } @@ -811,4 +816,27 @@ SQL; { return Yii::createObject(ColumnSchemaBuilder::class, [$type, $length, $this->db]); } + + /** + * Assigns a type boolean for the column type bit, for legacy versions of MSSQL. + * + * @param int $size column size. + * @param string $type column type. + * + * @return string column type. + */ + private function booleanTypeLegacy($size, $type) + { + if ($size === 1 && ($type === 'tinyint' || $type === 'bit')) { + return 'boolean'; + } elseif ($type === 'bit') { + if ($size > 32) { + return 'bigint'; + } elseif ($size === 32) { + return 'integer'; + } + } + + return $type; + } } diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index 38dec4ca03..846e5f33ce 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -279,7 +279,7 @@ SQL; if (isset($values[1])) { $column->scale = (int) $values[1]; } - if ($column->size === 1 && $type === 'bit') { + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { $column->type = 'boolean'; } elseif ($type === 'bit') { if ($column->size > 32) { diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index 60261f9828..21a1f9e643 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -497,4 +497,63 @@ class BaseStringHelper return implode('', $parts); } + + /** + * Masks a portion of a string with a repeated character. + * This method is multibyte-safe. + * + * @param string $string The input string. + * @param int $start The starting position from where to begin masking. + * This can be a positive or negative integer. + * Positive values count from the beginning, + * negative values count from the end of the string. + * @param int $length The length of the section to be masked. + * The masking will start from the $start position + * and continue for $length characters. + * @param string $mask The character to use for masking. The default is '*'. + * @return string The masked string. + */ + public static function mask($string, $start, $length, $mask = '*') { + $strLength = mb_strlen($string, 'UTF-8'); + + // Return original string if start position is out of bounds + if ($start >= $strLength || $start < -$strLength) { + return $string; + } + + $masked = mb_substr($string, 0, $start, 'UTF-8'); + $masked .= str_repeat($mask, abs($length)); + $masked .= mb_substr($string, $start + abs($length), null, 'UTF-8'); + + return $masked; + } + + /** + * Returns the portion of the string that lies between the first occurrence of the start string + * and the last occurrence of the end string after that. + * + * @param string $string The input string. + * @param string $start The string marking the start of the portion to extract. + * @param string $end The string marking the end of the portion to extract. + * @return string|null The portion of the string between the first occurrence of + * start and the last occurrence of end, or null if either start or end cannot be found. + */ + public static function findBetween($string, $start, $end) + { + $startPos = mb_strpos($string, $start); + + if ($startPos === false) { + return null; + } + + // Cut the string from the start position + $subString = mb_substr($string, $startPos + mb_strlen($start)); + $endPos = mb_strrpos($subString, $end); + + if ($endPos === false) { + return null; + } + + return mb_substr($subString, 0, $endPos); + } } diff --git a/framework/messages/fa/yii.php b/framework/messages/fa/yii.php index 86d08922ca..e9213b3482 100644 --- a/framework/messages/fa/yii.php +++ b/framework/messages/fa/yii.php @@ -27,7 +27,7 @@ return [ '"{attribute}" does not support operator "{operator}".' => '"{attribute}" از عملگر "{operator}" پشتیبانی نمی‌کند.', '(not set)' => '(تنظیم نشده)', 'Action not found.' => 'عمل یافت نشد.', - 'Aliases available: {aliases}' => 'نام مستعارهای موجود: {aliases}', + 'Aliases available: {aliases}' => 'نام‌های مستعار موجود: {aliases}', 'An internal server error occurred.' => 'خطای داخلی سرور رخ داده است.', 'Are you sure you want to delete this item?' => 'آیا اطمینان به حذف این مورد دارید؟', 'Condition for "{attribute}" should be either a value or valid operator specification.' => 'شرط برای "{attribute}" باید یک مقدار یا مشخصه‌ی عملگر معتبر باشد.', @@ -49,11 +49,11 @@ return [ 'Page not found.' => 'صفحه‌ای یافت نشد.', 'Please fix the following errors:' => 'لطفاً خطاهای زیر را رفع نمائید:', 'Please upload a file.' => 'لطفاً یک فایل آپلود کنید.', - 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'نمایش {begin, number} تا {end, number} مورد از کل {totalCount, number} مورد.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'نمایش {begin, number} تا {end, number} مورد از کل {totalCount, number} مورد.', 'The combination {values} of {attributes} has already been taken.' => 'مقدار {values} از {attributes} قبلاً گرفته شده است.', 'The file "{file}" is not an image.' => 'فایل "{file}" یک تصویر نیست.', 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'حجم فایل "{file}" بسیار بیشتر می‌باشد. حجم آن نمی‌تواند از {formattedLimit} بیشتر باشد.', - 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'حجم فایل "{file}" بسیار کم می‌باشد. حجم آن‌نمی تواند از {formattedLimit} کمتر باشد.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'حجم فایل "{file}" بسیار کم می‌باشد. حجم آن نمی‌تواند از {formattedLimit} کمتر باشد.', 'The format of {attribute} is invalid.' => 'قالب {attribute} نامعتبر است.', 'The format of {filter} is invalid.' => 'قالب {filter} نامعتبر است.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. ارتفاع نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', @@ -87,7 +87,7 @@ return [ '{attribute} is invalid.' => '{attribute} معتبر نیست.', '{attribute} is not a valid URL.' => '{attribute} یک URL معتبر نیست.', '{attribute} is not a valid email address.' => '{attribute} یک آدرس ایمیل معتبر نیست.', - '{attribute} is not in the allowed range.' => '{attribute} در محدوده مجاز نمی‎باشد.', + '{attribute} is not in the allowed range.' => '{attribute} در محدوده مجاز نمی‌باشد.', '{attribute} must be "{requiredValue}".' => '{attribute} باید "{requiredValue}" باشد.', '{attribute} must be a number.' => '{attribute} باید یک عدد باشد.', '{attribute} must be a string.' => '{attribute} باید یک رشته باشد.', diff --git a/tests/framework/caching/DbDependencyTest.php b/tests/framework/caching/DbDependencyTest.php index a629b866f6..a87a87e854 100644 --- a/tests/framework/caching/DbDependencyTest.php +++ b/tests/framework/caching/DbDependencyTest.php @@ -21,7 +21,6 @@ class DbDependencyTest extends DatabaseTestCase */ protected $driverName = 'sqlite'; - /** * {@inheritdoc} */ @@ -39,11 +38,14 @@ class DbDependencyTest extends DatabaseTestCase $db->createCommand()->insert('dependency_item', ['value' => 'initial'])->execute(); } - public function testIsChanged(): void + public function testQueryOneIsExecutedWhenQueryCacheEnabled() { $db = $this->getConnection(false); $cache = new ArrayCache(); + // Enable the query cache + $db->enableQueryCache = true; + $dependency = new DbDependency(); $dependency->db = $db; $dependency->sql = 'SELECT [[id]] FROM {{dependency_item}} ORDER BY [[id]] DESC LIMIT 1'; @@ -56,4 +58,39 @@ class DbDependencyTest extends DatabaseTestCase $this->assertTrue($dependency->isChanged($cache)); } + + public function testQueryOneIsExecutedWhenQueryCacheDisabled() + { + $db = $this->getConnection(false); + $cache = new ArrayCache(); + + // Disable the query cache + $db->enableQueryCache = false; + + $dependency = new DbDependency(); + $dependency->db = $db; + $dependency->sql = 'SELECT [[id]] FROM {{dependency_item}} ORDER BY [[id]] DESC LIMIT 1'; + $dependency->reusable = false; + + $dependency->evaluateDependency($cache); + $this->assertFalse($dependency->isChanged($cache)); + + $db->createCommand()->insert('dependency_item', ['value' => 'new'])->execute(); + + $this->assertTrue($dependency->isChanged($cache)); + } + + public function testMissingSqlThrowsException() + { + $this->expectException('\yii\base\InvalidConfigException'); + + $db = $this->getConnection(false); + $cache = new ArrayCache(); + + $dependency = new DbDependency(); + $dependency->db = $db; + $dependency->sql = null; + + $dependency->evaluateDependency($cache); + } } diff --git a/tests/framework/db/mysql/SchemaTest.php b/tests/framework/db/mysql/SchemaTest.php index a0b2ec2852..cea3013234 100644 --- a/tests/framework/db/mysql/SchemaTest.php +++ b/tests/framework/db/mysql/SchemaTest.php @@ -228,9 +228,13 @@ SQL; ] ); + $columns['bool_col']['type'] = 'boolean'; + $columns['bool_col']['phpType'] = 'boolean'; + $columns['bool_col2']['type'] = 'boolean'; + $columns['bool_col2']['phpType'] = 'boolean'; + if (version_compare($version, '5.7', '<')) { $columns['int_col3']['phpType'] = 'string'; - $columns['json_col']['type'] = 'text'; $columns['json_col']['dbType'] = 'longtext'; $columns['json_col']['phpType'] = 'string'; diff --git a/tests/framework/db/mysql/type/BooleanTest.php b/tests/framework/db/mysql/type/BooleanTest.php new file mode 100644 index 0000000000..bc9d307a78 --- /dev/null +++ b/tests/framework/db/mysql/type/BooleanTest.php @@ -0,0 +1,249 @@ +getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `false` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => false, 'bool_col_bit' => false])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + $this->assertEquals(0, $boolValues['bool_col_tinyint']); + $this->assertEquals(0, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertFalse($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertFalse($phpTypeCastBoolColBit); + + // test value `true` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => true, 'bool_col_bit' => true])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 2")->queryOne(); + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueInteger() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `0` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 0, 'bool_col_bit' => 0])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + $this->assertEquals(0, $boolValues['bool_col_tinyint']); + $this->assertEquals(0, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertFalse($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertFalse($phpTypeCastBoolColBit); + + // test value `1` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 1, 'bool_col_bit' => 1])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 2")->queryOne(); + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueNegative() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `-1` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => -1, 'bool_col_bit' => -1])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueNull() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `null` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => null, 'bool_col_bit' => null])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + + $this->assertNull($boolValues['bool_col_tinyint']); + $this->assertNull($boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertNull($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertNull($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueOverflow() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `2` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 2, 'bool_col_bit' => 2])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } +} diff --git a/tests/framework/db/pgsql/type/BooleanTest.php b/tests/framework/db/pgsql/type/BooleanTest.php new file mode 100644 index 0000000000..4a9b21df24 --- /dev/null +++ b/tests/framework/db/pgsql/type/BooleanTest.php @@ -0,0 +1,239 @@ +getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + ] + )->execute(); + + // test type `boolean` + $column = $db->getTableSchema($tableName)->getColumn('bool_col'); + $this->assertSame('boolean', $column->phpType); + + // test value `false` + $db->createCommand()->insert($tableName, ['bool_col' => false])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar(); + $this->assertEquals(0, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertFalse($phpTypeCast); + + // test value `true` + $db->createCommand()->insert($tableName, ['bool_col' => true])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 2")->queryScalar(); + $this->assertEquals(1, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertTrue($phpTypeCast); + } + + public function testBooleanWithValueInteger() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + ] + )->execute(); + + // test type `boolean` + $column = $db->getTableSchema($tableName)->getColumn('bool_col'); + $this->assertSame('boolean', $column->phpType); + + // test value `0` + $db->createCommand()->insert($tableName, ['bool_col' => 0])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar(); + $this->assertEquals(0, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertFalse($phpTypeCast); + + // test value `1` + $db->createCommand()->insert($tableName, ['bool_col' => 1])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 2")->queryScalar(); + $this->assertEquals(1, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertTrue($phpTypeCast); + } + + public function testBooleanWithValueNegative() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + ] + )->execute(); + + // test type `boolean` + $column = $db->getTableSchema($tableName)->getColumn('bool_col'); + $this->assertSame('boolean', $column->phpType); + + // test value `-1` + $db->createCommand()->insert($tableName, ['bool_col' => '-1'])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar(); + $this->assertEquals(1, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertTrue($phpTypeCast); + } + + public function testBooleanWithValueNull() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + ] + )->execute(); + + // test type `boolean` + $column = $db->getTableSchema($tableName)->getColumn('bool_col'); + $this->assertSame('boolean', $column->phpType); + + // test value `null` + $db->createCommand()->insert($tableName, ['bool_col' => null])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar(); + $this->assertNull($boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertNull($phpTypeCast); + } + + public function testBooleanWithValueOverflow() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + ] + )->execute(); + + // test type `boolean` + $column = $db->getTableSchema($tableName)->getColumn('bool_col'); + $this->assertSame('boolean', $column->phpType); + + // test value `2` + $db->createCommand()->insert($tableName, ['bool_col' => 2])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar(); + $this->assertEquals(1, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertTrue($phpTypeCast); + } + + public function testBooleanWithValueString() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + ] + )->execute(); + + // test type `boolean` + $column = $db->getTableSchema($tableName)->getColumn('bool_col'); + $this->assertSame('boolean', $column->phpType); + + // test value `0` + $db->createCommand()->insert($tableName, ['bool_col' => '0'])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar(); + $this->assertEquals(0, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertFalse($phpTypeCast); + + // test value `1` + $db->createCommand()->insert($tableName, ['bool_col' => '1'])->execute(); + $boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 2")->queryScalar(); + $this->assertEquals(1, $boolValue); + + // test php typecast + $phpTypeCast = $column->phpTypecast($boolValue); + $this->assertTrue($phpTypeCast); + } +} diff --git a/tests/framework/db/sqlite/type/BooleanTest.php b/tests/framework/db/sqlite/type/BooleanTest.php new file mode 100644 index 0000000000..c5bfd811c1 --- /dev/null +++ b/tests/framework/db/sqlite/type/BooleanTest.php @@ -0,0 +1,249 @@ +getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `false` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => false, 'bool_col_bit' => false])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + $this->assertEquals(0, $boolValues['bool_col_tinyint']); + $this->assertEquals(0, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertFalse($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertFalse($phpTypeCastBoolColBit); + + // test value `true` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => true, 'bool_col_bit' => true])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 2")->queryOne(); + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueInteger() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `0` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 0, 'bool_col_bit' => 0])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + $this->assertEquals(0, $boolValues['bool_col_tinyint']); + $this->assertEquals(0, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertFalse($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertFalse($phpTypeCastBoolColBit); + + // test value `1` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 1, 'bool_col_bit' => 1])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 2")->queryOne(); + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueNegative() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `-1` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => -1, 'bool_col_bit' => -1])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueNull() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `null` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => null, 'bool_col_bit' => null])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + + $this->assertNull($boolValues['bool_col_tinyint']); + $this->assertNull($boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertNull($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertNull($phpTypeCastBoolColBit); + } + + public function testBooleanWithValueOverflow() + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableName = '{{%boolean}}'; + + if ($db->getTableSchema($tableName)) { + $db->createCommand()->dropTable($tableName)->execute(); + } + + $db->createCommand()->createTable( + $tableName, + [ + 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), + 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), + 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), + ] + )->execute(); + + // test type `boolean` + $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); + $this->assertSame('boolean', $columnBoolColTinyint->phpType); + + $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); + $this->assertSame('boolean', $columnBoolColBit->phpType); + + // test value `2` + $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 2, 'bool_col_bit' => 2])->execute(); + $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); + + $this->assertEquals(1, $boolValues['bool_col_tinyint']); + $this->assertEquals(1, $boolValues['bool_col_bit']); + + // test php typecast + $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); + $this->assertTrue($phpTypeCastBoolColTinyint); + + $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); + $this->assertTrue($phpTypeCastBoolColBit); + } +} diff --git a/tests/framework/helpers/StringHelperTest.php b/tests/framework/helpers/StringHelperTest.php index 5e2d161dee..8241225a69 100644 --- a/tests/framework/helpers/StringHelperTest.php +++ b/tests/framework/helpers/StringHelperTest.php @@ -461,4 +461,66 @@ class StringHelperTest extends TestCase ['', ''], ]; } + + public function testMask() + { + // Standard masking + $this->assertSame('12******90', StringHelper::mask('1234567890', 2, 6)); + $this->assertSame('a********j', StringHelper::mask('abcdefghij', 1, 8)); + $this->assertSame('*************', StringHelper::mask('Hello, World!', 0, 13)); + $this->assertSame('************!', StringHelper::mask('Hello, World!', 0, 12)); + $this->assertSame('Hello, *orld!', StringHelper::mask('Hello, World!', 7, 1)); + $this->assertSame('Saleh Hashemi', StringHelper::mask('Saleh Hashemi', 0, 0)); + + // Different Mask Character + $this->assertSame('12######90', StringHelper::mask('1234567890', 2, 6, '#')); + + // Positions outside the string + $this->assertSame('1234567890', StringHelper::mask('1234567890', 20, 6)); + $this->assertSame('1234567890', StringHelper::mask('1234567890', -20, 6)); + + // Negative values for start + $this->assertSame('1234****90', StringHelper::mask('1234567890', -6, 4)); + + // type-related edge case + $this->assertSame('1234****90', StringHelper::mask(1234567890, -6, 4)); + + // Multibyte characters + $this->assertSame('你**', StringHelper::mask('你好吗', 1, 2)); + $this->assertSame('你好吗', StringHelper::mask('你好吗', 4, 2)); + + // Special characters + $this->assertSame('em**l@email.com', StringHelper::mask('email@email.com', 2, 2)); + $this->assertSame('******email.com', StringHelper::mask('email@email.com', 0, 6)); + } + + /** + * @param string $string + * @param string $start + * @param string $end + * @param string $expectedResult + * @dataProvider dataProviderFindBetween + */ + public function testFindBetween($string, $start, $end, $expectedResult) + { + $this->assertSame($expectedResult, StringHelper::findBetween($string, $start, $end)); + } + + public function dataProviderFindBetween() + { + return [ + ['hello world hello', ' hello', ' world', null], // end before start + ['This is a sample string', ' is ', ' string', 'a sample'], // normal case + ['startendstart', 'start', 'end', ''], // end before start + ['startmiddleend', 'start', 'end', 'middle'], // normal case + ['startend', 'start', 'end', ''], // end immediately follows start + ['multiple start start end end', 'start ', ' end', 'start end'], // multiple starts and ends + ['', 'start', 'end', null], // empty string + ['no delimiters here', 'start', 'end', null], // no start and end + ['start only', 'start', 'end', null], // start found but no end + ['end only', 'start', 'end', null], // end found but no start + ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters + ['من صالح هاشمی هستم', 'من ', ' هستم', 'صالح هاشمی'], // other languages + ]; + } } diff --git a/tests/framework/web/ErrorHandlerTest.php b/tests/framework/web/ErrorHandlerTest.php index 7eddb3f89b..662174c970 100644 --- a/tests/framework/web/ErrorHandlerTest.php +++ b/tests/framework/web/ErrorHandlerTest.php @@ -42,7 +42,50 @@ Message: This message is displayed to end user Exception: yii\web\NotFoundHttpException', $out); } - public function testClearAssetFilesInErrorView(): void + public function testFormatRaw() + { + Yii::$app->response->format = yii\web\Response::FORMAT_RAW; + + /** @var ErrorHandler $handler */ + $handler = Yii::$app->getErrorHandler(); + + ob_start(); // suppress response output + $this->invokeMethod($handler, 'renderException', [new \Exception('Test Exception')]); + $out = ob_get_clean(); + + $this->assertcontains('Test Exception', $out); + + $this->assertTrue(is_string(Yii::$app->response->data)); + $this->assertcontains("Exception 'Exception' with message 'Test Exception'", Yii::$app->response->data); + } + + public function testFormatXml() + { + Yii::$app->response->format = yii\web\Response::FORMAT_XML; + + /** @var ErrorHandler $handler */ + $handler = Yii::$app->getErrorHandler(); + + ob_start(); // suppress response output + $this->invokeMethod($handler, 'renderException', [new \Exception('Test Exception')]); + $out = ob_get_clean(); + + $this->assertcontains('Test Exception', $out); + + $outArray = Yii::$app->response->data; + + $this->assertTrue(is_array(Yii::$app->response->data)); + + $this->assertEquals('Exception', $outArray['name']); + $this->assertEquals('Test Exception', $outArray['message']); + $this->assertArrayHasKey('code', $outArray); + $this->assertEquals('Exception', $outArray['type']); + $this->assertContains('ErrorHandlerTest.php', $outArray['file']); + $this->assertArrayHasKey('stack-trace', $outArray); + $this->assertArrayHasKey('line', $outArray); + } + + public function testClearAssetFilesInErrorView() { Yii::$app->getView()->registerJsFile('somefile.js'); /** @var ErrorHandler $handler */