支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
在计算机软件领域,业务逻辑(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 创作共用授权。