在计算机软件领域,业务逻辑(business logic)或域逻辑(domain logic)指的是“程序中用于处理现实世界中决定数据被创建、显示、存储和改变的业务规则那部分内容”。(参考完整定义)
Symfony程序中,业务逻辑是指你为自己的不与框架本身重合(比如路由或控制器)的程序所写的全部定制代码。Domain classes,Doctrine entities以及被当作服务来使用的常规PHP类,都是业务逻辑的好样板。
对于多数项目来说,你应该把所有东西存放到AppBundle中。在这里,你可以创建任意目录,用来组织内容:
- symfony2-project/
- ├─ app/
- ├─ src/
- │ └─ AppBundle/
- │ └─ Utils/
- │ └─ MyClass.php
- ├─ tests/
- ├─ var/
- ├─ vendor/
- └─ web/
在bundle外部存储类?
不过,并没有技术上的理由要求把业务逻辑放在bundle之内。如果你喜欢,你可以在src/
目录下创建你自己的命名空间,然后把代码放进去:
- 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()
方法:
- // 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)))
- );
- }
- }
然后,为这个类定义一个服务:
- # 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
:
- 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
前面小节中,YAML被用于定义服务。
Best Practice
Best Practice
使用YAML定义你的服务。
这是有争议的,而且在我们的实验中,YAML和XML的使用即便在开发者中亦存在争议,YAML略微占先。两种格式拥有相同的性能,所以使用谁最终取决于个人口味。
我们推荐YAML是因为它对初学者友好且简洁。你当然可以选择你喜欢的格式。
服务:禁止使用类名参数
你可能已经注意到,配置服务定义时我们已经不再使用参数来替代类的命名空间:
- # 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,是我们的博客样板程序要用的,可谓一个良好范例:
- symfony2-project/
- ├─ ...
- └─ src/
- └─ AppBundle/
- └─ Entity/
- ├─ Comment.php
- ├─ Post.php
- └─ User.php
Tip
如果你是高级程度,当然也可以把它们存放在src/
下你自己的命名空间内。
Doctrine映射信息
Doctrine entity,是一个原生PHP对象,你要把它存入“数据库”。Doctrine只能通过你配置在model类中的metadata(元数据)来获知这个entity。Doctrine支持四种格式的metadata:YAML、XML、PHP和annotations。
Best Practice
Best Practice
使用annotation来定义Doctrine实体的映射信息。
annotations是目前设置和查找metadata时最方便也是最灵活的方式:
- 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 ...
所有格式的性能相同,因此再次提醒选择哪种完全依个人口味而定。
Data Fixtures
由于fixtures功能并未在Symfony中默认开启,你应该执行下述命令来安装Doctrine fixtures bundle:
- $ composer require "doctrine/doctrine-fixtures-bundle"
然后在AppKernel.php
中开启这bundle,但是仅在dev
和test
环境中:
- 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:
- $ 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,这是一个命令行工具,它可以“秒完成”地修复整个代码库的编码标准。