在函数中丢失全局变量

lose the globals in functions

我有一个用 PHP 编写的自定义 CMS 系统。我几乎完成了将所有旧的遗留 mysql_ 函数转换为 PDO 的工作。 但是,我在一个文件中有很多函数,没有 class 包装器。文件中只有大约 50 个函数,实际上是 运行 CMS 所需的每个函数。许多天前,我养成了这样使用全局变量的坏习惯:

function getWidgets($widget_id){
 global $db, $BLOG_ID;
 $stmt = $db->prepare("SELECT * FROM widget_assoc WHERE bid=? AND aid=?");
 $stmt->execute(array($BLOG_ID, $widget_id));
 $matches = $stmt->rowCount();
 if($matches !== 0){
 for($i = 0; $path[$i] = $stmt->fetch(); $i++) ;
 array_pop($path);
 return $path;
 }
 }

像 $db 和 $BLOG_ID 这样的变量需要保持不变,因为许多函数依赖于这两个变量(还有一些) 我不确定完成这项清理工作的最佳方法。 我可以将整个函数文件打包成一个 class 吗? 我是否必须将所有输出变量从函数更改为 $this-> ?

我正在尝试找到一种轻松的方法来删除所有全局变量,而不必重写在主题中解析函数输出的所有函数和模板。

我最近读了很多关于全局变量有多么糟糕的文章,但我似乎无法想出一种简化的方法来实现这一点。如果不对我的代码进行大量修改,这可能是不可能的。这就是我来这里的原因!谢谢

编辑: 我使用在每个 CMS 页面的页眉中调用的 config.php。此配置文件包含类似 $BLOG_ID = '12' 的变量;并在站点构建时动态创建。这些变量可用于所有页面,但要将它们放入我的函数中,我必须使用 global。我对 classes 没有任何经验,并且犯了一个错误,就是将许多函数放在一个没有 classes 的函数文件中。

如果你想在多个地方使用变量,你可以为这些变量创建一个单例。

class Config
{
/**
 * @var Singleton The reference to *Singleton* instance of this class
 */
private static $instance;

public $db = 'db';
public $BLOG_id = 'id';

/**
 * Returns the *Singleton* instance of this class.
 *
 * @return Singleton The *Singleton* instance.
 */
public static function getInstance()
{
    if (null === static::$instance) {
        static::$instance = new static();
    }

    return static::$instance;
}

/**
 * Protected constructor to prevent creating a new instance of the
 * *Singleton* via the `new` operator from outside of this class.
 */
protected function __construct()
{
}

}

//To use it
$config = Config::getInstance();
$config->db;

有多种方法可以解决这个问题,具体取决于您想花多少时间以及 "well" 您想要如何做。

保持原样

您可以按照他们假设工作正常的方式保留该方法。根据您关于不想重写所有内容的评论,这可能是您最好的方法。

归根结底,(在我看来)在所有函数中使用全局变量与拥有一个包含 50 个单独函数但没有 classes 的文件一样糟糕。

将全局变量传入

如果您将全局变量更改为函数参数,它会让您有洞察力,能够在您调用方法时准确地知道变量的值是什么以及它来自哪里,然后将变量传递给方法并删除对全局变量的需要。

使用 classes 依赖 injection/inheritance

这可能是 "best" 方法,但也是实现时间最长的方法。老实说,我建议你还是这样做。

因此假设您的 50 种方法文件包含多种用于不同目的的方法,您可能会决定需要 1、2 或 3 个碱基 classes 和 5-10 个 classes一个目的或角色,例如,您可能有一个抽象的 Base class 来设置您的 PDO (DB class) 连接,然后您的博客 class (示例)可能会扩展 Base class。这样,Blog class 将继承生成 Blog 条目所需的所有外部依赖项(外部意味着 Blog class 可能假设其目的是检索、格式化和输出 Blog post 仅 - 应该已经处理了数据库连接。

一个实际的例子可能是这样的:

/**
 * Handle your database connection, querying etc functions
 */
class DB {
    protected $_pdo;
    public function getPdo() {
        if (is_null($this->pdo)) {
            $this->_pdo = new PDO(...);
        }
        return $this->_pdo;
    }

    public function __construct() {
        return $this->getPdo();
    }

    public function query($sql, $binds = []) {
        // write a function that executes the $sql statement on the
        // PDO property and return the result. Use $binds if it is not
        // empty
        $eg = $this->getPdo()->prepare($sql);
        return $eg->execute((array) $binds);
}

/**
 * Create a basic framework for all purpose-classes to extend
 */
abstract class Base {
    /**
     * "DB" property might be broad here to cover other DBs or connection
     * methods (in theory)
     */
    protected $_db;
    public function __construct() {
         $this->_db = new DB;
    }

    public function db($sql, $binds) {
        return $this->_db->query($sql, $binds);
    }

    // insert other common methods here that all type-specific classes
    // can use
}

现具体执行action/role:

class Blog extends Base {
    public function get($blogId = null) {
        // Basic error check
        if (empty($blogId)) {
            throw new UnexpectedValueException('Blog post ID was missing!');
        }
        return $this->db('SELECT * FROM `blogposts` WHERE blog_id = ?', $blogId);
    }
}

NOTE 这个我没测试过,不过现在的原则是Blog class 只包含Blog 特有的逻辑post.任何格式化函数、安全函数等都可以在 Base class 中或在另一个辅助 class 中,Base class 使用类似于 DB,例如格式化程序 class.

你应该可以做到这一点:

<?php
# blogPost.php
# - Gets a blog post
require_once 'common.php'; // <--- include your class files, or an autoloader

// Instantiate the class for this role
$blog = new Blog;

// Get the blog post
$id = (isset($_GET['id'])) ? (int) $_GET['id'] : null;
$post = $blog->get($id);

// now other methods:
$post->toHTML(); // example - function might call a template file, insert the
                 // DB results into it and output it to the browser

这只是一个粗略的例子,但展示了您如何可以构建一组classes来实现class具有单一角色和具有单一目的的方法(例如 "get a blog post by its ID")。

这样,所有扩展 Base 的东西都将自动访问数据库(通过继承)。如果您想尝试从实现中删除 SQL,您可以向您的数据库 class 添加一些方法来提供基本的 ORM。

为通用值制作一个class

另一个 short/quick 选项是创建一个 class 可以为您包含的文件中的所有函数提供任何共同的东西,在本例中是数据库处理程序和博客post ID。例如:

class Common {
    protected static $_db;
    protected static $_blogId;

    public function getDb() {
        if (is_null(static::$_db)) {
            static::$_db = new PDO(...);
        }
        return static::$_db;
    }

    public static function getBlogId() {
        return (int) static::$_blogId;
    }
    public static function setBlogId($id) {
        static::$_blogId = (int) $id;
    }
}

现在您只需在开始调用您的函数之前实例化此 class,并设置博客 post ID(如果需要)。 PDO 连接将在需要时延迟创建。

# functions.php
require_once 'common.php';

function getWidgets($widget_id) {
    $stmt = Common::getDb()->prepare('SELECT * FROM widget_assoc WHERE bid = ? AND aid = ?');
    $stmt->execute(array(Common::getBlogId(), $widget_id));
    $matches = $stmt->rowCount();
    if ($matches !== 0) {
        for($i = 0; $path[$i] = $stmt->fetch(); $i++);
        array_pop($path);
        return $path;
    }
}

您唯一的责任是在每个页面上设置博客 post ID,例如:

# blogPost.php
require_once 'common.php';

// Manual dependency blog ID needs to be set before processing:
$blogId = isset($_GET['blog_id']) ? (int) $_GET['blog_id'] : null;
Common::setBlogId($blogId);

// now you call your processing methods and perform your logic flow