26/05/2014 - DOCTRINE, SYMFONY, TWIG
Aşağıdaki örnek one-to-many relationship (one Country to many League) üzerine kuruludur, ayrıca webform kullanıyor ve her ne kadar tavsiye edilmesede, tüm işlemler controller içinde yapılıyor. Bu sadece size temel fikir vermek içindir. Gerekli olan çoğu işlemleri, her hangi bir kişisel servis classa taşıyabilirsiniz. Uygulama geliştirmelerde "İnce controller, şişman service" tavsiye edilen yöntemdir.
$form->isSubmitted()
ve $form->isValid()
kontrolleri PUT ve PATCH methodları için doğru çalışmazlar çünkü, onlar direkt olarak handleRequest()
fonksiyonuna bağlı değillerdir. Bu problemi önlemek için, methodları setMethod($options['method'])
fonksiyonunu kullanarak form type classı içinde atayın.createQueryBuilder()
methodunu kullanarak sorgulama yapın çünkü, doctrine lazy load daha sonradan extra bir veya birden fazla sorgu daha çalıştırabilir. Tavsiye edilen leftJoin()
ilgili entitynin de seçilmesidir.namespace Football\FrontendBundle\Entity;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass="Football\FrontendBundle\Repository\CountryRepository")
* @ORM\Table(name="country")
* @UniqueEntity(fields="code", message="Code is already taken.")
* @UniqueEntity(fields="name", message="Name is already taken.")
* @ORM\HasLifecycleCallbacks
*/
class Country
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(type="smallint")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=100, unique=true)
* @Assert\NotBlank(message="Name is required.")
* @Assert\Length(
* max=100,
* exactMessage="Name cannot be longer than {{ limit }} characters."
* )
*/
protected $name;
/**
* @var object
*
* @ORM\OneToMany(
* targetEntity="League",
* mappedBy="country",
* cascade={"persist", "remove"}
* )
*/
protected $league;
/**
* Constructor
*/
public function __construct()
{
$this->league = new ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Country
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Add league
*
* @param League $league
* @return Country
*/
public function addLeague(League $league)
{
$this->league[] = $league;
return $this;
}
/**
* Remove league
*
* @param League $league
*/
public function removeLeague(League $league)
{
$this->league->removeElement($league);
}
/**
* Get league
*
* @return Collection
*/
public function getLeague()
{
return $this->league;
}
}
namespace Football\FrontendBundle\Entity;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass="Football\FrontendBundle\Repository\LeagueRepository")
* @ORM\Table(
* name="league",
* uniqueConstraints={@ORM\UniqueConstraint(columns={"name", "country_id"})}
* )
* @UniqueEntity(
* fields={"name","country"},
* message="League for given country already exists in database."
* )
* @ORM\HasLifecycleCallbacks
*/
class League
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(type="smallint")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=100)
* @Assert\NotBlank(message="Name is required.")
* @Assert\Length(
* max=100,
* exactMessage="Name cannot be longer than {{ limit }} characters."
* )
*/
protected $name;
/**
* @var datetime
*
* @ORM\Column(name="created_at", type="datetime", nullable=false)
*/
protected $createdAt;
/**
* @var datetime
*
* @ORM\Column(name="updated_at", type="datetime", nullable=true)
*/
protected $updatedAt;
/**
* @var object
*
* @Assert\NotBlank(message="Country is required.")
* @ORM\ManyToOne(
* targetEntity="Country",
* inversedBy="league"
* )
* @ORM\JoinColumn(
* name="country_id",
* referencedColumnName="id",
* onDelete="CASCADE",
* nullable=false
* )
*/
protected $country;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Country
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @ORM\PrePersist
*/
public function onPrePersist()
{
$this->createdAt = new DateTime('now');
}
/**
* @ORM\PreUpdate
*/
public function onPreUpdate()
{
$this->updatedAt = new DateTime('now');
}
/**
* Get createdAt
*
* @return DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Get updatedAt
*
* @return DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set country
*
* @param Country $country
* @return League
*/
public function setCountry(Country $country)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* @return Country
*/
public function getCountry()
{
return $this->country;
}
}
Country alanı embedded entity olarak geçiyor.
namespace Football\FrontendBundle\Form\Type;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LeagueType extends AbstractType
{
private $country;
public function __construct()
{
$this->country = [
'error_bubbling' => true,
'class' => 'FootballFrontendBundle:Country',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'required' => false,
'empty_value' => '',
'query_builder' => function (EntityRepository $repo)
{
return $repo->createQueryBuilder('c')->orderBy('c.name', 'ASC');
}
];
}
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
// Update page should not have empty option for country because it is FK to country entity.
if ($options['method'] == 'PATCH') {
$this->country['empty_value'] = false;
}
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add('name', 'text', ['required' => false, 'error_bubbling' => true])
->add('country', 'entity', $this->country);
}
public function getName()
{
return 'league';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'Football\FrontendBundle\Entity\League']
);
}
}
Her ne kadar sistemde önceden belirlenmiş olan find
methodunu kullanabilsekte, bunu neden yapmadığımızı yukaridaki gerçekler bölünü okuyarak anlayabilirsiz.
namespace Football\FrontendBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
class LeagueRepository extends EntityRepository
{
/**
* Built-in findAll() would run additional queries for Country because
* we are interacting with Country info as parent.
*/
public function findAll()
{
return
$this
->createQueryBuilder('l')
->select('l.id, l.name, l.createdAt, l.updatedAt, c.name as country')
->leftJoin('l.country', 'c')
->getQuery()
->getResult();
}
/**
* Built-in findAll() would run additional queries for Country because
* we are interacting with Country info as parent.
*/
public function findOneById($id, $hydrator = Query::HYDRATE_OBJECT)
{
return
$this
->createQueryBuilder('l')
->select('l.id, l.name, l.createdAt, l.updatedAt, c.name as country')
->leftJoin('l.country', 'c')
->where('l.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult($hydrator);
}
/**
* Removing an entity must return whole objects graph hence reason we must
* select l and c.
*/
public function findOneByIdAsObject($id)
{
return
$this
->createQueryBuilder('l')
->select('l, c')
->leftJoin('l.country', 'c')
->where('l.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
}
namespace Football\FrontendBundle\Controller;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query;
use Exception;
use Football\FrontendBundle\Entity\League;
use Football\FrontendBundle\Exception\LeagueException;
use Football\FrontendBundle\Form\Type\LeagueType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("/league")
*/
class LeagueController extends Controller
{
const ROUTER_PREFIX = 'football_frontend_league_';
/**
* Landing page.
*
* @Route("")
* @Method({"GET"})
*
* @return Response
*/
public function listAction()
{
$repo = $this->getDoctrine()->getRepository('FootballFrontendBundle:League');
return $this->getFormView('list', ['leagueArray' => $repo->findAll()]);
}
/**
* Create page.
*
* @Route("/create")
* @Method({"GET"})
*
* @return Response
*/
public function createAction()
{
$form = $this->getForm(
new League(),
'POST',
$this->generateUrl(self::ROUTER_PREFIX . 'create')
);
return $this->getFormView('create', ['form' => $form->createView()]);
}
/**
* Creates processing.
*
* @param Request $request
*
* @Route("/create")
* @Method({"POST"})
*
* @return RedirectResponse|Response
* @throws LeagueException
*/
public function createProcessAction(Request $request)
{
if ($request->getMethod() != 'POST') {
throw new LeagueException('League create: only POST method is allowed.');
}
$form = $this->getForm(
new League(),
'POST',
$this->generateUrl(self::ROUTER_PREFIX . 'create')
);
$form->handleRequest($request);
if (!$form->isSubmitted()) {
throw new LeagueException('League create: form is not submitted.');
}
if ($form->isValid() !== true) {
return $this->getFormView('create', ['form' => $form->createView()]);
}
try {
$data = $form->getData();
$league = new League();
$league->setName($data->getName());
$league->setCountry($data->getCountry());
$em = $this->getDoctrine()->getManager();
$em->persist($league);
$em->flush();
} catch (DBALException $e) {
$message = sprintf('DBALException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (ORMException $e) {
$message = sprintf('ORMException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (Exception $e) {
$message = sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage());
}
if (isset($message)) {
throw new LeagueException($message);
}
return $this->redirect($this->generateUrl(self::ROUTER_PREFIX . 'list'));
}
/**
* Update page.
*
* @Route("/update/{id}", requirements={"id"="\d+"})
* @Method({"GET"})
*/
public function updateAction($id)
{
$repo = $this->getDoctrine()->getRepository('FootballFrontendBundle:League');
$league = $repo->findOneByIdAsObject($id);
if (!$league instanceof League) {
throw new LeagueException(sprintf('League update: league [%s] cannot be found.', $id));
}
$form = $this->getForm(
$league,
'PATCH',
$this->generateUrl(self::ROUTER_PREFIX . 'updateprocess', ['id' => $id])
);
return $this->getFormView(
'update',
[
'form' => $form->createView(),
'id' => $league->getId()
]
);
}
/**
* Update processing.
*
* @param Request $request
* @param int $id
*
* @Route("/update/{id}", requirements={"id"="\d+"})
* @Method({"PATCH"})
*
* @return RedirectResponse|Response
* @throws LeagueException
*/
public function updateProcessAction(Request $request, $id)
{
if ($request->getMethod() != 'PATCH') {
throw new LeagueException('League update: only PATCH method is allowed.');
}
$repo = $this->getDoctrine()->getRepository('FootballFrontendBundle:League');
$league = $repo->findOneByIdAsObject($id);
if (!$league instanceof League) {
throw new LeagueException(sprintf('League update: league [%s] cannot be found.', $id));
}
$form = $this->getForm(
$league,
'PATCH',
$this->generateUrl(self::ROUTER_PREFIX . 'updateprocess', ['id' => $id])
);
$form->handleRequest($request);
if (!$form->isSubmitted()) {
throw new LeagueException('League update: form is not submitted.');
}
if ($form->isValid() !== true) {
return $this->getFormView('update', ['form' => $form->createView()]);
}
try {
$data = $form->getData();
$league->setName($data->getName());
$league->setCountry($data->getCountry());
$em = $this->getDoctrine()->getManager();
$em->flush();
} catch (DBALException $e) {
$message = sprintf('DBALException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (ORMException $e) {
$message = sprintf('ORMException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (Exception $e) {
$message = sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage());
}
if (isset($message)) {
throw new LeagueException($message);
}
return $this->redirect($this->generateUrl(self::ROUTER_PREFIX . 'list'));
}
/**
* Fetches country.
*
* @param int $id
*
* @Route("/{id}", requirements={"id"="\d+"})
* @Method({"GET"})
*
* @return RedirectResponse|Response
* @throws LeagueException
*/
public function readAction($id)
{
try {
$repo = $this->getDoctrine()->getRepository('FootballFrontendBundle:League');
$league = $repo->findOneById($id, Query::HYDRATE_SCALAR);
if (!count($league)) {
throw new LeagueException(sprintf('League read: league [%s] cannot be found.', $id));
}
return $this->getFormView('read', ['league' => $league]);
} catch (DBALException $e) {
$message = sprintf('DBALException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (ORMException $e) {
$message = sprintf('ORMException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (Exception $e) {
$message = sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage());
}
if (isset($message)) {
throw new LeagueException($message);
}
return $this->redirect($this->generateUrl(self::ROUTER_PREFIX . 'list'));
}
/**
* Deletes country.
*
* @param int $id
*
* @Route("/delete/{id}", requirements={"id"="\d+"})
* @Method({"GET"})
*
* @return RedirectResponse|Response
* @throws LeagueException
*/
public function deleteAction($id)
{
try {
$em = $this->getDoctrine()->getEntityManager();
$repo = $em->getRepository('FootballFrontendBundle:League');
$league = $repo->findOneByIdAsObject($id);
if (!$league instanceof League) {
throw new LeagueException(sprintf('League read: league [%s] cannot be found.', $id));
}
$em->remove($league);
$em->flush();
} catch (DBALException $e) {
$message = sprintf('DBALException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (ORMException $e) {
$message = sprintf('ORMException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (Exception $e) {
$message = sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage());
}
if (isset($message)) {
throw new LeagueException($message);
}
return $this->redirect($this->generateUrl(self::ROUTER_PREFIX . 'list'));
}
/**
* Creates form object.
*
* @param League $league
* @param string $method
* @param string $action
*
* @return Form
*/
private function getForm(League $league, $method, $action)
{
return $this->createForm(
new LeagueType(),
$league,
[
'method' => $method,
'action' => $action
]
);
}
/**
* Creates webform.
*
* @param string $template
* @param array $parameters
*
* @return Response
*/
private function getFormView($template, array $parameters = [])
{
return $this->render(
sprintf('FootballFrontendBundle:League:%s.html.twig', $template),
$parameters
);
}
}
inanzzz-MBP:sport inanzzz$ php app/console router:debug
[router] Current routes
Name Method Scheme Host Path
_wdt ANY ANY ANY /_wdt/{token}
_profiler_home ANY ANY ANY /_profiler/
_profiler_search ANY ANY ANY /_profiler/search
_profiler_search_bar ANY ANY ANY /_profiler/search_bar
_profiler_purge ANY ANY ANY /_profiler/purge
_profiler_info ANY ANY ANY /_profiler/info/{about}
_profiler_phpinfo ANY ANY ANY /_profiler/phpinfo
_profiler_search_results ANY ANY ANY /_profiler/{token}/search/results
_profiler ANY ANY ANY /_profiler/{token}
_profiler_router ANY ANY ANY /_profiler/{token}/router
_profiler_exception ANY ANY ANY /_profiler/{token}/exception
_profiler_exception_css ANY ANY ANY /_profiler/{token}/exception.css
_configurator_home ANY ANY ANY /_configurator/
_configurator_step ANY ANY ANY /_configurator/step/{index}
_configurator_final ANY ANY ANY /_configurator/final
_twig_error_test ANY ANY ANY /_error/{code}.{_format}
football_frontend_default_index GET ANY ANY /
football_frontend_league_list GET ANY ANY /league
football_frontend_league_create GET ANY ANY /league/create
football_frontend_league_createprocess POST ANY ANY /league/create
football_frontend_league_update GET ANY ANY /league/update/{id}
football_frontend_league_updateprocess PATCH ANY ANY /league/update/{id}
football_frontend_league_read GET ANY ANY /league/{id}
football_frontend_league_delete GET ANY ANY /league/delete/{id}
{% extends 'FootballFrontendBundle:League:index.html.twig' %}
{% block body %}
{% spaceless %}
{{ parent() }}
LEAGUE - List
<hr />
{% if leagueArray is defined and leagueArray|length != 0 %}
<table border="1px">
<tr>
<td>#</td>
<td>ID</td>
<td>NAME</td>
<td>CREATED</td>
<td>UPDATED</td>
<td>COUNTRY</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
{% for league in leagueArray %}
<tr>
<td class="head">{{ loop.index }}</td>
<td>{{ league.id }}</td>
<td>{{ league.name }}</td>
<td>{{ league.createdAt|date('d/m/Y H:i:s') }}</td>
<td>{{ league.updatedAt is not null ? league.updatedAt|date('d/m/Y H:i:s') : '' }}</td>
<td>{{ league.country }}</td>
<td><a href="{{ path('football_frontend_league_read', {'id':league.id}) }}">view</a></td>
<td><a href="{{ path('football_frontend_league_delete', {'id':league.id}) }}">delete</a></td>
<td><a href="{{ path('football_frontend_league_update', {'id':league.id}) }}">update</a></td>
</tr>
{% endfor %}
</table>
{% else %}
No league found in database!
{% endif %}
{% endspaceless %}
{% endblock %}
{% extends 'FootballFrontendBundle:League:index.html.twig' %}
{% block body %}
{% spaceless %}
{{ parent() }}
LEAGUE - Read
<hr />
<table border="1px">
<tr>
<td>ID</td>
<td>NAME</td>
<td>CREATED</td>
<td>UPDATED</td>
<td>* COUNTRY</td>
</tr>
<tr>
<td>{{ league.id }}</td>
<td>{{ league.name }}</td>
<td>{{ league.createdAt|date('d/m/Y H:i:s') }}</td>
<td>{{ league.updatedAt is not null ? league.updatedAt|date('d/m/Y H:i:s') : '' }}</td>
<td>{{ league.country }}</td>
</tr>
</table>
{% endspaceless %}
{% endblock %}
{% extends 'FootballFrontendBundle:League:index.html.twig' %}
{% block body %}
{% spaceless %}
{{ parent() }}
LEAGUE - Create
<hr />
{{ form_start(form) }}
{% if form_errors(form) != '' %}
{{ form_errors(form) }}
{% endif %}
<p>NAME: {{ form_widget(form.name) }}</p>
<p>COUNTRY: {{ form_widget(form.country) }}</p>
<p><button name="button">Submit</button></p>
{{ form_end(form) }}
{% endspaceless %}
{% endblock %}
{% extends 'FootballFrontendBundle:League:index.html.twig' %}
{% block body %}
{% spaceless %}
{{ parent() }}
LEAGUE - Update
<hr />
{{ form_start(form) }}
{% if form_errors(form) != '' %}
{{ form_errors(form) }}
{% endif %}
<p>NAME: {{ form_widget(form.name) }}</p>
<p>COUNTRY: {{ form_widget(form.country) }}</p>
<p><button name="button">Submit</button></p>
{{ form_end(form) }}
{% endspaceless %}
{% endblock %}