Skip to content

Commit 045996e

Browse files
committed
Merge branch 'master' into fix-type-boolean-mysql
2 parents 18ed620 + 9d3c71d commit 045996e

File tree

5 files changed

+296
-9
lines changed

5 files changed

+296
-9
lines changed

framework/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ Yii Framework 2 Change Log
33

44
2.0.50 under development
55
------------------------
6-
6+
- Bug #20040: Fix type `boolean` in `MSSQL` (terabytesoftw)
77
- Bug #20005: Fix `yii\console\controllers\ServeController` to specify the router script (terabytesoftw)
88
- Bug #19060: Fix `yii\widgets\Menu` bug when using Closure for active item and adding additional tests in `tests\framework\widgets\MenuTest` (atrandafir)
99
- Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher)
1010
- 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)
1111
- Bug #20002: Fixed superfluous query on HEAD request in serializer (xicond)
1212
- Enh #12743: Added new methods `BaseActiveRecord::loadRelations()` and `BaseActiveRecord::loadRelationsFor()` to eager load related models for existing primary model instances (PowerGamer1)
1313
- Enh #20030: Improve performance of handling `ErrorHandler::$memoryReserveSize` (antonshevelev, rob006)
14+
- Enh #20032: Added `mask` method for string masking with multibyte support (salehhashemi1992)
15+
1416

1517
2.0.49.2 October 12, 2023
1618
-------------------------

framework/db/mssql/Schema.php

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ protected function resolveTableNames($table, $name)
375375
*/
376376
protected function loadColumnSchema($info)
377377
{
378+
$isVersion2017orLater = version_compare($this->db->getSchema()->getServerVersion(), '14', '>=');
378379
$column = $this->createColumnSchema();
379380

380381
$column->name = $info['column_name'];
@@ -393,20 +394,21 @@ protected function loadColumnSchema($info)
393394
if (isset($this->typeMap[$type])) {
394395
$column->type = $this->typeMap[$type];
395396
}
397+
398+
if ($isVersion2017orLater && $type === 'bit') {
399+
$column->type = 'boolean';
400+
}
401+
396402
if (!empty($matches[2])) {
397403
$values = explode(',', $matches[2]);
398404
$column->size = $column->precision = (int) $values[0];
405+
399406
if (isset($values[1])) {
400407
$column->scale = (int) $values[1];
401408
}
402-
if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
403-
$column->type = 'boolean';
404-
} elseif ($type === 'bit') {
405-
if ($column->size > 32) {
406-
$column->type = 'bigint';
407-
} elseif ($column->size === 32) {
408-
$column->type = 'integer';
409-
}
409+
410+
if ($isVersion2017orLater === false) {
411+
$column->type = $this->booleanTypeLegacy($column->size, $type);
410412
}
411413
}
412414
}
@@ -813,4 +815,27 @@ public function createColumnSchemaBuilder($type, $length = null)
813815
{
814816
return Yii::createObject(ColumnSchemaBuilder::className(), [$type, $length, $this->db]);
815817
}
818+
819+
/**
820+
* Assigns a type boolean for the column type bit, for legacy versions of MSSQL.
821+
*
822+
* @param int $size column size.
823+
* @param string $type column type.
824+
*
825+
* @return string column type.
826+
*/
827+
private function booleanTypeLegacy($size, $type)
828+
{
829+
if ($size === 1 && ($type === 'tinyint' || $type === 'bit')) {
830+
return 'boolean';
831+
} elseif ($type === 'bit') {
832+
if ($size > 32) {
833+
return 'bigint';
834+
} elseif ($size === 32) {
835+
return 'integer';
836+
}
837+
}
838+
839+
return $type;
840+
}
816841
}

framework/helpers/BaseStringHelper.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,4 +497,34 @@ public static function mb_ucwords($string, $encoding = 'UTF-8')
497497

498498
return implode('', $parts);
499499
}
500+
501+
/**
502+
* Masks a portion of a string with a repeated character.
503+
* This method is multibyte-safe.
504+
*
505+
* @param string $string The input string.
506+
* @param int $start The starting position from where to begin masking.
507+
* This can be a positive or negative integer.
508+
* Positive values count from the beginning,
509+
* negative values count from the end of the string.
510+
* @param int $length The length of the section to be masked.
511+
* The masking will start from the $start position
512+
* and continue for $length characters.
513+
* @param string $mask The character to use for masking. The default is '*'.
514+
* @return string The masked string.
515+
*/
516+
public static function mask($string, $start, $length, $mask = '*') {
517+
$strLength = mb_strlen($string, 'UTF-8');
518+
519+
// Return original string if start position is out of bounds
520+
if ($start >= $strLength || $start < -$strLength) {
521+
return $string;
522+
}
523+
524+
$masked = mb_substr($string, 0, $start, 'UTF-8');
525+
$masked .= str_repeat($mask, abs($length));
526+
$masked .= mb_substr($string, $start + abs($length), null, 'UTF-8');
527+
528+
return $masked;
529+
}
500530
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
/**
3+
* @link https://www.yiiframework.com/
4+
* @copyright Copyright (c) 2008 Yii Software LLC
5+
* @license https://www.yiiframework.com/license/
6+
*/
7+
8+
namespace yiiunit\framework\db\mssql\type;
9+
10+
use yii\db\mssql\Schema;
11+
use yiiunit\framework\db\DatabaseTestCase;
12+
13+
/**
14+
* @group db
15+
* @group mssql
16+
*/
17+
class BooleanTest extends DatabaseTestCase
18+
{
19+
protected $driverName = 'sqlsrv';
20+
21+
public function testBoolean()
22+
{
23+
$db = $this->getConnection(true);
24+
$schema = $db->getSchema();
25+
$tableName = '{{%boolean}}';
26+
27+
if ($db->getTableSchema($tableName)) {
28+
$db->createCommand()->dropTable($tableName)->execute();
29+
}
30+
31+
$db->createCommand()->createTable(
32+
$tableName,
33+
[
34+
'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK),
35+
'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN),
36+
]
37+
)->execute();
38+
39+
// test type
40+
$column = $db->getTableSchema($tableName)->getColumn('bool_col');
41+
$this->assertSame('boolean', $column->phpType);
42+
43+
// test value `false`
44+
$db->createCommand()->insert($tableName, ['bool_col' => false])->execute();
45+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar();
46+
$this->assertEquals(0, $boolValue);
47+
48+
// test php typecast
49+
$phpTypeCast = $column->phpTypecast($boolValue);
50+
$this->assertFalse($phpTypeCast);
51+
52+
// test value `true`
53+
$db->createCommand()->insert($tableName, ['bool_col' => true])->execute();
54+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 2")->queryScalar();
55+
$this->assertEquals(1, $boolValue);
56+
57+
// test php typecast
58+
$phpTypeCast = $column->phpTypecast($boolValue);
59+
$this->assertTrue($phpTypeCast);
60+
}
61+
62+
public function testBooleanWithValueInteger()
63+
{
64+
$db = $this->getConnection(true);
65+
$schema = $db->getSchema();
66+
$tableName = '{{%boolean}}';
67+
68+
if ($db->getTableSchema($tableName)) {
69+
$db->createCommand()->dropTable($tableName)->execute();
70+
}
71+
72+
$db->createCommand()->createTable(
73+
$tableName,
74+
[
75+
'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK),
76+
'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN),
77+
]
78+
)->execute();
79+
80+
// test type
81+
$column = $db->getTableSchema($tableName)->getColumn('bool_col');
82+
$this->assertSame('boolean', $column->phpType);
83+
84+
// test value 0
85+
$db->createCommand()->insert($tableName, ['bool_col' => 0])->execute();
86+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar();
87+
$this->assertEquals(0, $boolValue);
88+
89+
// test php typecast
90+
$phpTypeCast = $column->phpTypecast($boolValue);
91+
$this->assertFalse($phpTypeCast);
92+
93+
// test value 1
94+
$db->createCommand()->insert($tableName, ['bool_col' => 1])->execute();
95+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 2")->queryScalar();
96+
$this->assertEquals(1, $boolValue);
97+
98+
// test php typecast
99+
$phpTypeCast = $column->phpTypecast($boolValue);
100+
$this->assertTrue($phpTypeCast);
101+
}
102+
103+
public function testBooleanValueNegative()
104+
{
105+
$db = $this->getConnection(true);
106+
$schema = $db->getSchema();
107+
$tableName = '{{%boolean}}';
108+
109+
if ($db->getTableSchema($tableName)) {
110+
$db->createCommand()->dropTable($tableName)->execute();
111+
}
112+
113+
$db->createCommand()->createTable(
114+
$tableName,
115+
[
116+
'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK),
117+
'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN),
118+
]
119+
)->execute();
120+
121+
// test type
122+
$column = $db->getTableSchema($tableName)->getColumn('bool_col');
123+
$this->assertSame('boolean', $column->phpType);
124+
125+
// test value 2
126+
$db->createCommand()->insert($tableName, ['bool_col' => -1])->execute();
127+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar();
128+
$this->assertEquals(1, $boolValue);
129+
130+
// test php typecast
131+
$phpTypeCast = $column->phpTypecast($boolValue);
132+
$this->assertTrue($phpTypeCast);
133+
}
134+
135+
public function testBooleanWithValueNull()
136+
{
137+
$db = $this->getConnection(true);
138+
$schema = $db->getSchema();
139+
$tableName = '{{%boolean}}';
140+
141+
if ($db->getTableSchema($tableName)) {
142+
$db->createCommand()->dropTable($tableName)->execute();
143+
}
144+
145+
$db->createCommand()->createTable(
146+
$tableName,
147+
[
148+
'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK),
149+
'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN),
150+
]
151+
)->execute();
152+
153+
// test type
154+
$column = $db->getTableSchema($tableName)->getColumn('bool_col');
155+
$this->assertSame('boolean', $column->phpType);
156+
157+
// test value `null`
158+
$db->createCommand()->insert($tableName, ['bool_col' => null])->execute();
159+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar();
160+
$this->assertNull($boolValue);
161+
162+
// test php typecast
163+
$phpTypeCast = $column->phpTypecast($boolValue);
164+
$this->assertNull($phpTypeCast);
165+
}
166+
167+
public function testBooleanWithValueOverflow()
168+
{
169+
$db = $this->getConnection(true);
170+
$schema = $db->getSchema();
171+
$tableName = '{{%boolean}}';
172+
173+
if ($db->getTableSchema($tableName)) {
174+
$db->createCommand()->dropTable($tableName)->execute();
175+
}
176+
177+
$db->createCommand()->createTable(
178+
$tableName,
179+
[
180+
'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK),
181+
'bool_col' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN),
182+
]
183+
)->execute();
184+
185+
// test type
186+
$column = $db->getTableSchema($tableName)->getColumn('bool_col');
187+
$this->assertSame('boolean', $column->phpType);
188+
189+
// test value 2
190+
$db->createCommand()->insert($tableName, ['bool_col' => 2])->execute();
191+
$boolValue = $db->createCommand("SELECT bool_col FROM $tableName WHERE id = 1")->queryScalar();
192+
$this->assertEquals(1, $boolValue);
193+
194+
// test php typecast
195+
$phpTypeCast = $column->phpTypecast($boolValue);
196+
$this->assertTrue($phpTypeCast);
197+
}
198+
}

tests/framework/helpers/StringHelperTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,36 @@ public function dataProviderDirname()
474474
['', ''],
475475
];
476476
}
477+
478+
public function testMask()
479+
{
480+
// Standard masking
481+
$this->assertSame('12******90', StringHelper::mask('1234567890', 2, 6));
482+
$this->assertSame('a********j', StringHelper::mask('abcdefghij', 1, 8));
483+
$this->assertSame('*************', StringHelper::mask('Hello, World!', 0, 13));
484+
$this->assertSame('************!', StringHelper::mask('Hello, World!', 0, 12));
485+
$this->assertSame('Hello, *orld!', StringHelper::mask('Hello, World!', 7, 1));
486+
$this->assertSame('Saleh Hashemi', StringHelper::mask('Saleh Hashemi', 0, 0));
487+
488+
// Different Mask Character
489+
$this->assertSame('12######90', StringHelper::mask('1234567890', 2, 6, '#'));
490+
491+
// Positions outside the string
492+
$this->assertSame('1234567890', StringHelper::mask('1234567890', 20, 6));
493+
$this->assertSame('1234567890', StringHelper::mask('1234567890', -20, 6));
494+
495+
// Negative values for start
496+
$this->assertSame('1234****90', StringHelper::mask('1234567890', -6, 4));
497+
498+
// type-related edge case
499+
$this->assertSame('1234****90', StringHelper::mask(1234567890, -6, 4));
500+
501+
// Multibyte characters
502+
$this->assertSame('你**', StringHelper::mask('你好吗', 1, 2));
503+
$this->assertSame('你好吗', StringHelper::mask('你好吗', 4, 2));
504+
505+
// Special characters
506+
$this->assertSame('em**[email protected]', StringHelper::mask('[email protected]', 2, 2));
507+
$this->assertSame('******email.com', StringHelper::mask('[email protected]', 0, 6));
508+
}
477509
}

0 commit comments

Comments
 (0)