Repository: laravel/framework (php)

Issue:
New command php artisan db:show returns exception on laravel version 9.24 with mysql 5.7 database
<!-- DO NOT THROW THIS AWAY -->
<!-- Fill out the FULL versions with patch versions -->

- Laravel Version: 9.24.0
- PHP Version: 8.1.9
- Database Driver & Version: Mysql 5.7.38

### Description:
php artisan db:show returns "Unknown database type enum requested, Doctrine\DBAL\Platforms\MySQL57Platform may not support it." on mysql 5.7

``` 
   Doctrine\DBAL\Exception 

  Unknown database type enum requested, Doctrine\DBAL\Platforms\MySQL57Platform may not support it.

  at vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php:418
    414▕ 
    415▕         $dbType = strtolower($dbType);
    416▕ 
    417▕         if (! isset($this->doctrineTypeMapping[$dbType])) {
  ➜ 418▕             throw new Exception(
    419▕                 'Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.'
    420▕             );
    421▕         }
    422▕ 

      +19 vendor frames 
  20  artisan:37
      Illuminate\Foundation\Console\Kernel::handle()

```

### Steps To Reproduce:
Run command `php artisan db:show` on laravel version 9.24 with mysql 5.7 database
<!-- If possible, please provide a GitHub repository to demonstrate your issue -->
<!-- laravel new bug-report --github="--public" -->


Pull request description:
[9.x] Prevent error in db/model commands when using unsupported columns
The PR resolves #43630 #43622 

The `doctrine/dbal` package does not support enum types when listing database columns. This PR adds support for mapping `enum` and `sysname` (issue with some distributions of sqlsrv) to `string`. The Artisan `db:show`, `db:table` and `model:show` commands have all been updated to use it.

Additonally, `db:show` uses the `rescue` helper to prevent an error if the engine type is not available. This PR also prevents the error from being reported. 


Patch:
diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml
index f23b92ea45..4527206863 100644
--- a/.github/workflows/databases.yml
+++ b/.github/workflows/databases.yml
@@ -4,7 +4,7 @@ on: [push, pull_request]
 
 jobs:
   mysql_57:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     services:
       mysql:
@@ -47,7 +47,7 @@ jobs:
           DB_USERNAME: root
 
   mysql_8:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     services:
       mysql:
@@ -90,7 +90,7 @@ jobs:
           DB_USERNAME: root
 
   mariadb:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     services:
       mysql:
@@ -133,7 +133,7 @@ jobs:
           DB_USERNAME: root
 
   pgsql:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     services:
       postgresql:
@@ -201,7 +201,7 @@ jobs:
         uses: shivammathur/setup-php@v2
         with:
           php-version: 8.1
-          extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv
+          extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc
           tools: composer:v2
           coverage: none
 
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index d7ff60d753..065fa45ac8 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -8,7 +8,7 @@ on:
 
 jobs:
   src:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     strategy:
       fail-fast: true
diff --git a/src/Illuminate/Console/Concerns/InteractsWithIO.php b/src/Illuminate/Console/Concerns/InteractsWithIO.php
index 6a7be40c54..0e6a16872e 100644
--- a/src/Illuminate/Console/Concerns/InteractsWithIO.php
+++ b/src/Illuminate/Console/Concerns/InteractsWithIO.php
@@ -207,7 +207,7 @@ public function secret($question, $fallback = true)
      *
      * @param  string  $question
      * @param  array  $choices
-     * @param  string|null  $default
+     * @param  string|int|null  $default
      * @param  mixed|null  $attempts
      * @param  bool  $multiple
      * @return string|array
diff --git a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php
index 1125d03841..3618e25643 100644
--- a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php
+++ b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php
@@ -8,6 +8,7 @@
 use Illuminate\Database\MySqlConnection;
 use Illuminate\Database\PostgresConnection;
 use Illuminate\Database\SQLiteConnection;
+use Illuminate\Database\SqlServerConnection;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Composer;
 use Symfony\Component\Process\Exception\ProcessSignaledException;
@@ -16,6 +17,25 @@
 
 abstract class DatabaseInspectionCommand extends Command
 {
+    /**
+     * A map of database column types.
+     *
+     * @var array
+     */
+    protected $typeMappings = [
+        'bit' => 'string',
+        'enum' => 'string',
+        'geometry' => 'string',
+        'geomcollection' => 'string',
+        'linestring' => 'string',
+        'multilinestring' => 'string',
+        'multipoint' => 'string',
+        'multipolygon' => 'string',
+        'point' => 'string',
+        'polygon' => 'string',
+        'sysname' => 'string',
+    ];
+
     /**
      * The Composer instance.
      *
@@ -83,10 +103,12 @@ protected function getTableSize(ConnectionInterface $connection, string $table)
      */
     protected function getMySQLTableSize(ConnectionInterface $connection, string $table)
     {
-        return $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [
+        $result = $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [
             $connection->getDatabaseName(),
             $table,
-        ])->size;
+        ]);
+
+        return Arr::wrap((array) $result)['size'];
     }
 
     /**
@@ -98,9 +120,11 @@ protected function getMySQLTableSize(ConnectionInterface $connection, string $ta
      */
     protected function getPostgresTableSize(ConnectionInterface $connection, string $table)
     {
-        return $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [
+        $result = $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [
             $table,
-        ])->size;
+        ]);
+
+        return Arr::wrap((array) $result)['size'];
     }
 
     /**
@@ -112,25 +136,33 @@ protected function getPostgresTableSize(ConnectionInterface $connection, string
      */
     protected function getSqliteTableSize(ConnectionInterface $connection, string $table)
     {
-        return $connection->selectOne('SELECT SUM(pgsize) FROM dbstat WHERE name=?', [
+        $result = $connection->selectOne('SELECT SUM(pgsize) FROM dbstat WHERE name=?', [
             $table,
-        ])->size;
+        ]);
+
+        return Arr::wrap((array) $result)['size'];
     }
 
     /**
      * Get the number of open connections for a database.
      *
      * @param  \Illuminate\Database\ConnectionInterface  $connection
-     * @return null
+     * @return int|null
      */
     protected function getConnectionCount(ConnectionInterface $connection)
     {
-        return match (class_basename($connection)) {
-            'MySqlConnection' => (int) $connection->selectOne($connection->raw('show status where variable_name = "threads_connected"'))->Value,
-            'PostgresConnection' => (int) $connection->selectOne('select count(*) as connections from pg_stat_activity')->connections,
-            'SqlServerConnection' => (int) $connection->selectOne('SELECT COUNT(*) connections FROM sys.dm_exec_sessions WHERE status = ?', ['running'])->connections,
+        $result = match (true) {
+            $connection instanceof MySqlConnection => $connection->selectOne('show status where variable_name = "threads_connected"'),
+            $connection instanceof PostgresConnection => $connection->selectOne('select count(*) AS "Value" from pg_stat_activity'),
+            $connection instanceof SqlServerConnection => $connection->selectOne('SELECT COUNT(*) Value FROM sys.dm_exec_sessions WHERE status = ?', ['running']),
             default => null,
         };
+
+        if (! $result) {
+            return null;
+        }
+
+        return Arr::wrap((array) $result)['Value'];
     }
 
     /**
@@ -193,4 +225,17 @@ protected function installDependencies()
             }
         }
     }
+
+    /**
+     * Register custom Doctrine type mappings.
+     *
+     * @param  \Doctrine\DBAL\Platforms\AbstractPlatform  $platform
+     * @return void
+     */
+    protected function registerTypeMapping(AbstractPlatform $platform)
+    {
+        foreach ($this->typeMappings as $type => $value) {
+            $platform->registerDoctrineTypeMapping($type, $value);
+        }
+    }
 }
diff --git a/src/Illuminate/Database/Console/ShowCommand.php b/src/Illuminate/Database/Console/ShowCommand.php
index 125de1cac5..6a4c300db4 100644
--- a/src/Illuminate/Database/Console/ShowCommand.php
+++ b/src/Illuminate/Database/Console/ShowCommand.php
@@ -44,6 +44,8 @@ public function handle(ConnectionResolverInterface $connections)
 
         $schema = $connection->getDoctrineSchemaManager();
 
+        $this->registerTypeMapping($schema->getDatabasePlatform());
+
         $data = [
             'platform' => [
                 'config' => $this->getConfigFromDatabase($database),
@@ -75,7 +77,7 @@ protected function tables(ConnectionInterface $connection, AbstractSchemaManager
             'table' => $table->getName(),
             'size' => $this->getTableSize($connection, $table->getName()),
             'rows' => $this->option('counts') ? $connection->table($table->getName())->count() : null,
-            'engine' => rescue(fn () => $table->getOption('engine')),
+            'engine' => rescue(fn () => $table->getOption('engine'), null, false),
             'comment' => $table->getComment(),
         ]);
     }
diff --git a/src/Illuminate/Database/Console/TableCommand.php b/src/Illuminate/Database/Console/TableCommand.php
index 7cc99162f7..f221e4727b 100644
--- a/src/Illuminate/Database/Console/TableCommand.php
+++ b/src/Illuminate/Database/Console/TableCommand.php
@@ -42,6 +42,7 @@ public function handle(ConnectionResolverInterface $connections)
         $connection = $connections->connection($this->input->getOption('database'));
 
         $schema = $connection->getDoctrineSchemaManager();
+        $this->registerTypeMapping($schema->getDatabasePlatform());
 
         $table = $this->argument('table') ?: $this->components->choice(
             'Which table would you like to inspect?',
diff --git a/src/Illuminate/Foundation/Console/ShowModelCommand.php b/src/Illuminate/Foundation/Console/ShowModelCommand.php
index 5a01b574cd..c5bca312f9 100644
--- a/src/Illuminate/Foundation/Console/ShowModelCommand.php
+++ b/src/Illuminate/Foundation/Console/ShowModelCommand.php
@@ -113,6 +113,7 @@ public function handle()
     protected function getAttributes($model)
     {
         $schema = $model->getConnection()->getDoctrineSchemaManager();
+        $this->registerTypeMapping($schema->getDatabasePlatform());
         $table = $model->getConnection()->getTablePrefix().$model->getTable();
         $columns = $schema->listTableColumns($table);
         $indexes = $schema->listTableIndexes($table);
diff --git a/src/Illuminate/Redis/RedisManager.php b/src/Illuminate/Redis/RedisManager.php
index d7c976210f..e869741856 100644
--- a/src/Illuminate/Redis/RedisManager.php
+++ b/src/Illuminate/Redis/RedisManager.php
@@ -158,7 +158,7 @@ protected function configure(Connection $connection, $name)
     /**
      * Get the connector instance for the current driver.
      *
-     * @return \Illuminate\Contracts\Redis\Connector
+     * @return \Illuminate\Contracts\Redis\Connector|null
      */
     protected function connector()
     {
diff --git a/src/Illuminate/Support/Facades/Notification.php b/src/Illuminate/Support/Facades/Notification.php
index 68f7201d27..658e25fc16 100644
--- a/src/Illuminate/Support/Facades/Notification.php
+++ b/src/Illuminate/Support/Facades/Notification.php
@@ -11,6 +11,7 @@
  * @method static \Illuminate\Support\Collection sent(mixed $notifiable, string $notification, callable $callback = null)
  * @method static bool hasSent(mixed $notifiable, string $notification)
  * @method static mixed channel(string|null $name = null)
+ * @method static void assertCount(int $expectedCount)
  * @method static void assertNotSentTo(mixed $notifiable, string|\Closure $notification, callable $callback = null)
  * @method static void assertNothingSent()
  * @method static void assertNothingSentTo(mixed $notifiable)


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 175b476b6b..d18c9378ff 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,7 +8,7 @@ on:
 
 jobs:
   linux_tests:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     services:
       memcached: