 
                    支付宝扫一扫付款
 
                    微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
在计算机软件领域,业务逻辑(business logic)或域逻辑(domain logic)指的是“程序中用于处理现实世界中决定数据被创建、显示、存储和改变的业务规则那部分内容”。(参考完整定义)
Symfony程序中,业务逻辑是指你为自己的不与框架本身重合(比如路由或控制器)的程序所写的全部定制代码。Domain classes,Doctrine entities以及被当作服务来使用的常规PHP类,都是业务逻辑的好样板。
对于多数项目来说,你应该把所有东西存放到AppBundle中。在这里,你可以创建任意目录,用来组织内容:
| 1 2 3 4 5 6 7 8 9 10 | symfony2-project/
├─ app/
├─ src/
│  └─ AppBundle/
│     └─ Utils/
│        └─ MyClass.php
├─ tests/
├─ var/
├─ vendor/
└─ web/ | 
不过,并没有技术上的理由要求把业务逻辑放在bundle之内。如果你喜欢,你可以在src/目录下创建你自己的命名空间,然后把代码放进去:
| 1 2 3 4 5 6 7 8 9 10 11 | symfony2-project/
├─ app/
├─ src/
│  ├─ Acme/
│  │   └─ Utils/
│  │      └─ MyClass.php
│  └─ AppBundle/
├─ tests/
├─ var/
├─ vendor/
└─ web/ | 
Tip
推荐的方案是使用AppBundle/目录以简化操作。如果你的技术足够先进,了解哪些东西必须在bundle之内,而哪些可以移到bundle外面,请随意。
博客程序需要一个工具,用来把文章标题(如“Hello World”)转换成slug(“hello-world”)。此处的slug将被用于文章URL的一部分。
我们先在src/AppBundle/Utils/创建一个新的Slugger类并添加下面的slugify()方法:
| 1 2 3 4 5 6 7 8 9 10 11 12 | // src/AppBundle/Utils/Slugger.php
namespace AppBundle\Utils;
 
class Slugger
{
    public function slugify($string)
    {
        return preg_replace(
            '/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string)))
        );
    }
} | 
然后,为这个类定义一个服务:
| 1 2 3 4 5 | # app/config/services.yml
services:
    # keep your service names short
    app.slugger:
        class: AppBundle\Utils\Slugger | 
Note
如果使用的是 默认的services.yml配置,类会被自动注册为服务。
传统上,一个服务的命名约定,会受到该类名称及其所在位置的影响,以避免命名冲突。因此,上面这个服务将被命名为 app.utils.slugger。但是通过使用更短的服务名,你的代码可以被更容易理解和使用。
Best Practice
Best Practice
程序级别的服务之名称,宜尽可能的短,但必须唯一,以便在所需之时你可随时从项目中找到它。
现在你可以在任何controller中使用这个自定义slugger了,比如AdminController:
| 1 2 3 4 5 6 7 8 9 10 11 | public function createAction(Request $request)
{
    // ...
 
    if ($form->isSubmitted() && $form->isValid()) {
        $slug = $this->get('app.slugger')->slugify($post->getTitle());
        $post->setSlug($slug);
 
        // ...
    }
} | 
服务也可以是 public 或 private 的。如果你使用了 默认的services.yml配置,所有的服务默认都是私有的。
Best Practice
Best Practice
Services应尽可能的 private。这可以防止通过 $container->get() 来访问服务。取而代之的是你必须使用依赖注入。
前面小节中,YAML被用于定义服务。
Best Practice
Best Practice
使用YAML定义你的服务。
这是有争议的,而且在我们的实验中,YAML和XML的使用即便在开发者中亦存在争议,YAML略微占先。两种格式拥有相同的性能,所以使用谁最终取决于个人口味。
我们推荐YAML是因为它对初学者友好且简洁。你当然可以选择你喜欢的格式。
你可能已经注意到,配置服务定义时我们已经不再使用参数来替代类的命名空间:
| 1 2 3 4 5 6 7 8 9 10 | # app/config/services.yml
 
# service definition with class namespace as parameter
# 在服务定义中把类的命名空间当作一个参数
parameters:
    slugger.class: AppBundle\Utils\Slugger
services:
    app.slugger:
        class: '%slugger.class%' | 
这种方式对于你自己的服务来说,完全是累赘和不必要的:
Best Practice
Best Practice
决不在自己的服务定义中把类名搞成参数。
这个实践一度被第三方bundle错误地采纳。当Symfony引入服务容器时,一些开发者使用了使用了这种技巧以便能够轻松覆写服务。然而实际上,仅仅是通过改变类的名字这样去覆写一个服务,在实战中实在是太罕见了,更多的情况是,新服务的构造器完全不同于旧服务。
Symfony是一个HTTP框架,它只关心为每一个HTTP请求生成一个HTTP响应。这就是为何Symfony不提供用于持久化的层(即database,外部API)的原因。对此,你可以选择自己的类库或策略来达到目的。
实践中,很多Symfony程序依赖于独立的Doctrine project,利用entity和repository来定义其数据模型。就像在业务逻辑中建议的那样,我们推荐把Doctrine entities存放在AppBundle目录下。
以下三个entities,是我们的博客样板程序要用的,可谓一个良好范例:
| 1 2 3 4 5 6 7 8 | symfony2-project/
├─ ...
└─ src/
   └─ AppBundle/
      └─ Entity/
         ├─ Comment.php
         ├─ Post.php
         └─ User.php | 
Tip
如果你是高级程度,当然也可以把它们存放在src/下你自己的命名空间内。
Doctrine entity,是一个原生PHP对象,你要把它存入“数据库”。Doctrine只能通过你配置在model类中的metadata(元数据)来获知这个entity。Doctrine支持四种格式的metadata:YAML、XML、PHP和annotations。
Best Practice
Best Practice
使用annotation来定义Doctrine实体的映射信息。
annotations是目前设置和查找metadata时最方便也是最灵活的方式:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | namespace AppBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
 
/**
 * @ORM\Entity
 */
class Post
{
    const NUM_ITEMS = 10;
 
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;
 
    /**
     * @ORM\Column(type="string")
     */
    private $title;
 
    /**
     * @ORM\Column(type="string")
     */
    private $slug;
 
    /**
     * @ORM\Column(type="text")
     */
    private $content;
 
    /**
     * @ORM\Column(type="string")
     */
    private $authorEmail;
 
    /**
     * @ORM\Column(type="datetime")
     */
    private $publishedAt;
 
    /**
     * @ORM\OneToMany(
     *      targetEntity="Comment",
     *      mappedBy="post",
     *      orphanRemoval=true
     * )
     * @ORM\OrderBy({"publishedAt" = "ASC"})
     */
    private $comments;
 
    public function __construct()
    {
        $this->publishedAt = new \DateTime();
        $this->comments = new ArrayCollection();
    }
 
    // getters and setters ... | 
所有格式的性能相同,因此再次提醒选择哪种完全依个人口味而定。
由于fixtures功能并未在Symfony中默认开启,你应该执行下述命令来安装Doctrine fixtures bundle:
| 1 | $  composer require "doctrine/doctrine-fixtures-bundle" | 
然后在AppKernel.php中开启这bundle,但是仅在dev和test环境中:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | use Symfony\Component\HttpKernel\Kernel;
 
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
        );
 
        if (in_array($this->getEnvironment(), array('dev', 'test'))) {
            // ...
            $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
        }
 
        return $bundles;
    }
 
    // ...
} | 
为了简化操作,我们推荐仅创建一个 fixture class,但如果类中的内容过长的话你也可以创建更多类。
假设你至少有一个fixtures类,而且数据库连接信息已被正确配置,通过以下命令即可加载你的fixtures:
| 1 2 3 4 5 | $  php bin/console doctrine:fixtures:load
 
Careful, database will be purged. Do you want to continue Y/N ? Y
  > purging database
  > loading AppBundle\DataFixtures\ORM\LoadFixtures | 
Symfony源代码遵循PSR-1和PSR-2代码书写规范,它们是由PHP社区制定的。你可以从the Symfony Coding standards 了解更多,甚至使用PHP-CS-Fixer,这是一个命令行工具,它可以“秒完成”地修复整个代码库的编码标准。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。