返回

解决PHP错误:尝试读取int类型的'game_id'属性

php

PHP 错误解析:"Warning: Attempt to read property 'game_id' on int"

当 PHP 代码中出现 "Warning: Attempt to read property 'game_id' on int" 或类似的错误时,通常意味着你正试图将一个非对象类型(在这里是整型 int)当作对象来访问其属性。 本文将深入分析此问题的常见原因,并提供多种解决方案。

问题分析

错误信息 “Warning: Attempt to read property "game_id" on int” 明确指出,代码试图访问一个整型变量的 game_id 属性,而整型变量没有属性。 发生此错误的原因通常是数据库查询结果返回的数据类型与预期不符。 分析提供的代码,问题出在 ShowsModel::getAllGames 方法:

public static function getAllGames($show_id)
    {
        // ... 省略数据库连接代码 ...

        $sql = 
        "SELECT * FROM 
        games
        WHERE show_id = :show_id";

        $query = $database->prepare($sql);
        $query->execute(array(':show_id' => $show_id));

        // fetch() is the PDO method that gets a single result
        return $query->fetch();
    }

这段代码使用 $query->fetch() 获取数据,PDO::fetch() 默认情况下会返回一行结果,且当结果集中没有更多行时,该方法会返回 FALSE 值,不会报错。 问题在于当 games 表中没有与 $show_id 匹配的记录时,$query->fetch() 会返回 FALSE,在 PHP 中 FALSE 会被视为整数 0。 之后视图层代码尝试遍历这个 0 值,导致了错误。

解决方案

以下提供几种解决此问题的方法,并详细解释其原理和应用场景。

1. 使用 fetchAll() 获取所有匹配结果

当期望获取多条记录时,应该使用 PDOStatement::fetchAll() 方法,它会将所有匹配的行以数组形式返回。 修改 ShowsModel::getAllGames 方法如下:

public static function getAllGames($show_id)
    {
        // ... 省略数据库连接代码 ...

        $sql = 
        "SELECT * FROM 
        games
        WHERE show_id = :show_id";

        $query = $database->prepare($sql);
        $query->execute(array(':show_id' => $show_id));

        // fetchAll() gets all results
        return $query->fetchAll();
    }

操作步骤:

  • 打开 ShowsModel.php 文件。
  • 找到 getAllGames 方法。
  • $query->fetch(); 修改为 $query->fetchAll();

代码示例:

// ShowsModel.php
public static function getAllGames($show_id)
    {
        $database = DatabaseFactory::getFactory()->getConnection();

        $sql = "SELECT * FROM games WHERE show_id = :show_id";
        $query = $database->prepare($sql);
        $query->execute(array(':show_id' => $show_id));

        return $query->fetchAll(); // 获取所有结果
    }

// shows/details.php (视图层)
<table class="table table-hover">
  <thead>
    <tr>
      <th scope="col">game_id (primary key)</th>
      <th scope="col">show_id</th>
    </tr>
  </thead>
  <tbody>
  <?php 
      // 检查 $this->games 是否为数组且不为空
      if (is_array($this->games) && !empty($this->games)) { 
          foreach ($this->games as $game) { 
  ?>
                 <tr>
                     <td><?= $game['game_id']; ?></td>
                     <td><?= $game['show_id']; ?></td>
                 </tr>
               <?php 
              } 
          } else {
  ?>
               <tr>
                   <td colspan="2">No games found for this show.</td>
               </tr>
               <?php
           }
        ?>  
  </tbody>
</table>

原理: fetchAll() 会将所有符合条件的记录以关联数组或索引数组的形式返回。 如果没有匹配记录,会返回一个空数组。 视图层需要做相应的修改来处理这个空数组,防止尝试遍历非数组的情况。

安全建议: 使用 fetchAll() 需要注意数据量,如果结果集很大,一次性加载到内存可能导致性能问题。可以考虑分页查询或使用 PDO::FETCH_ASSOCwhile 循环逐行读取, 这样处理不会占用过多内存,代码如下:

    // 修改后的ShowsModel
    public static function getAllGames($show_id)
    {
        $database = DatabaseFactory::getFactory()->getConnection();
        $sql = "SELECT * FROM games WHERE show_id = :show_id";
        $query = $database->prepare($sql);
        $query->execute([':show_id' => $show_id]);

        // 返回 PDOStatement 对象
        return $query;
    }

修改控制器:

   // 略过其他控制器代码
    'games' => ShowsModel::getAllGames($show_id) 

然后修改 details.php 文件,将fetchAll相关的语句替换如下,这能避免获取全部数据导致内存溢出。

<tbody>
    <?php
    // 确保 $this->games 是一个 PDOStatement 对象
    if ($this->games instanceof PDOStatement) {
        // 设置抓取模式为关联数组
        $this->games->setFetchMode(PDO::FETCH_ASSOC);
        while ($game = $this->games->fetch()) {
            ?>
            <tr>
                <td><?= $game['game_id']; ?></td>
                <td><?= $game['show_id']; ?></td>
            </tr>
        <?php
        }
        // 如果没有找到记录
        if ($this->games->rowCount() === 0) {
            ?>
            <tr>
                <td colspan="2">没有找到与此演出相关的游戏。</td>
            </tr>
        <?php
        }
    } else {
        ?>
        <tr>
            <td colspan="2">发生错误,无法获取游戏数据。</td>
        </tr>
    <?php
    }
    ?>
</tbody>

这段代码使用了 while 循环来迭代 $this->games 对象中的每一行,使用 $game['game_id'] 获取特定值,并正确显示结果。这段代码做了更完善的错误检查处理,以确定 PDOStatement 是否已成功创建。这种改进在处理大型数据集时更有意义,不会因内存溢出而崩溃。

2. 在视图层添加数据类型检查

即使数据库查询结果不符合预期,也可以在视图层添加防御性代码,防止直接访问可能不存在的属性。

操作步骤:

  • 打开 shows/details.php 文件。
  • foreach 循环前,添加数据类型检查代码。

代码示例:

<table class="table table-hover">
  <thead>
    <tr>
      <th scope="col">game_id (primary key)</th>
      <th scope="col">show_id</th>
    </tr>
  </thead>
  <tbody>
  <?php if (is_array($this->games) || is_object($this->games)) { foreach ($this->games as $game) { ?>
                <tr>
                   <?php if(is_object($game)){ ?>
                       <td><?= $game->game_id; ?></td>
                       <td><?= $game->show_id; ?></td>
                   <?php } else if(is_array($game)) { ?>
                      <td><?= $game['game_id']; ?></td>
                      <td><?= $game['show_id']; ?></td>
                   <?php } ?>
                </tr>
               <?php }
           } else { ?>
                  <tr>
                    <td colspan="2">No games found for this show.</td>
                  </tr>
              <?php } ?>
  </tbody>
</table>

原理: 这段代码首先检查 $this->games 是否为数组或者对象,如果是则继续遍历。 然后检查每一个 $game 的类型是对象还是数组,再分别读取相应的数据,防止在非对象或数组上访问属性。 如果不是,则显示 “No games found for this show.”。 这可以避免错误并提供更友好的用户体验。 请根据 fetchAll() 使用时返回的数组类型(索引数组或关联数组)来调整访问数据的方式。

3. 在 Model 层返回空数组

更进一步,可以修改 Model 层代码,在没有找到数据时直接返回一个空数组,这样可以简化视图层的逻辑。

操作步骤:

  • 打开 ShowsModel.php 文件。
  • 修改 getAllGames 方法。

代码示例:

public static function getAllGames($show_id)
    {
        $database = DatabaseFactory