yiisoft / yii

Yii PHP Framework 1.1.x
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
4.84k stars 2.28k forks source link

oci8 schema adapter returns null for 'KEY' field in column info which causes strpos deprecation error #4499

Closed johnrembo closed 1 year ago

johnrembo commented 1 year ago

What steps will reproduce the problem?

Create app with oci8 AR

$app = Yii::createWebApplication('protected/config/main.php');
...
Pack::Model()->findByPk(...)
...

Config is:

return array(
...
    'components'=>array(
        'db'=>array(
            'class'=>'ext.oci8Pdo.OciDbConnection',
            'connectionString' => 'oci:dbname=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST='.ORACLE_HOST.')
                (PORT='.ORACLE_PORT.'))(CONNECT_DATA=(SERVICE_NAME='.ORACLE_SID.')));charset='.ORACLE_CP.';',
            'username'=> ORACLE_USER,
            'password'=> ORACLE_PASS
    ),
...

What is the expected result?

Create an AR model

What do you get instead?

PHP error strpos(): Passing null to parameter #1 ($haystack) of type string is deprecated

../yii/framework/db/schema/oci/COciSchema.php(234)

Additional info

Q A
Yii version 1.1.27
PHP version 8.1.2
Operating system Linux 5.15.0-53-generic #59-Ubuntu SMP
marcovtwout commented 1 year ago

@johnrembo Could you post the full stack trace and also a simple example table that can be used to reproduce this issue?

johnrembo commented 1 year ago

Yeah why not.

Given Test.php

<?php
class Test extends CActiveRecord {

    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }

    public function tableName()
    {
        return 'SCOTT.TEST';
    }

    public function primaryKey(){
        return array('ID');
    }

}

and test.php

<?php
define('YII_DEBUG', true);
require 'vendor/autoload.php';
$config = [
    'components'=>array(
        'db'=>array(
            'class'=>'ext.oci8Pdo.OciDbConnection',
            'connectionString' => 'oci:dbname=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)
                (PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)));charset=UTF-8;',
            'username'=> 'scott',
            'password'=> 'tiger',
        ),
    )
];
$app = Yii::createWebApplication($config);
echo 'Connection string: '; echo Yii::app()->db->connectionString; echo '<br />'.PHP_EOL;

$command = Yii::app()->db->createCommand("create table test (id number, col varchar2(1), constraint id_pk primary key (id))");
$command->execute();
$command = Yii::app()->db->createCommand("insert into test values (1, '1')");
$command->execute();
var_dump(Test::Model()->findByPk(1));

I get

PHP error
strpos(): Passing null to parameter #1 ($haystack) of type string is deprecated

/home/user/src/yii/framework/db/schema/oci/COciSchema.php(234)

222 
223     /**
224      * Creates a table column.
225      * @param array $column column metadata
226      * @return CDbColumnSchema normalized column metadata
227      */
228     protected function createColumn($column)
229     {
230         $c=new COciColumnSchema;
231         $c->name=$column['COLUMN_NAME'];
232         $c->rawName=$this->quoteColumnName($c->name);
233         $c->allowNull=$column['NULLABLE']==='Y';
234         $c->isPrimaryKey=strpos($column['KEY'],'P')!==false;
235         $c->isForeignKey=false;
236         $c->init($column['DATA_TYPE'],$column['DATA_DEFAULT']);
237         $c->comment=$column['COLUMN_COMMENT']===null ? '' : $column['COLUMN_COMMENT'];
238 
239         return $c;
240     }
241 
242     /**
243      * Collects the primary and foreign key column details for the given table.
244      * @param COciTableSchema $table the table metadata
245      */
246     protected function findConstraints($table)
Stack Trace
#0  
–  /home/user/src/yii/framework/db/schema/oci/COciSchema.php(234): strpos(null, "P")
229     {
230         $c=new COciColumnSchema;
231         $c->name=$column['COLUMN_NAME'];
232         $c->rawName=$this->quoteColumnName($c->name);
233         $c->allowNull=$column['NULLABLE']==='Y';
234         $c->isPrimaryKey=strpos($column['KEY'],'P')!==false;
235         $c->isForeignKey=false;
236         $c->init($column['DATA_TYPE'],$column['DATA_DEFAULT']);
237         $c->comment=$column['COLUMN_COMMENT']===null ? '' : $column['COLUMN_COMMENT'];
238 
239         return $c;
#1  
–  /home/user/src/yii/framework/db/schema/oci/COciSchema.php(205): COciSchema->createColumn(array("COLUMN_NAME" => "COL", "DATA_TYPE" => "VARCHAR2(1)", "NULLABLE" => "Y", "DATA_DEFAULT" => null, ...))
200             return false;
201         }
202 
203         foreach($columns as $column)
204         {
205             $c=$this->createColumn($column);
206 
207             $table->columns[$c->name]=$c;
208             if($c->isPrimaryKey)
209             {
210                 if($table->primaryKey===null)
#2  
–  /home/user/src/yii/framework/db/schema/oci/COciSchema.php(123): COciSchema->findColumns(COciTableSchema)
118     protected function loadTable($name)
119     {
120         $table=new COciTableSchema;
121         $this->resolveTableNames($table,$name);
122 
123         if(!$this->findColumns($table))
124             return null;
125         $this->findConstraints($table);
126 
127         return $table;
128     }
#3  
–  /home/user/src/yii/framework/db/schema/CDbSchema.php(103): COciSchema->loadTable("SCOTT.TEST")
098                         $cache->set($key,$table,$duration);
099                 }
100                 $this->_tables[$name]=$table;
101             }
102             else
103                 $this->_tables[$name]=$table=$this->loadTable($realName);
104 
105             if(isset($qcDuration))  // re-enable query caching
106                 $this->_connection->queryCachingDuration=$qcDuration;
107 
108             return $table;
#4  
–  /home/user/src/yii/framework/db/ar/CActiveRecord.php(2390): CDbSchema->getTable("SCOTT.TEST")
2385     public function __construct($model)
2386     {
2387         $this->_modelClassName=get_class($model);
2388 
2389         $tableName=$model->tableName();
2390         if(($table=$model->getDbConnection()->getSchema()->getTable($tableName))===null)
2391             throw new CDbException(Yii::t('yii','The table "{table}" for active record class "{class}" cannot be found in the database.',
2392                 array('{class}'=>$this->_modelClassName,'{table}'=>$tableName)));
2393                 
2394         if(($modelPk=$model->primaryKey())!==null || $table->primaryKey===null)
2395         {
#5  
–  /home/user/src/yii/framework/db/ar/CActiveRecord.php(413): CActiveRecordMetaData->__construct(Test)
408     {
409         $className=get_class($this);
410         if(!array_key_exists($className,self::$_md))
411         {
412             self::$_md[$className]=null; // preventing recursive invokes of {@link getMetaData()} via {@link __get()}
413             self::$_md[$className]=new CActiveRecordMetaData($this);
414         }
415         return self::$_md[$className];
416     }
417 
418     /**
#6  
–  /home/user/src/yii/framework/db/ar/CActiveRecord.php(663): CActiveRecord->getMetaData()
658      * Returns the metadata of the table that this AR belongs to
659      * @return CDbTableSchema the metadata of the table that this AR belongs to
660      */
661     public function getTableSchema()
662     {
663         return $this->getMetaData()->tableSchema;
664     }
665 
666     /**
667      * Returns the command builder used by this AR.
668      * @return CDbCommandBuilder the command builder used by this AR
#7  
–  /home/user/src/yii/framework/db/ar/CActiveRecord.php(1493): CActiveRecord->getTableSchema()
1488      */
1489     public function findByPk($pk,$condition='',$params=array())
1490     {
1491         Yii::trace(get_class($this).'.findByPk()','system.db.ar.CActiveRecord');
1492         $prefix=$this->getTableAlias(true).'.';
1493         $criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params,$prefix);
1494         return $this->query($criteria);
1495     }
1496 
1497     /**
1498      * Finds all active records with the specified primary keys.
#8  
–  /home/user/work/arrest/front/test.php(22): CActiveRecord->findByPk(1)
17 
18 $command = Yii::app()->db->createCommand("create table test (id number, col varchar2(1), constraint id_pk primary key (id))");
19 $command->execute();
20 $command = Yii::app()->db->createCommand("insert into test values (1, '1')");
21 $command->execute();
22 var_dump(Test::Model()->findByPk(1));
2022-12-08 14:54:03 Apache/2.4.52 (Ubuntu) Yii Framework/1.1.28-dev

Actually I ve already fixed it in a fork.

And slightly optimized the query. But now struggling to run unit test on phpunit v9.5.10:

phpunit framework                                                                      ↑
PHP Fatal error:  Declaration of CComponentTest::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): vo
id in /home/user/src/yii/tests/framework/base/CComponentTest.php on line 23
PHP Stack trace:
PHP   1. {main}() /usr/bin/phpunit:0
PHP   2. PHPUnit\TextUI\Command::main($exit = *uninitialized*) /usr/bin/phpunit:73
PHP   3. PHPUnit\TextUI\Command->run($argv = [0 => '/usr/bin/phpunit', 1 => 'framework'], $exit = TRUE) /usr/share/php/P
HPUnit/TextUI/Command.php:96
PHP   4. PHPUnit\Runner\BaseTestRunner->getTest($suiteClassFile = '/home/user/src/yii/tests/framework', $suffixes
= [0 => 'Test.php', 1 => '.phpt']) /usr/share/php/PHPUnit/TextUI/Command.php:120
PHP   5. PHPUnit\Framework\TestSuite->addTestFiles($fileNames = [0 => 

Apparently this also needs a little php compatibility updates

marcovtwout commented 1 year ago

@johnrembo Could you test if https://github.com/yiisoft/yii/pull/4510 solves your issue?

marcovtwout commented 1 year ago

Marking as fixed with https://github.com/yiisoft/yii/pull/4510/