PHP 技巧和窍门

2025-06-07

PHP 技巧和窍门

使用 PHP 对我来说是最有趣的经历之一,我参与过大大小小的项目,每次都能学到新的东西。

我将分享一些小技巧,这些技巧帮助我减少了几行代码,还有一些技巧完全改变了我的工作流程,如下所示:

如果您有任何想法、问题或意见,请联系我...事不宜迟,让我们开始吧。

提示 1:(if 和 else)

你可能之前在教程或其他文章中见过这个,但这真的很重要,我应该深入讲一下。在代码中使用elseelse if块并没有错,事实上,它们就是为使用而设计的,然而在某些情况下,这些块会变得多余。让我们来看一个例子:



function output_gender(bool $user_is_male) {
    if ($user_is_male) {
        return "User is male";
    } else {
        return "User is female";
    }
}


Enter fullscreen mode Exit fullscreen mode

在本例中,output_gender 函数根据$user_is_male真假返回一组输出。当return在函数中使用时,return语句后面的任何代码都将被完全忽略,因此,如果$user_is_male为真,则该代码块将被忽略,因为返回了一个值。有了这个概念,我们可以像这样else删除该代码块:else



function output_gender(bool $user_is_male) {
    if ($user_is_male) {
        return "User is male";
    }

    return "User is female";
}


Enter fullscreen mode Exit fullscreen mode

我们知道,如果传入的条件为 ,则 if 语句不会运行false。这意味着它将直接跳到“用户是女性”部分。

提示 2:(如果块:较少 vs. 更多)

技巧 2 建立在我们刚刚讨论的技巧之上,但更深入一些。在 if/else 语句中,或者使用技巧 1 这样的示例,你可能会遇到一个条件,其中一个代码块(if 或 else)的代码比另一个代码块少。在这种情况下,最好先处理代码较少的代码块。让我们来看一个真实的例子。



public function categoryWithPosts($category)
{
    $category = Category::find($category);

    if ($category) {
        $category->posts = $category->posts()->published()->get();
        return response(['data' => $category], 200);
    } else {
        return response(['error' => 'Category not found'], 404);
    }
}


Enter fullscreen mode Exit fullscreen mode

上面的代码检查了文章分类,并根据分类是否找到来判断条件。如果我们遵循技巧 1,代码应该如下所示:



public function categoryWithPosts($category)
{
    $category = Category::find($category);

    if ($category) {
        $category->posts = $category->posts()->published()->get();
        // having any more code here would
        // bloat this part of the function
        return response(['data' => $category], 200);
    }

    return response(['error' => 'Category not found'], 404);
}


Enter fullscreen mode Exit fullscreen mode

这段代码是正确的,但是您可以清楚地看到,我们的主要代码被包裹在{}, 中,并进一步被推入。如果这段代码很长,将它们全部放在 if 块中会很麻烦。按照技巧 2 的方法,我们可以改写为:



public function categoryWithPosts($category)
{
    $category = Category::find($category);

    if (!$category) {
        return response(['error' => 'Category not found'], 404);
    }

    $category->posts = $category->posts()->published()->get();
    // we can freely have more code here
    // without worrying about the code looking weird
    return response(['data' => $category], 200);
}


Enter fullscreen mode Exit fullscreen mode

由于 else 代码块代码较少,我们使用否定语句 来!优先执行该代码。因此我们的 if 语句包含if not category, run code...。这为我们提供了更多空间来自由处理主要代码。

提示3:(验证多个字符串)

假设我们想要确定某个变量是否是众多字符串之一,我们显然必须编写一堆条件语句来验证这一点:



$item = "candy";

switch ($item) {
    case 'candy':
        return true;
    case 'toy':
        return true;
    default:
        return false;
}
// we're not adding break because we're using return

// or
if ($item == 'candy' || $item == 'toy') {
    return true;
}

return false;


Enter fullscreen mode Exit fullscreen mode

false如果 item 变量既不是candy也不是,则此代码返回toy。这完全正确,但是重复性太强。我们可以检查数组中是否存在我们想要查找的字符串:



if (in_array($item, ["candy", "toy"])) {
    return true;
}

return false;


Enter fullscreen mode Exit fullscreen mode

in_array由于返回布尔值,因此甚至可以进一步缩短。



return in_array($item, ["candy", "toy"]);


Enter fullscreen mode Exit fullscreen mode

我们刚刚把这些行缩短到只有一行,简洁吧?这是怎么回事?我们有一个包含要检查的字符串的数组。然后我们将其传递给in_array。这会创建一个简单的条件,如下所示:



if $item is inside the array holding "candy" and "toy", return true, else false


Enter fullscreen mode Exit fullscreen mode

您可能想知道,为什么不直接返回 $item 是糖果还是玩具,因为那也只是一行,如下所示:



return ($item == 'candy' || $item == 'toy');


Enter fullscreen mode Exit fullscreen mode

这将给我们相同的结果,但是假设我们检查 10 个字符串:



return ($letter == 'a' || $letter == 'b' || $letter == 'c' || $letter == 'd' ...);


Enter fullscreen mode Exit fullscreen mode

与此相比,您可以清楚地看到这很容易失控:



return in_array($letter, ["a", "b", "c", "d", ...]);


Enter fullscreen mode Exit fullscreen mode

请注意,第一个参数in_array是我们实际检查的字符串

提示 4:(??)

??这可能是创建内联条件最简单的方法,无需两部分代码。我的意思是什么?我们来看一个例子,它能帮我解释清楚。



$data = [
    "a" => 1,
    "b" => 2,
    "c" => null,
];

return $data["c"] ? $data["c"] : "No data";


Enter fullscreen mode Exit fullscreen mode

这里的最后一行检查键是否c$data真,如果不是,则返回“无数据”。

我们可以用 ?? 重写最后一行,如下所示:



// ...
return $data["c"] ?? "No data";


Enter fullscreen mode Exit fullscreen mode

在这种情况下, ?? 的行为类似于||其他语言中的逻辑运算符。现实世界中的示例如下:



$user = getUserFromDb($user_id) ?? trigger_error("User id is invalid");

echo $user;


Enter fullscreen mode Exit fullscreen mode

getUserFromDb是从数据库返回用户,但是,如果找不到用户,我们不会设置用户变量,而是使用 来中断应用程序trigger_error。如果没有它,??我们就必须这样写:



$user = getUserFromDb($user_id);

if (!$user) {
    trigger_error("User id is invalid");
}

echo $user;


Enter fullscreen mode Exit fullscreen mode

提示 5:(递归优于重复)

我认为这个技巧非常简单:尽量使用递归,而不是大量重复代码。有些情况下你需要重复一些代码,这没问题,但如果你发现重复的代码太多,那就把它变成一个方法。递归是怎么实现的?我们来看一个例子:这是我为Leaf 框架的请求对象编写的一种方法,用于返回传入请求的特定字段。



/**
 * Returns request data
 *
 * This methods returns data passed into the request (request or form data).
 * This method returns get, post, put patch, delete or raw faw form data or NULL
 * if the data isn't found.
 *
 * @param string|array $params The parameter(s) to return
 * @param bool $safeData Sanitize output
 */


Enter fullscreen mode Exit fullscreen mode

这意味着此方法可以接受数组或字符串作为参数,并根据输入返回字符串或数组。解决方案是检查输入是否为数组,然后循环获取数组中的字符串,最后对这些字符串执行数据提取操作,如下所示。



public function get($params, bool $safeData = true)
{
    if (is_string($params)) return $this->body($safeData)[$params] ?? null;

    $data = [];
    foreach ($params as $param) {
        $data[$param] = $this->body($safeData)[$params] ?? null;
    }
    return $data;
}


Enter fullscreen mode Exit fullscreen mode

这里你注意到了$this->body($safeData)[$params] ?? null重复,不仅如此,如果传入一个包含另一个数组的数组会怎么样呢?因为这是一个库,所以无法预知用户会传入什么样的内容,所以我改用了这种方法。



public function get($params, bool $safeData = true)
{
    if (is_string($params)) return $this->body($safeData)[$params] ?? null;

    $data = [];
    foreach ($params as $param) {
        $data[$param] = $this->get($param, $safeData); // I called the function again
    }
    return $data;
}


Enter fullscreen mode Exit fullscreen mode

这确保了在循环值不是字符串之前,它不会尝试获取其数据。与上面的技巧相比,这只是一个小技巧,但绝对有用。请注意,此函数是类作用域的,因此使用$this

技巧 6:(PHP + HTML)

这适用于你想在 HTML 中编写 PHP 或在 PHP 中编写 HTML 的情况😅。我们通常会这样做:



<?php
foreach ($items as $item) {
    echo '
        <div class="product__card">
            <h3>{$item->name}</h3>
        </div>
    ';
}
?>


Enter fullscreen mode Exit fullscreen mode

虽然这样没问题,但您可以清楚地看到,我们将 HTML 输出为字符串。HTML 越庞大,匹配标签并准确跟踪我们正在编写的 HTML 部分就越费力。对此有一个简洁的解决方案。



<?php foreach ($items as $item): ?>
    <div class="product__card">
        <h3><?php echo $item->name; ?></h3>
    </div>
<?php endforeach; ?>


Enter fullscreen mode Exit fullscreen mode

您可以清楚地看到我们是如何维护 HTML 格式和代码对齐的……而且,这并非模板引擎,而只是 PHP 帮我们简化了工作。PHP 的一大优势在于它允许用多种不同的方式完成同一件事。在上面的例子中,我们使用了:



foreach (...):
// code
endforeach;

// also works with if
if (...):
// code
endif;

// also
if (...) #one line code

while():
// ...
endwhile;


Enter fullscreen mode Exit fullscreen mode

技巧7:(编写功能块)

功能块的范围很广,可以是大型功能,也可以是围绕默认 PHP 函数的单行包装器,关键在于创建该功能块。这不仅是为了避免重复,还能加快您的工作流程并提高代码的可读性。

您可以编写一个简单的方法来创建重定向,如下所示:



function redirectTo($route) {
    header("location: $route", true, 302);
}


Enter fullscreen mode Exit fullscreen mode

因此,与其header("location: /home", true, 302)每次都写,不如这样写redirectTo("/home")。这同样适用于第三方库和长流程,以开放的方式编写可重用的代码块,例如:



UserNotification::send($user_id, $notification);


Enter fullscreen mode Exit fullscreen mode

显然比每次给用户发送通知时都写一堆代码要好。又一个很小但非常有用的技巧。

提示 8:(使用类型)

另一个很简单的。这是 PHP 中最少使用但非常强大的功能之一。如果您与团队合作,此功能可以为您和其他开发人员减轻很多压力。

当然,您可以像上面技巧 5 中的示例一样编写函数描述,但在大型项目中为所有函数和变量编写函数描述将是一项艰巨的任务。

我们来看看类型如何拯救我们的生命:



function getItem($item) {
    // $item is expected to be an array
    // for whatever reason
    return allItems()[$item[0]];
}


Enter fullscreen mode Exit fullscreen mode

如果不同的开发人员或者甚至是您自己在几周后从事该项目,看到该getItem方法,$item那里的变量显然应该是一个字符串,但该函数是为处理数组而编写的。

这里的危险之处在于,传入字符串并不会破坏应用程序,它仍然可以完美运行。为什么?

如果将“椅子”传递给函数,它将被评估为allItems()["c"],最终会导致错误,让你在凌晨 12 点睡不着觉😅。这可以通过以下方式轻松避免:



function getItem(array $item) {
    return allItems()[$item[0]];
}


Enter fullscreen mode Exit fullscreen mode

这将确保传入的内容都是所需的类型。更多信息请访问 php.net

您还可以使用我们上面看到的类似方法is_stringis_array如下所示:



function getItem($item) {
    if (!is_array($item)) throwErr("item should be array");

    return allItems()[$item[0]];
}


Enter fullscreen mode Exit fullscreen mode

提示 9:(框架/库并不邪恶)

说实话,开源库确实会出问题!有时候,我们引入的库非但没有帮到我们,反而给我们带来了更多麻烦。这听起来好像我在彻底否定开源软件包,其实不然,我自己也写过开源软件包,所以显然不是!

我的观点是,你应该多了解你引入的软件包,阅读它们的文档,在 GitHub 上查看它们的 issue,不要冒不必要的风险。我建议的一点,可以追溯到技巧 7,就是为你引入的软件包的功能编写包装器。这会给你带来更多控制权,也会让你的代码更简洁。

关于框架,你可能之前听说过,但你应该先熟悉一下 PHP。PHP 框架,无论用什么语言编写,都遵循 PHP 的原则和风格,所以第一步显然是熟悉 PHP。

接下来就是选择一个你觉得舒服的,并坚持下去。有很多选择:

  • Laravel:如果你喜欢魔法,Laravel 几乎可以为你做所有事情(除非你另有决定)
  • Slim:一个 REST API 框架,有一种“自带”的感觉
  • Leaf:这就是我写的,受到 Slim 和 Laravel 的启发,它赋予了你可以控制的魔力。

我只提到我实际使用的框架以避免偏见。

提示 10:(不要只编写代码!)

好吧,这是一条额外的建议。它不仅适用于 PHP,从技术上讲,几乎适用于你使用的所有语言/框架。我所说的“不仅仅是代码”相对简单。

假设您想编写一种方法来从用户的帐户中请求付款,直接编写此功能可能会(也可能不会)让您在某个时候感到困惑,这时您将不得不停下来,向上滚动,从某个文件中检查某些内容,或类似的事情。

我的建议是什么?这里:



// in class scope
public function requestPayout()
{
    // parse token to get user id

    // fetch user from DB with id

    // check if the user is eligible for payouts

    // get user balance with user helper

    // check and throwErr for insufficient funds

    // ...
}


Enter fullscreen mode Exit fullscreen mode

以上只是让你在实际开始编写任何功能之前进行所有必要的思考。它在某种程度上也帮助你交叉检查你正在构建的内容,因为你最终会先列出所有流程。

感谢阅读

这些是我在使用 PHP 的过程中发现的一些技巧和窍门,其中一些可能适合您,而其他一些可能不适合您,请随意选择您觉得合适的并坚持使用。

说这些是做事的好方法是完全错误的,所以只使用它们,正如我之前提到的,PHP 是一种提供许多不同方式来做同一件事的语言,所以如果你有任何东西想分享,一个新的技巧,一种更快的方式来做我提到的事情,一些你不同意的事情,请联系我。

文章来源:https://dev.to/mychi_darko/php-tips-and-tricks-4kpn
PREV
排名前 5 的 JavaScript 轮播库
NEXT
保护您的数据 API 免受网络爬虫的攻击