簡體   English   中英

Symfony @ParamConverter 通過用戶關系獲取實體

[英]Symfony @ParamConverter get entity through user relationship

我有幾條以/experiment/{id}/...開頭的路線,我厭倦了重寫相同的邏輯來檢索已登錄用戶的實驗。 我想我可以重構我的代碼,但我猜@ParamConverter會是一個更好的解決方案。

我將如何重寫以下代碼以利用 Symfony 的@ParamConverter功能?

/**
 * Displays details about an Experiment entity, including stats.
 *
 * @Route("/experiment/{id}/report", requirements={"id" = "\d+"}, name="experiment_report")
 * @Method("GET")
 * @Template()
 * @Security("has_role('ROLE_USER')")
 */
public function reportAction(Request $request, $id)
{
    $em = $this->getDoctrine()->getManager();

    $experiment = $em->getRepository('AppBundle:Experiment')
        ->findOneBy(array(
            'id'   => $id,
            'user' => $this->getUser(),
        ));

    if (!$experiment) {
        throw $this->createNotFoundException('Unable to find Experiment entity.');
    }

    // ...
}

實驗實體具有如下復合主鍵

class Experiment
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     */
    protected $id;

    /**
     * @var integer
     *
     * @ORM\Column(name="user_id", type="integer")
     * @ORM\Id
     */
    protected $userId;

    /**
     * @ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="experiments", cascade={"persist"})
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     */
    protected $user;

    // ..

}

我想使用他們的用戶id和路由中的實驗id檢索登錄用戶的實驗。

您可以通過使用自定義ParamConverter來實現。 例如這樣的:

namespace AppBundle\Request\ParamConverter;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use AppBundle\Entity\Experiment;

class ExperimentConverter implements ParamConverterInterface
{
    protected $em;
    protected $user;

    public function __construct(EntityManager $em, TokenStorage $tokenStorage)
    {
        $this->em = $em;
        $this->user = $tokenStorage->getToken()->getUser();
    }

    public function apply(Request $request, ParamConverter $configuration)
    {
        $object = $this->em->getRepository(Experiment::class)->findOneBy([
            'id'   => $request->attributes->get('id'),
            'user' => $this->user
        ]);

        if (null === $object) {
            throw new NotFoundHttpException(
                sprintf('%s object not found.', $configuration->getClass())
            );
        }

        $request->attributes->set($configuration->getName(), $object);

        return true;
    }

    public function supports(ParamConverter $configuration)
    {
        return Experiment::class === $configuration->getClass();
    }
}

您需要注冊您的轉換器服務並為其添加標簽:

# app/config/config.yml
services:
    experiment_converter:
        class: AppBundle\Request\ParamConverter\ExperimentConverter
        arguments:
            - "@doctrine.orm.default_entity_manager"
            - "@security.token_storage"
        tags:
            - { name: request.param_converter, priority: 1, converter: experiment_converter }

不幸的是,您不能將當前登錄的用戶 id 注入參數轉換器,除非您實際上將其作為參數傳遞到 url。

您可以創建自己的轉換器,但我認為您最好的選擇是創建一個受保護的方法來獲取實驗。 它將像注釋一樣易於使用和維護:

protected function getCurrentUsersExperiment($experimentId)
{
    return $this->getDoctrine()->getManager()->getRepository('AppBundle:Experiment')
        ->findOneBy(array(
            'id' => $experimentId,
            'user' => $this->getUser()
        ));
}

public function reportAction(Request $request, $id)
{
    $experiment = $this->getCurrentUsersExperiment($id);

    ...
}

正如symfony 最佳實踐中指出的那樣:在適當的時候使用ParamFetcher ,但不要想太多。

雖然這個問題已經有了答案,但我想加兩分錢:

看起來您可能正在解決一個錯誤的問題:您的目標是阻止訪問其他用戶的實驗嗎? 如果是這樣,只需加載 Experiment 對象,並使用Security Voter來確定當前用戶是否可以訪問該實驗。

以您想要的方式使用參數轉換器將拋出 404 錯誤而不是 401,最終結果相同:訪問其他人的實驗被拒絕。 除非您試圖隱藏特定實驗 ID 的存在,否則您還不如返回反映實際情況的 HTTP 響應 - 401,而不是 404。我認為隱藏某些 ID 的存在可能沒有真正的好處。

如果不出意外,與自制參數轉換器相比,在 Symfony 上下文中使用安全投票器拒絕訪問聽起來更適合目的和可重用的方法。

如果您不想使用安全投票者,您也可以使用表達式:如果您的 Experiment 的存儲庫類正在自動裝配 Security 對象,您將能夠從存儲庫類中獲取當前用戶。 如果該存儲庫類具有方法findCurrentUserExperiment(int $id) ,則用法將接近:

/**
 * @Route("/experiment/{experiment}/report", requirements={"experiment" = "\d+"}, name="experiment_report")
 * @Entity("experiment", expr="repository.findCurrentUserExperiment(experiment)")
 */
public function reportAction(Request $request, ?Experiment $experiment)

我仍然認為安全選民更適合,但至少使用表達式您不必管理狹窄用例的 ParamConverter。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM