SlimPHP开发指南二:第一个Slim程序

1,684 阅读16分钟

教科书般的PHP框架学习指南

注:翻译水平有限,如有错误,欢迎指正

If you’re looking for a tour through all the ingredients for setting up a very simple Slim application (this one doesn’t use Twig, but does use Monolog and a PDO database connection) then you’re in the right place. Either walk through the tutorial to build the example application, or adapt each step for your own needs.

如果您正在寻找建立一个非常简单的Slim应用程序所需的要素(这个应用程序不使用Twig,但是使用了Mongolog和PDO数据库连接),那么您来对地方了。您可以跟着本教程来构建示例应用程序,也可以根据自己的需要调整每个步骤。

Before you start: There is also a skeleton project which will give you a quick-start for a sample application, so use that if you’d rather just have something working rather than exploring how all the moving parts work.

在开始之前:还有一个骨架项目,它将为示例应用程序提供一个快速启动,因此,如果您只想让某些东西工作,而不想探究所有移动部件是如何工作的,那么可以使用它。

Getting Set Up

Start by making a folder for your project (mine is called project, because naming things is hard). I like to reserve the top level for things-that-are-not-code and then have a folder for source code, and a folder inside that which is my webroot, so my initial structure looks like this:

首先为您的项目创建一个文件夹(我的文件夹名为project,因为命名很困难)。我喜欢把非代码的东西放在顶层,然后一个文件夹用来放源码,另一个文件夹用来做webroot,所以我的初始结构是这样的:

.
├── project
│   └── src
│       └── public

安装slim框架

Composer is the best way to install Slim Framework. If you don’t have it already, you can follow the installation instructions, in my project I’ve just downloaded the composer.phar into my src/ directory and I’ll use it locally. So my first command looks like this (I’m in the src/ directory):

Composer是安装Slim框架的最佳方法。如果你还没有,你可以按照说明安装,在我的项目中,我刚刚下载了composer.phar到我的src/目录中,我将在本地使用它。所以我的第一个命令是这样的(我在src/目录中):

php composer.phar require slim/slim

This does two things:

  • Add the Slim Framework dependency to composer.json (in my case it creates the file for me as I don’t already have one, it’s safe to run this if you do already have a composer.json file)

    向composer.json添加slim依赖(在我的例子中,它为我创建了一个文件,因为我还没有一个,如果您已经有一个composer.json,那么运行这个文件也是安全的)

  • Run composer install so that those dependencies are actually available to use in your application

  • 运行composer install,以便这些依赖项实际上可以在应用程序中使用

If you look inside the project directory now, you’ll see that you have a vendor/ folder with all the library code in it. There are also two new files: composer.json and composer.lock. This would be a great time to get our source control setup correct as well: when working with composer, we always exclude the vendor/ directory, but both composer.json and composer.lock should be included under source control. Since I’m using composer.phar in this directory I’m going to include it in my repo as well; you could equally install the composer command on all the systems that need it.

如果您现在查看项目目录,您将看到您有一个包含所有库代码的vendor/文件夹。还有两个新文件:composer.json和composer.lock。这将是纠正源代码控制设置的好时机:当使用composer时,我们总是排除vendor/目录,但把composer.json和composer.lock放在源代码控制之下。因为我用的composer.phar在当前这个目录中,我也将把它包含在我的代码库中;您同样也可以在所有需要composer命令的系统上全局安装composer命令并使用它。

To set up the git ignore correctly, create a file called src/.gitignore and add the following single line to the file:

要正确设置git ignore,请创建一个名为src/.gitignore的文件。并在文件中添加以下单行:

vendor/*

Now git won’t prompt you to add the files in vendor/ to the repository - we don’t want to do this because we’re letting composer manage these dependencies rather than including them in our source control repository.

现在git不会提示您将vendor/中的文件添加到存储库中——我们不想这样做,因为我们让composer管理这些依赖项,而不是将它们包含在我们的源代码控制存储库中。

创建应用

There’s a really excellent and minimal example of an index.php for Slim Framework on the project homepage so we’ll use that as our starting point. Put the following code into src/public/index.php:

在项目主页上有一个很好的例子,它是index.php for Slim框架的一个非常小的例子,所以我们将使用它作为我们的起点。将以下代码放入src/public/index.php:

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

require '../../vendor/autoload.php';

$app = new \Slim\App;
$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");

    return $response;
});
$app->run();

We just pasted a load of code … let’s take a look at what it does.

我们刚刚粘贴了一大堆代码…让我们看看它是做什么的。

The use statements at the top of the script are just bringing the Request and Response classes into our script so we don’t have to refer to them by their long-winded names. Slim framework supports PSR-7 which is the PHP standard for HTTP messaging, so you’ll notice as you build your application that the Request and Response objects are something you see often. This is a modern and excellent approach to writing web applications.

脚本顶部的use语句只是将请求和响应类引入到脚本中,因此我们不必通过它们冗长的名称引用它们。Slim框架支持PSR-7,这是HTTP消息传递的PHP标准,因此在构建应用程序时,您将注意到请求和响应对象是您经常看到的对象。这是编写web应用程序的一种现代而优秀的方法。

Next we include the vendor/autoload.php file - this is created by Composer and allows us to refer to the Slim and other related dependencies we installed earlier. Look out that if you’re using the same file structure as me then the vendor/ directory is one level up from your index.php and you may need to adjust the path as I did above.

接下来,我们将包含vendor/autoload.php文件——该文件由Composer创建,允许我们引用前面安装的Slim和其他相关依赖项。注意,如果您使用的是与我相同的文件结构,那么vendor/目录比index.php高一级,您可能需要像我上面所做的那样调整路径。

Finally we create the app object which is the start of the Slim goodness. Theapp->get() call is our first “route” - when we make a GET request to /hello/someone then this is the code that will respond to it. Don’t forget you need that final $app->run() line to tell Slim that we’re done configuring and it’s time to get on with the main event.

最后,我们创建app对象,这是使用Slim的第一步关键。app->get()调用是我们的第一个“路由”——当我们向/hello/somename发出get请求时,这是响应它的代码。不要忘记,您需要最后的$app->run()行来告诉Slim我们已经完成了配置,现在是继续处理主事件的时候了。

Now we have an application, we’ll need to run it. I’ll cover two options: the built-in PHP webserver, and an Apache virtual host setup.

现在我们有了一个应用程序,我们需要运行它。我将介绍两个选项:内置的PHP web服务器和Apache虚拟主机设置。

通过PHP自带服务器运行你的应用

This is my preferred “quick start” option because it doesn’t rely on anything else! From the src/public directory run the command:

这是我首选的“快速启动”选项,因为它不依赖于任何其他东西!从src/public目录运行命令:

php -S localhost:8080

This will make your application available at http://localhost:8080 (if you’re already using port 8080 on your machine, you’ll get a warning. Just pick a different port number, PHP doesn’t care what you bind it to).

可以通过http://localhost:8080访问你的slim应用(如果您的计算机上已经使用端口8080,您将得到一个警告。只需选择一个不同的端口号,PHP并不关心具体的端口号是什么)。

通过nginx或apache运行你的应用

To get this set up on a standard LAMP stack, we’ll need a couple of extra ingredients: some virtual host configuration, and one rewrite rule.

要在标准LAMP堆栈上设置此功能,我们需要一些额外的组件:一些虚拟主机配置和一个重写规则。

The vhost configuration should be fairly straightforward; we don’t need anything special here. Copy your existing default vhost configuration and set the ServerName to be how you want to refer to your project. For example you can set:

vhost配置应该相当简单;我们这里不需要什么特别的东西。复制现有的默认vhost配置,并将ServerName设置为您希望引用项目的方式。例如,你可以设置:

ServerName slimproject.test
or for nginx:
server_name slimproject.test;

Then you’ll also want to set the DocumentRoot to point to the public/ directory of your project, something like this (edit the existing line):

然后,您还需要将DocumentRoot设置为指向项目的公共/目录,如下所示(编辑现有行):

DocumentRoot    /home/lorna/projects/slim/project/src/public/
or for nginx:
root    /home/lorna/projects/slim/project/src/public/

Don’t forget to restart your server process now you’ve changed the configuration! I also have a .htaccess file in my src/public directory; this relies on Apache’s rewrite module being enabled and simply makes all web requests go to index.php so that Slim can then handle all the routing for us. Here’s my .htaccess file:

不要忘记重新启动您的服务器进程,现在您已经更改了配置!

我的src/public目录中也有一个.htaccess文件;这依赖于启用Apache的重写模块,并简单地将所有web请求转到index.php,这样Slim就可以为我们处理所有路由。这是我的.htaccess文件:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

nginx does not use .htaccess files, so you will need to add the following to your server configuration in the location block:

nginx不使用.htaccess文件,所以您需要在location块中向服务器配置添加以下内容:

if (!-e $request_filename){
    rewrite ^(.*)$ /index.php break;
}

NOTE: If you want your entry point to be something other than index.php you will need your config to change as well. api.php is also commonly used as an entry point, so your set up should match accordingly. This example assumes you are using index.php.

注意:如果希望入口点不是index.php,还需要修改配置,api.php也通常用作入口点,因此您的设置应该相应地匹配。本例假设您正在使用index.php。

With this setup, just remember to use slimproject.test instead of http://localhost:8080 in the other examples in this tutorial. The same health warning as above applies: you’ll see an error page at slimproject.test but crucially it’s Slim’s error page. If you go to slimproject.test/hello/joebl… then something better should happen.

使用此设置,只需记住使用http://slimproject。在本教程的其他示例中,测试而不是http://localhost:8080。上面的健康警告同样适用:您将在http://slimproject中看到一个错误页面。但关键是这是Slim的错误页面。如果你访问http://slimproject.test/hello/joebloggs然后应该会发生更好的事情。

配置和自动加载

Now we’ve set up the platform, we can start getting everything we need in place in the application itself.

现在我们已经建立了平台,我们可以开始在应用程序中获得所需的一切。

向应用程序中添加配置设置

The initial example uses all the Slim defaults, but we can easily add configuration to our application when we create it. There are a few options but here I’ve just created an array of config options and then told Slim to take its settings from here when I create it. First the configuration itself:

现在我们已经建立了平台,我们可以开始在应用程序本身中获得所需的一切。

向应用程序添加配置设置

最初的示例使用了所有Slim缺省值,但是我们可以在创建应用程序时轻松地将配置添加到应用程序中。但这里我只是创建了一个配置选项数组,然后告诉Slim在我创建它时从这里获取设置。

首先配置本身:

$config['displayErrorDetails'] = true;
$config['addContentLengthHeader'] = false;

$config['db']['host']   = 'localhost';
$config['db']['user']   = 'user';
$config['db']['pass']   = 'password';
$config['db']['dbname'] = 'exampleapp';

These string should be added into src/public/index.php before the $app = new \Slim\App line.

这些字符串应该在$app = new \Slim\ app行之前添加到src/public/index.php中。

The first line is the most important! Turn this on in development mode to get information about errors (without it, Slim will at least log errors so if you’re using the built in PHP webserver then you’ll see them in the console output which is helpful). The second line allows the web server to set the Content-Length header which makes Slim behave more predictably.

第一行是最重要的!在开发模式中打开此选项,以获取有关错误的信息(如果没有它,Slim至少会记录错误,因此如果您正在使用内置的PHP webserver,那么您将在控制台输出中看到这些错误,这很有帮助)。第二行允许web服务器设置Content-Length报头,这使得Slim的行为更加可预测。

The other settings here are not specific keys/values, they’re just some data that I want to be able to access later.

Now to feed this into Slim, we need to change where we create the Slim/App object so that it now looks like this:

这里的其他设置不是特定的键/值,它们只是我希望以后能够访问的一些数据。

现在,要把这个输入Slim,我们需要改变要创建的Slim/App对象,使它看起来像这样:

$app = new \Slim\App(['settings' => $config]);

We’ll be able to access any settings we put into that $config array from our application later on.

稍后,我们将能够从应用程序中访问放入$config数组中的任何设置。

为您自己的类设置自动加载

Composer can handle the autoloading of your own classes just as well as the vendored ones. For an in-depth guide, take a look at using Composer to manage autoloading rules.

Composer可以像处理vendored类一样处理您自己类的自动加载。要获得深入的指导,请查看如何使用Composer管理自动加载规则。

My setup is pretty simple since I only have a few extra classes, they’re just in the global namespace, and the files are in the src/classes/ directory. So to autoload them, I add this autoload section to my composer.json file:

我的设置非常简单,因为我只有几个额外的类,它们只是在全局名称空间中,而文件在src/classes/目录中。因此,为了自动加载它们,我将这个自动加载部分添加到composer.json文件:

{
    "require": {
        "slim/slim": "^3.1",
        "slim/php-view": "^2.0",
        "monolog/monolog": "^1.17",
        "robmorgan/phinx": "^0.5.1"
    },
    "autoload": {
        "psr-4": {
            "": "classes/"
        }
    }
}

添加依赖关系

Most applications will have some dependencies, and Slim handles them nicely using a DIC (Dependency Injection Container) built on Pimple. This example will use both Monolog and a PDO connection to MySQL.

大多数应用程序都有一些依赖关系,Slim使用构建在Pimple上的DIC(依赖注入容器)很好地处理这些依赖关系。本例将使用monolog和MySQL的PDO连接。

The idea of the dependency injection container is that you configure the container to be able to load the dependencies that your application needs, when it needs them. Once the DIC has created/assembled the dependencies, it stores them and can supply them again later if needed.

依赖项注入容器的思想是,将容器配置为能够在应用程序需要依赖项时加载它们。一旦DIC创建/组装了依赖项,它就会存储依赖项,并在以后需要时再次提供依赖项。

To get the container, we can add the following after the line where we create $app and before we start to register the routes in our application:

要获得容器,我们可以在创建$app的行后面,注册路由的前面添加以下内容:

$container = $app->getContainer();

Now we have the Slim\Container object, we can add our services to it.

现在我们有了Slim\Container对象,可以将服务添加到其中。

在应用程序中使用monolog组件

If you’re not already familiar with Monolog, it’s an excellent logging framework for PHP applications, which is why I’m going to use it here. First of all, get the Monolog library installed via Composer:

如果您还不熟悉Monolog,它是一个非常好的PHP应用程序日志框架,这就是为什么我要在这里使用它。首先,通过Composer安装monolog库:

php composer.phar require monolog/monolog

The dependency is named logger and the code to add it looks like this:

依赖项命名为logger,添加它的代码如下:

$container['logger'] = function($c) {
    $logger = new \Monolog\Logger('my_logger');
    $file_handler = new \Monolog\Handler\StreamHandler('../logs/app.log');
    $logger->pushHandler($file_handler);
    return $logger;
};

We’re adding an element to the container, which is itself an anonymous function (the $c that is passed in is the container itself so you can access other dependencies if you need to). This will be called when we try to access this dependency for the first time; the code here does the setup of the dependency. Next time we try to access the same dependency, the same object that was created the first time will be used the next time.

我们正在向容器添加一个元素,它本身是一个匿名函数(传入的$c是容器本身,因此如果需要,您可以访问其他依赖项)。当我们第一次尝试访问这个依赖项时,它将被调用;这里的代码负责设置依赖项。下次我们尝试访问相同的依赖项时,将使用第一次创建的相同对象。

My Monolog config here is fairly light; just setting up the application to log all errors to a file called logs/app.log (remember this path is from the point of view of where the script is running, i.e. index.php).

这里的Monolog配置相当简单;只需设置应用程序将所有错误记录到一个名为logs/app.log的文件中(请记住,该路径是从脚本运行的角度来看的,即index.php)。

With the logger in place, I can use it from inside my route code with a line like this:

有了logger,我可以在我的路由代码内这样使用它:

    $this->logger->addInfo('Something interesting happened');

Having good application logging is a really important foundation for any application so I’d always recommend putting something like this in place. This allows you to add as much or as little debugging as you want, and by using the appropriate log levels with each message, you can have as much or as little detail as is appropriate for what you’re doing in any one moment.

对于任何应用程序来说,良好的应用程序日志记录都是非常重要的基础,所以我总是建议在适当的地方使用类似的日志记录。这允许您根据需要添加尽可能多或尽可能少的调试,并且通过对每个消息使用适当的日志级别,您可以在任何时刻获得尽可能多或尽可能少的细节。

向应用程序中添加数据库连接

There are many database libraries available for PHP, but this example uses PDO - this is available in PHP as standard so it’s probably useful in every project, or you can use your own libraries by adapting the examples below.

有许多数据库库可供PHP使用,但是本例使用PDO—这在PHP中是标准的,所以它可能在每个项目中都很有用,或者您可以通过调整下面的示例来使用自己的库。

Exactly as we did for adding Monolog to the DIC(dependency injection container), we’ll add an anonymous function that sets up the dependency, in this case called db:

正如我们在向DIC(依赖注入容器)添加Monolog时所做的那样,我们将添加一个匿名函数来设置依赖关系,在本例中称为db:

$container['db'] = function ($c) {
    $db = $c['settings']['db'];
    $pdo = new PDO('mysql:host=' . $db['host'] . ';dbname=' . $db['dbname'],
        $db['user'], $db['pass']);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    return $pdo;
};

Remember the config that we added into our app earlier? Well, this is where we use it - the container knows how to access our settings, and so we can grab our configuration very easily from here. With the config, we create the PDO object (remember this will throw a PDOException if it fails and you might like to handle that here) so that we can connect to the database. I’ve included two setAttribute() calls that really aren’t necessary but I find these two settings make PDO itself much more usable as a library so I left the settings in this example so you can use them too! Finally, we return our connection object.

还记得我们之前添加到应用程序中的配置吗?这就是我们使用它的地方——容器知道如何访问我们的设置,因此我们可以很容易地从这里获取配置。通过配置,我们创建了PDO对象(请记住,如果它失败,将抛出一个PDOException,您可能希望在这里处理它),以便我们可以连接到数据库。我已经包含了两个setAttribute()调用,这实际上是不必要的,但我发现这两个设置使PDO本身作为一个库更有用,所以我在本例中保留了这些设置,以便您也可以使用它们!最后,返回connection对象。

Again, we can access our dependencies with just $this-> and then the name of the dependency we want which in this case is $this->db, so there is code in my application that looks something like:

同样,我们可以用$this->方式访问我们的依赖,后面跟上我们想要的依赖名称,在本例中是$this->db,所以我的应用程序中的代码看起来像:

    $mapper = new TicketMapper($this->db);

This will fetch the db dependency from the DIC, creating it if necessary, and in this example just allows me to pass the PDO object straight into my mapper class.

这将从DIC获取db依赖项,在必要时创建它,在本例中只允许我将PDO对象直接传递到映射器类中。

创建路由

“Routes” are the URL patterns that we’ll describe and attach functionality to. Slim doesn’t use any automatic mapping or URL formulae so you can make any route pattern you like map onto any function you like, it’s very flexible. Routes can be linked to a particular HTTP verb (such as GET or POST), or more than one verb.

“路由”是我们将描述并附加功能的URL模式。Slim没有使用任何自动映射或URL公式,所以您可以将任何您喜欢的路由模式映射到任何您喜欢的函数上,这是非常灵活的。路由可以链接到特定的HTTP动作(如GET或POST),也可以链接到多个动作。

As a first example, here’s the code for making a GET request to /tickets which lists the tickets in my bug tracker example application. It just spits out the variables since we haven’t added any views to our application yet:

作为第一个示例,下面是向/tickets发出GET请求的代码,它列出了我的bug跟踪器示例应用程序中的罚单。它只是吐出变量,因为我们还没有添加任何视图到我们的应用程序:

$app->get('/tickets', function (Request $request, Response $response) {
    $this->logger->addInfo("Ticket list");
    $mapper = new TicketMapper($this->db);
    $tickets = $mapper->getTickets();

    $response->getBody()->write(var_export($tickets, true));
    return $response;
});

The use of $app->get() here means that this route is only available for GET requests; there’s an equivalent $app->post() call that also takes the route pattern and a callback for POST requests. There are also methods for other verbs - and also the map() function for situations where more than one verb should use the same code for a particular route.

这里使用$app->get()意味着此路由仅对get请求可用;还有一个等效的$app->post()调用,它也接受路由模式和post请求的回调。还有用于其他谓词的方法,以及map()函数,用于多个谓词对特定路由使用相同代码的情况。

Slim routes match in the order they are declared, so if you have a route which could overlap another route, you need to put the most specific one first. Slim will throw an exception if there’s a problem, for example in this application I have both /ticket/new and /ticket/{id} and they need to be declared in that order otherwise the routing will think that “new” is an ID!

Slim路由按照声明的顺序匹配,因此,如果您有一个可能与另一个路由重叠的路由,那么您需要首先放置最具体的一个。如果有问题,Slim会抛出异常,例如在这个应用程序中,我同时拥有/ticket/new和/ticket/{id},它们需要按这个顺序声明,否则路由会认为“new”是一个id !

In this example application, all the routes are in index.php but in practice this can make for a rather long and unwieldy file! It’s fine to refactor your application to put routes into a different file or files, or just register a set of routes with callbacks that are actually declared elsewhere.

在这个示例应用程序中,所有的路由都在index.php中,但实际上这可能导致文件很长且很笨重!可以重构应用程序,将路由放入一个或多个不同的文件中,或者只注册一组路由,而将其中的回调部分放到其他地方。

All route callbacks accept three parameters (the third one is optional):

  • Request: this contains all the information about the incoming request, headers, variables, etc.
  • Response: we can add output and headers to this and, once complete, it will be turned into the HTTP response that the client receives
  • Arguments: the named placeholders from the URL (more on those in just a moment), this is optional and is usually omitted if there aren’t any

所有路由回调都接受三个参数(第三个是可选的):

  • Request:包含所有关于传入请求、头、变量等的信息。
  • Response:我们可以添加输出和报头到其中,一旦完成,它将转换成客户端接收到的HTTP响应
  • 参数:URL中的已命名占位符(稍后会详细介绍),这是可选的,如果没有占位符,通常会省略

This emphasis on Request and Response illustrates Slim 3 being based on the PSR-7 standard for HTTP Messaging. Using the Request and Response object also makes the application more testable as we don’t need to make actual requests and responses, we can just set up the objects as desired.

这种对请求和响应的强调说明了Slim 3基于HTTP消息传递的PSR-7标准。使用请求和响应对象还可以使应用程序更具可测试性,因为我们不需要进行实际的请求和响应,我们可以根据需要设置对象。

使用命名占位符的路由

Sometimes, our URLs have variables in them that we want to use in our application. In my bug tracking example, I want to have URLs like /ticket/42 to refer to the ticket - and Slim has an easy way of parsing out the “42” bit and making it available for easy use in the code. Here’s the route that does exactly that:

有时,url中有一些变量,我们希望在应用程序中使用这些变量。在我的bug跟踪示例中,我希望有像/ticket/42这样的url来引用票据——Slim有一种简单的方法来解析“42”位并使其在代码中易于使用。下面就是具体的方法:

$app->get('/ticket/{id}', function (Request $request, Response $response, $args) {
    $ticket_id = (int)$args['id'];
    $mapper = new TicketMapper($this->db);
    $ticket = $mapper->getTicketById($ticket_id);

    $response->getBody()->write(var_export($ticket, true));
    return $response;
});

Look at where the route itself is defined: we write it as /ticket/{id}. When we do this, the route will take the portion of the URL from where the {id} is declared, and it becomes available as $args['id'] inside the callback.

查看路由本身的定义位置:我们将它写为/ticket/{id}。当我们这样做时,该路由将从声明{id}的URL中提取部分,并在回调函数中以$args['id']的形式被使用。

获取GET参数

Since GET and POST send data in such different ways, then the way that we get that data from the Request object differs hugely in Slim.

由于GET和POST以如此不同的方式发送数据,所以我们从请求对象获取数据的方式在Slim中有很大的不同。

It is possible to get all the query parameters from a request by doing $request->getQueryParams() which will return an associative array. So for the URL /tickets?sort=date&order=desc we’d get an associative array like:

通过执行$request->getQueryParams()可以从请求中获得所有查询参数,该方法将返回一个关联数组。URL /tickes?sort=date&order=desc我们会得到一个关联数组,如下所示:

['sort' => 'date', 'order' => 'desc']

These can then be used (after validating of course) inside your callback.

然后可以在回调中使用它们(当然是在验证之后)。

处理POST数据

When working with incoming data, we can find this in the body. We’ve already seen how we can parse data from the URL and how to obtain the GET variables by doing $request->getQueryParams() but what about POST data? The POST request data can be found in the body of the request, and Slim has some good built in helpers to make it easier to get the information in a useful format.

当处理传入数据时,我们可以在主体中找到这些数据。我们已经了解了如何解析URL中的数据,以及如何通过执行$request->getQueryParams()获得GET变量,但是POST数据呢?POST请求数据可以在请求体中找到,Slim内置一些很好的帮助程序,可以更容易地获得格式化后的有用信息。

For data that comes from a web form, Slim will turn that into an array. My tickets example application has a form for creating new tickets that just sends two fields: “title” and “description”. Here is the first part of the route that receives that data, note that for a POST route use app->post() rather thanapp->get():

对于来自web表单的数据,Slim将其转换为数组。我的tickets示例应用程序有一个用于创建新罚单的表单,它只发送两个字段:“title”和“description”。这里是接收数据的路由的第一部分,注意对于POST路由使用app-> POST()而不是app->get():

$app->post('/ticket/new', function (Request $request, Response $response) {
    $data = $request->getParsedBody();
    $ticket_data = [];
    $ticket_data['title'] = filter_var($data['title'], FILTER_SANITIZE_STRING);
    $ticket_data['description'] = filter_var($data['description'], FILTER_SANITIZE_STRING);
    // ...

The call to $request->getParsedBody() asks Slim to look at the request and the Content-Type headers of that request, then do something smart and useful with the body. In this example it’s just a form post and so the resulting $data array looks very similar to what we’d expect from $_POST - and we can go ahead and use the filter extension to check the value is acceptable before we use it. A huge advantage of using the built in Slim methods is that we can test things by injecting different request objects - if we were to use $_POST directly, we aren’t able to do that.

对$request->getParsedBody()的调用要求Slim查看请求和该请求的内容类型头,然后对该请求的正文做一些智能的操作。在本例中,它只是一个表单post,因此生成的$data数组与我们期望的$_POST非常相似——在使用它之前,我们可以继续使用filter扩展来检查值是否可以接受。使用内置Slim方法的一个巨大优势是,我们可以通过注入不同的请求对象来测试—如果直接使用$_POST,我们就不能这样做。

What’s really neat here is that if you’re building an API or writing AJAX endpoints, for example, it’s super easy to work with data formats that arrive by POST but which aren’t a web form. As long as the Content-Type header is set correctly, Slim will parse a JSON payload into an array and you can access it exactly the same way: by using $request->getParsedBody().

这里真正简洁的地方是,如果您正在构建API或编写AJAX站点,那么处理通过POST到达但不是web表单的数据格式就非常容易。只要正确设置了Content-Type头部,Slim就会将JSON有效负载解析为一个数组,您可以使用完全相同的方法访问它:使用$request->getParsedBody()。

视图和模板

Slim doesn’t have an opinion on the views that you should use, although there are some options that are ready to plug in. Your best choices are either Twig or plain old PHP. Both options have pros and cons: if you’re already familiar with Twig then it offers lots of excellent features and functionality such as layouts - but if you’re not already using Twig, it can be a large learning curve overhead to add to a microframework project. If you’re looking for something dirt simple then the PHP views might be for you! I picked PHP for this example project, but if you’re familiar with Twig then feel free to use that; the basics are mostly the same.

Slim对您应该使用的视图没有任何意见,不过有一些选项可以插入。您最好的选择是Twig或普通的老式PHP。这两种选择都有优缺点:如果您已经熟悉Twig,那么它提供了许多优秀的特性和功能,比如布局——但是如果您还没有使用Twig,那么将其添加到微框架项目中可能需要花费大量的学习时间。如果您正在寻找一些非常简单的东西,那么PHP视图可能适合您!我选择PHP作为这个示例项目,但是如果您熟悉Twig,那么可以随意使用它;基本是一样的。

Since we’ll be using the PHP views, we’ll need to add this dependency to our project via Composer. The command looks like this (similar to the ones you’ve already seen):

由于我们将使用PHP视图,因此需要通过Composer将此依赖项添加到项目中。命令看起来是这样的(类似于您已经看到的命令):

php composer.phar require slim/php-view

In order to be able to render the view, we’ll first need to create a view and make it available to our application; we do that by adding it to the DIC. The code we need goes with the other DIC additions near the top of src/public/index.php and it looks like this:

为了能够呈现视图,我们首先需要创建一个视图并使它对我们的应用程序可用;我们把它加到DIC中。我们需要的代码与src/public/index.php顶部的其他DIC附加代码一起使用,它看起来是这样的:

$container['view'] = new \Slim\Views\PhpRenderer('../templates/');

Now we have a view element in the DIC, and by default it will look for its templates in the src/templates/ directory. We can use it to render templates in our actions - here’s the ticket list route again, this time including the call to pass data into the template and render it:

现在DIC中有一个view元素,默认情况下,它将在src/templates/目录中查找它的模板。在我们的控制器中我们使用它来渲染模板,这一次包括调用传递数据到模板和渲染它:

$app->get('/tickets', function (Request $request, Response $response) {
    $this->logger->addInfo('Ticket list');
    $mapper = new TicketMapper($this->db);
    $tickets = $mapper->getTickets();

    $response = $this->view->render($response, 'tickets.phtml', ['tickets' => $tickets]);
    return $response;
});

The only new part here is the penultimate line where we set the $response variable. Now that the view is in the DIC, we can refer to it as $this->view. Calling render() needs us to supply three arguments: the $response to use, the template file (inside the default templates directory), and any data we want to pass in. Response objects are immutable which means that the call to render() won’t update the response object; instead it will return us a new object which is why it needs to be captured like this. This is always true when you operate on the response object.

这里惟一的新部分是设置$response变量的倒数第二行。既然视图在DIC中,我们可以将其称为$this->view。调用render()需要提供三个参数:要使用的$response、模板文件(在默认模板目录中)和任何我们希望传入的数据。响应对象是不可变的,这意味着render()调用不会更新响应对象;相反,它将返回一个新对象,这就是为什么需要像这样捕获它。当您对响应对象进行操作时,就需要这样做才行。

When passing the data to templates, you can add as many elements to the array as you want to make available in the template. The keys of the array are the variables that the data will exist in once we get to the template itself.

当将数据传递给模板时,您可以向数组中添加尽可能多的元素,使其在模板中可用。数组的键将以变量的形式在模板中存在。

As an example, here’s a snippet from the template that displays the ticket list (i.e. the code from src/templates/tickets.phtml - which uses Pure.css to help cover my lack of frontend skills):

例如,下面是显示门票列表的模板代码片段(即src/templates/tickets.phtml中的代码-它使用Pure.css来帮助弥补我缺乏的前端技能):

<h1>All Tickets</h1>

<p><a href="/ticket/new">Add new ticket</a></p>

<table class="pure-table">
    <tr>
        <th>Title</th>
        <th>Component</th>
        <th>Description</th>
        <th>Actions</th>
    </tr>

<?php foreach ($tickets as $ticket): ?>

    <tr>
        <td><?=$ticket->getTitle() ?></td>
        <td><?=$ticket->getComponent() ?></td>
        <td><?=$ticket->getShortDescription() ?> ...</td>
        <td>
            <a href="<?=$router->pathFor('ticket-detail', ['id' => $ticket->getId()])?>">view</a>
        </td>
    </tr>

<?php endforeach; ?>
</table>

In this case, $tickets is actually a TicketEntity class with getters and setters, but if you passed in an array, you’d be able to access it using array rather than object notation here.

Did you notice something fun going on with $router->pathFor() right at the end of the example? Let’s talk about named routes next :)

在本例中,$tickets实际上是一个带有getter和setter的TicketEntity类,但如果传入一个数组,则可以使用数组而不是这里的对象符号访问它。

您是否注意到$router->pathFor()在示例的末尾发生了一些有趣的事情?下面让我们讨论命名路由:)

简单的URL构建与命名路由

When we create a route, we can give it a name by calling ->setName() on the route object. In this case, I am adding the name to the route that lets me view an individual ticket so that I can quickly create the right URL for a ticket by just giving the name of the route, so my code now looks something like this (just the changed bits shown here):

当我们创建一个路由时,我们可以通过调用route对象上的->setName()为它命名。在这种情况下,我们通过给查看机票详情的路由起一个名字,因此仅凭这个路由名字就可以快速创建正确的URL,所以现在我的代码是这样的(只是更改的部分所示):

$app->get('/ticket/{id}', function (Request $request, Response \$response, $args) {
    // ...
})->setName('ticket-detail');

To use this in my template, I need to make the router available in the template that’s going to want to create this URL, so I’ve amended the tickets route to pass a router through to the template by changing the render line to look like this:

要在我的模板中使用这个,我们需要用router变量来生成次URL,所以就像下面这样,我通过改变render这行代码,将路由中的router传入模板来实现:

    $response = $this->view->render($response, 'tickets.phtml', ['tickets' => $tickets, 'router' => $this->router]);

With the /tickets/{id} route having a friendly name, and the router now available in our template, this is what makes the pathFor() call in our template work. By supplying the id, this gets used as a named placeholder in the URL pattern, and the correct URL for linking to that route with those values is created. This feature is brilliant for readable template URLs and is even better if you ever need to change a URL format for any reason - no need to grep templates to see where it’s used. This approach is definitely recommended, especially for links you’ll use a lot.

由于/tickets/{id}路由有一个友好的名称,并且router对象现在可以在模板中使用,这就是使模板中的pathFor()调用起作用的原因。通过提供id,可以在URL模式中将其用作命名占位符,然后通过这些值建立链接到该路由的正确URL。这个特性对于可读的模板URL来说是非常棒的,如果您曾经因为一些原因需要更改URL格式—不需要使用grep模板来查看它的使用位置,那么这个特性甚至更好。这种方法绝对值得推荐,特别是对于您将经常使用的链接。

接下来去哪?

This article gave a walkthrough of how to get set up with a simple application of your own, which I hope will let you get quickly started, see some working examples, and build something awesome.

本文简要介绍了如何使用您自己的简单应用程序进行设置,我希望这将使您快速入门,看到一些工作示例,并构建一些很棒的东西。

From here, I’d recommend you take a look at the other parts of the project documentation for anything you need that wasn’t already covered or that you want to see an alternative example of. A great next step would be to take a look at the Middleware section - this technique is how we layer up our application and add functionality such as authentication which can be applied to multiple routes.

从这里开始,我建议您查看项目文档的其他部分,了解您需要的任何内容,这些内容还没有涵盖,或者您希望看到另一个示例。下一个重要的步骤是查看中间件部分——该技术是我们如何分层应用程序并添加诸如身份验证之类的功能,这些功能可以应用于多个路由。