【问题标题】:CodeIgniter Models vs Symfony/Doctrine ModelsCodeIgniter 模型与 Symfony/Doctrine 模型
【发布时间】:2016-12-29 15:55:44
【问题描述】:

背景:

我使用 CodeIgniter 构建了我的 Web 应用程序,因为它是唯一一个我可以轻松掌握并快速上手的框架。现在看到 symfony 令人难以置信的高级功能和 PSR 标准,我很高兴能够深入了解它。

困境

我不确定如何使用 symfony/doctrine 处理模型层。据我了解:教义为这样的数据库表生成一个实体类......

这个类包含一堆 setter/getter 函数。

目前我的心理障碍是我不明白我应该如何将功能添加到我的模型层。

要了解我的出发点,请查看我目前正在使用的典型 CodeIgniter 模型。这个处理折扣券。

<?php

/**
 * This class handles all coupon codes
 */

class Coupon_Model extends CI_Model
{
    /**
     * gets a specific coupon
     * @param string $coupon_code
     * @return obj
     */
    public function getCoupon($coupon_code)
    {
        $this->db->where('coupon_code', $coupon_code);
        $query = $this->db->get('coupons');
        return $query->row();
    }

    /**
     * gets all coupons associated with a course
     * @param int $course_id
     * @return array
     */
    public function getCourseCoupons($course_id)
    {
        $this->db->where('course_id', $course_id);
        $query = $this->db->get('coupons');
        return $query->result();
    }

    /**
     * generates a string of 10 random alphanumeric numbers
     * @return string
     */
    public function generateCouponCode()
    {
        return strtoupper(substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
    }

    /**
     * creates a new active coupon
     * @param array $data
     * @param string $coupon_code
     * @return bool
     */
    public function createCoupon($data, $coupon_code = null)
    {
        if ($coupon_code !== '') {
            $data['coupon_code'] = $coupon_code;
        } else {
            $data['coupon_code'] = $this->generateCouponCode();
        }

        return $this->db->insert('coupons', $data);
    }

    /**
     * checks if a coupon is valid
     * @param string $coupon_code
     * @param int    $course_id
     * @return bool
     */
    public function checkCoupon($coupon_code, $course_id = null)
    {
        $this->db->where('coupon_code', $coupon_code);
        $query = $this->db->get('coupons');
        $coupon = $query->row();

        // if coupon code exists
        if ($coupon === null) {
            return false;
        }

        // if coupon is for the right course
        if ($coupon->course_id !== $course_id && $course_id !== null) {
            return false;
        }

        // if coupon code has not expired
        if ($coupon->expiry_date <= $this->Time_Model->getCarbonNow()->timestamp) {
            return false;
        }
        return true;
    }

    /**
     * deletes a coupon record
     * @param int coupon_id
     * @return bool
     */
    public function deleteCoupon($coupon_id)
    {
        $this->db->where('coupon_id', $coupon_id);
        return $this->db->delete('coupons');
    }

    /**
     * applys the coupon discount
     * @param int   $price
     * @param float $discount (percentage)
     */
    public function applyDiscount($price, $discount)
    {
        $price = $price - (($discount / 100) * $price);
        return $price;
    }
}

正如您所看到的,它非常简单,如果我想添加功能,我实际上只是创建一个新功能。

要使用这个模型,我只需像这样将它加载到控制器上:

$this->model->load('coupons/Coupon_Model');
$this->Coupon_Model->getCoupon($coupon_code);

简单,完成和尘埃落定......不幸的是,我不确定如何使用 symfony/doctrine 实现这种功能。

我是否需要创建一个与实体分开的新类并向该类添加额外的功能?还是应该给实体类增加更多的功能?

以我生成优惠券代码的简单函数为例:

    /**
     * generates a string of 10 random alphanumeric numbers
     * @return string
     */
    public function generateCouponCode()
    {
        return strtoupper(substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
    }

放置此功能的最佳位置是哪里?在 AppBundle/models/coupons 下?

我显然已经从 CodeIgniter 中养成了坏习惯,并且感觉我正在以错误的方式处理这个问题。

【问题讨论】:

  • 我相信您正在寻找的是一个存储库:它们将允许您定义自定义查询。见symfony.com/doc/current/doctrine/repository.html
  • 同意 Solarbear。存储库是您的问题的关键字。
  • CodeIgniter > Symfony...
  • 我不知道怎么...这是 codeigniters 多语言实现的限制,这迫使我转向 Symfony。

标签: php codeigniter doctrine-orm symfony


【解决方案1】:

Symfony + Doctrine ORM 带有很多默认需求,通过在控制器中使用 EntityManager 来替换 CodeIgniter 模型。

例如

namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class DefaultController extends Controller
{

    /**
     * @Route("/{id}/show", name="app_show", defaults={"id" = 1})
     */
    public function showAction(Request $request, $id)
    {
        $em = $this->getDoctrine()->getManager();
        if (!$coupon = $em->find('AppBundle:Coupon', $id)) {
            throw new NotFoundException('Unknown Coupon Specified');
        }

        //see below to see how this was implemented
        $similarCoupons = $em->getRepository('AppBundle:Coupon')
                             ->filterCourse($coupon->course);

        return $this->render('AppBundle:template.twig', [
             'coupon' => $coupon,
             'similarCoupons' => $similarCoupons
        ]);
    }

    /**
     * @Route("/new", name="app_new")
     */
    public function newAction(Request $request)
    {
       //use Symfony Form Component instead
       $em = $this->getDoctrine()->getManager();
       $coupon = new \AppBundle\Entity\Coupon;
       //calls __construct to call generateCouponCode

       $coupon->setName($request->get('name'));
       $em->persist($coupon);
       $em->flush();

       return $this->redirectToRoute('app_show', ['id' => $coupon->getId()]);
    }

    //...
}

您希望在 Entity 类中指定每个实体在使用它时所具有的功能。 它变得可用而无需重新访问存储库,因为实体永远不应该知道 EntityManager。 实际上,每个实体都可以被视为自己的模型。

例如来自实体内部的$coupon-&gt;generateCouponCode();$this-&gt;generateCouponCode()

否则,您将使用 Doctrine 数据库实体的 Repository 来添加更复杂的功能。

// /src/AppBundle/Entity/Coupon.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repository="CouponRepository")
 */
class Coupon 
{
   /**
    * @var integer
    * @ORM\Column(name="id", type="integer", nullable=false)
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="IDENTITY")
    */
   private $id;

   /**
    * @var string
    * @ORM\Column(name="name", type="string", length=50)
    */
   private $name;

   /**
    * @var string
    * @ORM\Column(name="coupon_code", type="string", length=10)
    */
   private $couponCode;

   /**
    * @var Course
    * @ORM\ManyToOne(targetEntity="Course", inversedBy="coupons")
    * @ORM\JoinColumn(name="course", referencedColumnName="id")
    */
   private $course;

   //...

   public function __construct()
   {
       //optionally create code when persisting a new database entry by using LifeCycleCallbacks or a Listener instead of this line.
       $this->couponCode = $this->generateCouponCode();
   }

   //...

   /**
    * generates a string of 10 random alphanumeric numbers
    * @return string
    */
   public function generateCouponCode()
   {
       return strtoupper(substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
   }
}

然后您的自定义查询将进入您的存储库。

// /src/AppBundle/Entity/CouponRepository.php

namespace AppBundle\Entity;

use Doctrine\ORM\EntityRepository;

class CouponRepository extends EntityRepository
{
    /**
     * filters a collection of Coupons that matches the supplied Course
     * @param Course $course
     * @return array|Coupons[]
     */
    public function filterCourse(Course $course)
    {
        $qb = $this->createQueryBuilder('c');
        $expr = $qb->expr();
        $qb->where($expr->eq('c.course', ':course'))
            ->setParameter('course', $course);

        return $qb->getQuery()->getResult();
    }

}  

此外,您还可以filter collections 在您的实体中引用关联(外键)。

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;

//...
class Course
{
    //...

   /**
    * @var ArrayCollection|Coupon[]
    * @ORM\OneToMany(targetEntity="Coupon", mappedBy="course")
    */
   private $coupons;

   public function __construct()
   {
       $this->coupons  = new ArrayCollection;
   }

   /**
    * @return ArrayCollection|Coupon[]
    */
   public function getCoupons()
   {
      return $this->coupons;
   }

   /**
    * @param string $name
    * @return \Doctrine\Common\Collections\Collection|Coupon[]
    */
   public function getCouponsByName($name)
   {
      $criteria = Criteria::create();
      $expr = $criteria::expr();

      return $this->coupons->matching($criteria->where($expr->eq('name', $name)));
   }
} 

【讨论】:

  • 我倾向于避免在存储库方法上使用 get 前缀,以确保它没有被定义为实体获取器。但这只是个人喜好。
  • 我想为没有早点发表评论而道歉,这个定制的例子将极大地帮助我,我很感激这个答案。我刚刚到达我的学说教程的存储库部分,所以这很棒。