Symfony 4 - serialization of objects with ManyToMany relation - serialization

I have two entities : Acquistion & priceHistory.
they have a Many to Many relation as below :
Aquisition.php
....
/**
* #ORM\ManyToMany(targetEntity="App\Entity\PriceHistory", inversedBy="acquisitions")
* #MaxDepth(1)
*/
private $services;
public function __construct()
{
$this->services = new ArrayCollection();
....
}
/**
* #return Collection|ServicePrice[]
*/
public function getServices(): Collection
{
return $this->services;
}
public function addService(ServicePrice $service): self
{
if (!$this->services->contains($service)) {
$this->services[] = $service;
}
return $this;
}
public function removeService(ServicePrice $service): self
{
if ($this->services->contains($service)) {
$this->services->removeElement($service);
}
return $this;
}
PriceHistory.php
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Acquisition", mappedBy="services")
*/
private $acquisitions;
public function __construct(){
$this->acquisitions = new ArrayCollection();
}
/**
* #return Collection|Acquisition[]
*/
public function getAcquisitions(): Collection
{
return $this->acquisitions;
}
public function addAcquisition(Acquisition $acquisition): self
{
if (!$this->acquisitions->contains($acquisition)) {
$this->acquisitions[] = $acquisition;
$acquisition->addService($this);
}
return $this;
}
public function removeAcquisition(Acquisition $acquisition): self
{
if ($this->acquisitions->contains($acquisition)) {
$this->acquisitions->removeElement($acquisition);
$acquisition->removeService($this);
}
return $this;
}
It works normally, schema are properly created, entities persisted, etc.
But I want to serialize and deserialize the Acquiisition object as follow :
$encoders = array(new JsonEncoder());
$objectNormalizer = new ObjectNormalizer();
$objectNormalizer->setCircularReferenceHandler(function ($object) {
return $object->getId();
});
$normalizers = array(new DateTimeNormalizer(), $objectNormalizer);
$serializer = new Serializer($normalizers, $encoders);
$tempCart = new Acquisition();
$service_prices = $this->getDoctrine()->getRepository(ServicePrice::class)->findAll()[0];
$tempCart->addService($service_prices);
$jsonContent = $serializer->serialize($tempCart, 'json');
This give me the following error:
Type error: Return value of App\Entity\PriceHistory::getAcquisitions() must be an instance of Doctrine\Common\Collections\ArrayCollection, instance of Doctrine\ORM\PersistentCollection returned
How can I fixe this ?
Thanks a lot for the help.
Pierre

You have wrong return type hint in your getAcquisitions() method, it should be Collection (which ArrayCollection implements).

You can try,
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Acquisition", mappedBy="services")
*
* #ORM\JoinTable(name="relation_table_name")
*
*/
private $acquisitions;

Related

symfony 4 string instead of UploadedFile in form collection entity with file type create

I have the following error: "Argument 1 passed to App\Service\FileUploader::upload() must be an instance of Symfony\Component\HttpFoundation\File\UploadedFile, string given"
I applied the solution found in this post but it did not change anything. Quite normal, it's not excactly the same error. Can anyone help me please ?
My goal here is to attach multiple documents to a company from the company screen.
I have been trying to solve the problem since yesterday morning. Now, it's time for me to make a break away from keyboard for 1 or 2 hours ...
Here is the code:
Document entity
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\DocumentRepository")
*/
class Document
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $fichier;
/**
* #var UploadedFile
*/
private $file;
// ...
}
Entreprise entity
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EntrepriseRepository")
*/
class Entreprise
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Document", mappedBy="entreprise", orphanRemoval=true, cascade={"persist"})
*/
private $documents;
// ...
public function __construct()
{
$this->documents = new ArrayCollection();
}
// ...
public function getDocuments()
{
return $this->documents;
}
public function addDocument(Document $document)
{
if (!$this->documents->contains($document)) {
$this->documents[] = $document;
//...
}
return $this;
}
public function removeDocument(Document $document)
{
if ($this->documents->contains($document)) {
$this->documents->removeElement($document);
}
return $this;
}
}
Entreprise Form Typenamespace App\Form\Type;
use App\Entity\Entreprise;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class EntrepriseType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('documents', CollectionType::class, [
'entry_type' => DocumentType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
])
// ...
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Entreprise::class,
]);
}
}
Entreprise Controller
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\Request;
use App\Entity\Entreprise;
use App\Form\Type\EntrepriseType;
use App\Repository\EntrepriseRepository;
use App\Service\FileUploader;
class EntrepriseController extends AbstractController
{
/**
* #Route("/entreprise/{id}", name="entreprise_detail")
* #Route("/entreprise/new", name="entreprise_new")
*/
public function index(Entreprise $entreprise = null, Request $request, ObjectManager $manager, FileUploader $fileUploader)
{
if (!$entreprise) {
$entreprise = new Entreprise();
}
$formDetail = $this->createForm(EntrepriseType::class, $entreprise);
$formDetail->handleRequest($request);
if ($formDetail->isSubmitted() && $formDetail->isValid()) {
$this->setDefault($entreprise);
// Téléchargement des nouveaux documents rattachés à l'entreprise
$documents = $entreprise->getDocuments();
foreach ($documents as $document) {
if (!$document->getId()){
/** #var Symfony\Component\HttpFoundation\File\UploadedFile $file */
$file = $document->getFile();
$document->setFichier($fileUploader->upload($file));
}
}
// Mise à jour de la base de données
$manager->persist($entreprise);
$manager->flush();
return $this->redirectToRoute('entreprise_detail', ['id'=> $entreprise->getId()]);
}
return $this->render('entreprise/index.html.twig', [
'formDetail' => $formDetail->createView(),
'entreprise' => $entreprise,
]);
}
// ...
}
PS : Sorry if my english is not good enough but if you want, you can answer in french.
I had the same issue and solved it by removing the type casting in the getFile() and setFile() in the entity. I suppose this is located in your Document entity.
Look for:
public function getFile(): ?string
{
return $this->file;
}
public function setFile(string $file): self
{
$this->file = $file;
return $this;
}
and replace it with
public function getFile()
{
return $this->file;
}
public function setFile($file): self
{
$this->file = $file;
return $this;
}
This will make sure that the file property will have an Instance of the UploadedFile class instead of invoking the __toString method of the same class (due to casting of type to string).

Symfony 2: How to use the ParamConverter with a PUT method to get or create an entity object

I need to implement an API with a PUT method and I would like to use the ParamConverter in my Controller to find an existing entity object, or if the entity object doesn't exist, to create a new one.
However the standard Symfony ParamConverter returns an exception if it doesn't find the entity object in the repository.
Do you have any ideas to do that in a nice and clean way ? Thx.
Here is an example of what I would like to do (I use FOS REST Bundle to handle the PUT request):
/**
* #param Request $request
* #return View
*
* #ParamConverter("video")
*
*/
public function putVideosAction(Request $request, Video $video)
{
try {
return $this->getHandlerVideos()->put($video, $request->request->all());
} catch (InvalidFormException $e) {
return $e->getForm();
}
}
Here's a solution. Please give me your thoughts on it.
In your controller, I would do that:
/**
* #param Request $request
* #return View
*
* #Rest\Put()
* #Rest\View()
*
* #ParamConverter("video", converter="app_get_or_create_entity_converter", options={"repository_method" = "findOneById"})
*/
public function putVideosAction(Request $request, Video $video)
{
try {
$video = $this->getHandlerVideos()->put($video, $request->request->all());
return $video;
} catch (InvalidFormException $e) {
return $e->getForm();
}
}
I would write a dynamic param converter that way:
class GetOrCreateEntityConverter implements \Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface
{
/**
* #var EntityManagerInterface
*/
protected $entityManager;
/**
* #var ManagerRegistry $registry Manager registry
*/
private $registry;
/**
* #param ManagerRegistry $registry
* #param EntityManagerInterface $entityManager
*/
public function __construct(ManagerRegistry $registry, EntityManagerInterface $entityManager)
{
$this->registry = $registry;
$this->entityManager = $entityManager;
}
public function supports(ParamConverter $configuration)
{
if ('app_get_or_create_entity_converter' !== $configuration->getConverter()) {
return false;
}
return true;
}
/**
* {#inheritdoc}
*
* Applies converting
*
* #throws \InvalidArgumentException When route attributes are missing
* #throws NotFoundHttpException When object not found
*/
public function apply(Request $request, ParamConverter $configuration)
{
$name = $configuration->getName();
$options = $configuration->getOptions();
$class = $configuration->getClass();
$repository = $this->entityManager->getRepository($class);
$repositoryMethod = $options['repository_method'];
if (!is_callable([$repository, $repositoryMethod])) {
throw new \BadMethodCallException($repositoryMethod . ' function does not exist.', 405);
}
$entity = $repository->$repositoryMethod($id);
if (null === $entity) {
$entity = new $class;
}
$request->attributes->set($name, $entity);
}
}
If you ask why I return a form in the catch, please go and see https://github.com/liuggio/symfony2-rest-api-the-best-2013-way/blob/master/src/Acme/BlogBundle/Controller/PageController.php
You'll have to create your own custom paramConverter.
First, here is what you want to write in your controller:
/**
* #ParamConverter("video", class = "MyBundle:Video", converter = "my_param_converter")
* #param Request $request
* #param Video $video
* #return \Symfony\Component\HttpFoundation\Response
*/
public function putVideosAction(Request $request, Video $video)
{
// your code..
}
Now let's write the my_param_converter!
use Doctrine\Common\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
// ...
class MyParamConverter implements ParamConverterInterface
{
private $registry;
/**
* #param ManagerRegistry $registry
*/
public function __construct(ManagerRegistry $registry = null)
{
$this->registry = $registry;
}
/**
* Check if object supported by our paramConverter
*
* #param ParamConverter $configuration
*/
public function supports(ParamConverter $configuration)
{
// In this case we can do nothing and just return
if (null === $this->registry || !count($this->registry->getManagers())) {
return false;
}
// Check if the class is set in configuration
if(null === $configuration->getClass()) {
return false;
}
// Get actual entity manager for class
$em = $this->registry->getManagerForClass($configuration->getClass());
// Check what you need to check...
return true;
}
public function apply(Request $request, ParamConverter $configuration)
{
$videoId = $request->attributes->get('video');
if(null === videoId) {
throw new \InvalidArgumentException('Route attribute is missing');
}
// Get actual entity manager for class
$em = $this->registry->getManagerForClass($configuration->getClass());
$repository = $em->getRepository($configuration->getClass());
// Try to find the video
$video = $$repository->findOneById($videoId);
if($video === null || !($video instanceof Video)) {
// Here you can create your new video object
}
// Map video to the route's parameter
$request->attributes->set($configuration->getName(), $video);
}
}
Once your new paramConverter wrote, declare it as a service:
services:
app.param_converter.my_param_converter:
class: YourBundle\Path\To\MyParamConverter
tags:
- { name: request.param_converter, converter: my_param_converter }
arguments:
- #?doctrine
Here you're done!
My answer is largely inspired by this article and hope is helpful.

authentication class wont let me save in the user model

I am trying to save ( $user->save();) the access token to my user table for later use.
but for some reason its not saving to my table.
here is my controller
<?php
namespace api\controllers;
use Yii;
use yii\rest\ActiveController;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\QueryParamAuth;
use common\models\Authentication;
class AuthenticationController extends ActiveController
{
public $modelClass = 'common\models\Authentication';
public function actionGrantAuthentication()
{
$access_token = null;
$message = array();
if (Yii::$app->request->post()) {
if (!Yii::$app->user->identity || !Yii::$app->user->identity || Yii::$app->user->isGuest) {
$username = isset(Yii::$app->request->post()['username']) && Yii::$app->request->post()['username'] ? Yii::$app->request->post()['username'] : null;
$password = isset(Yii::$app->request->post()['password']) && Yii::$app->request->post()['password'] ? Yii::$app->request->post()['password'] : null;
if ($username && $password) {
$user = Authentication::findOne(['username' => $username, 'password_hash' => $password]);
if ($user) {
//log this user in if the identity is verified
Yii::$app->user->login($user);
$user->access_token = Yii::$app->security->generateRandomString();
$user->auth_key = "djfskdjh";
$user->save();
$message[] = $user;
} else {
//add error message
$message[] = "Wrong login credentials";
}
} else {
$message[] = "Must provide username and password";
}
} else {
$message[] = "you are already logged in";
}
}
return $message;
}
}
here is my authentication model
<?php
namespace common\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
/**
* User model
*
* #property integer $id
* #property string $username
* #property string $password_hash
* #property string $password_reset_token
* #property string $email
* #property string $auth_key
* #property integer $status
* #property integer $created_at
* #property integer $updated_at
* #property string $password write-only password
*/
class Authentication extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'user';
}
/**
* #inheritdoc
*/
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
/**
* #inheritdoc
*/
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
];
}
/**
* #inheritdoc
*/
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null)
{
//throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
return static::findOne(['access_token' => $token]);
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
return static::findOne(['username' => $username]);
}
/**
* Finds user by password reset token
*
* #param string $token password reset token
* #return static|null
*/
public static function findByPasswordResetToken($token)
{
if (!static::isPasswordResetTokenValid($token)) {
return null;
}
return static::findOne([
'password_reset_token' => $token,
'status' => self::STATUS_ACTIVE,
]);
}
/**
* Finds out if password reset token is valid
*
* #param string $token password reset token
* #return boolean
*/
public static function isPasswordResetTokenValid($token)
{
if (empty($token)) {
return false;
}
$timestamp = (int) substr($token, strrpos($token, '_') + 1);
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
return $timestamp + $expire >= time();
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->getPrimaryKey();
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->auth_key;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return Yii::$app->security->validatePassword($password, $this->password_hash);
}
/**
* Generates password hash from password and sets it to the model
*
* #param string $password
*/
public function setPassword($password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
/**
* Generates "remember me" authentication key
*/
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
/**
* Generates new password reset token
*/
public function generatePasswordResetToken()
{
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
}
/**
* Removes password reset token
*/
public function removePasswordResetToken()
{
$this->password_reset_token = null;
}
}
here is my table
Tipically when a model is not saved in DB this is related to validation problem ..
You can try using for debugging
$user->save(false); //this disable the validation rules for user
Then is the user i saved in db you should looking for the validation rule that fail
you can look at this guide for some suggetion for getting the validation errors
http://www.yiiframework.com/doc-2.0/guide-input-validation.html
if ($user->validate()) {
// all inputs are valid
$user->save()
} else {
// validation failed: $errors is an array containing error messages
$errors = $model->errors;
var_dump($errors);
}

Create sql query in sonata admin

I use sonata admin in my project. i created two admin classes exam and student. i want to create an action in exam class that shows all students that should pass the exam after creating a new exam, this is the sql query that i want:
"Select * from student,exam where student.codeAdministration=exam.codeAdministration"
examAdmin.php:
<?php
namespace Exam\ExamBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
class ExamAdmin extends Admin
{
////
protected function configureFormFields(FormMapper $formMapper)
{
//////
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
////
}
protected function configureListFields(ListMapper $listMapper)
{
/////
}
protected function configureShowField(ShowMapper $showMapper)
{
//////
}
}
i tried this query without putting the condition of WHERE but it does not work:
protected function configureListFields(ListMapper $listMapper)
{
$query = $this->modelManager->getEntityManager()->createQuery('SELECT s FROM Exam\ExamBundle\Entity\student s');
$listMapper
->add('student', 'sonata_type_model', array('required' => true, 'query' => $query))
}
How can i create this action with this query??
exam.php:
<?php
namespace Exam\ExamBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Exam
{
/**
* #var integer
*/
private $idExam
////////
/**
* #var \Examens\ExamensBundle\Entity\Administration
*/
private $codeAdministration;
//////
public function getIdExam()
{
return $this->idExam;
}
///////
public function setCodeAdministration(\Exam\ExamBundle\Entity\Administration $codeAdministration = null)
{
$this->codeAdministration = $codeAdministration;
return $this;
}
/**
* Get codeAdministration
*
* #return \Exam\ExamBundle\Entity\Administration
*/
public function getCodeAdministration()
{
return $this->codeAdministration;
}
///////
}

Zend Framework 2 - Service method require as parameter InputFilter

I have a bit OOD question.
I have service:
namespace Front\Service\Course;
use Front\ORM\EntityManagerAwareInterface;
use Zend\Http\Request;
use Zend\InputFilter\InputFilter;
use Front\InputFilter\Course\CreateFilter;
class Create implements EntityManagerAwareInterface
{
/**
* #var \Doctrine\Orm\EntityManager
*/
protected $entityManager = null;
public function create(CreateFilter $createFilter)
{
if (!$createFilter->isValid()) return false;
/* #var $courseRepository \Front\Repositories\CourseRepository */
$courseRepository = $this->getEntityManager()->getRepository('Front\Entities\Course');
$course = $courseRepository->findByName($createFilter->getCourse());
}
/* (non-PHPdoc)
* #see \Front\ORM\EntityManagerAwareInterface::getEntityManager()
*/
public function getEntityManager()
{
return $this->entityManager;
}
/* (non-PHPdoc)
* #see \Front\ORM\EntityManagerAwareInterface::setEntityManager()
*/
public function setEntityManager(\Doctrine\ORM\EntityManager $entityManager)
{
$this->entityManager = $entityManager;
return $this;
}
}
And controller :
class CreateController extends \Zend\Mvc\Controller\AbstractController
{
public function onDispatch(MvcEvent $e)
{
$jsonModel = new JsonModel();
/* #var $courseCreateService \Front\Service\Course\Create */
$courseCreateService = $this->getServiceLocator()->get('Front\Service\Course\Create');
$courseCreateFilter = new CreateFilter();
$courseCreateFilter->setData($this->params()->fromPost());
if (!$courseCreateFilter->isValid()) {
$jsonModel->setVariable('status', 0);
$jsonModel->setVariable('message', $courseCreateFilter->getMessages());
return;
}
$courseCreateService->create($courseCreateFilter);
}
}
By service method declaration :
public function create(CreateFilter $createFilter)
i force user of the Service to use CreateFilter container which derived from Zend/InputFilter every time when he want to create new Course.
My question is: Might it be better when i will send to the service layer not the Typed object but simple value?
On example in my case it is might looks like:
public function create($courseName)
My CreateFilter looks like:
class CreateFilter extends InputFilter
{
public function __construct()
{
$input = new Input('name');
$validatorChain = new ValidatorChain();
$validatorChain->addValidator(new StringLength(array('max'=>60)))
->addValidator(new NotEmpty());
$input->setRequired(true)->setValidatorChain($validatorChain);
$this->add($input);
}
/**
* #return string | null
*/
public function getCourse()
{
return $this->getValue('name');
}
}
If you provide a concrete class name as you're doing now, you're forever tied to a concrete implementation of the class or one derived from it. If you decide later that you want to use a different class entirely, you have to refactor your service class code, whereas with an interface, you only need to implement it in your new class and your service will continue to work without any changes.
Without any interface at all, your service class would have to do extra checks to first see if it's an object and then if it implements the method you're expecting before it can even begin doing its job. By requiring an interface you remove the uncertainty, and negate the need for checks.
By providing an interface you create a contract between your methods and the classes they're expecting as arguments without restricting which classes may enter into the contract. All in all, contract by interface is preferable to contract by class name, but both are preferable to no contract at all.
I usually bind my entities to my form, so they are populated with the data from the form. This way, you inject the entity to your service and imho that's much cleaner. The service should not be aware of how you got your data.
My "admin" controller for an entity Bar usually is injected with three objects: the repository (to query objects), the service (to persist/update/delete objects) and the form (to modify objects for the user). A standard controller is then very CRUD based and only pushes entities to the service layer:
<?php
namespace Foo\Controller;
use Foo\Repository\Bar as Repository;
use Foo\Form\Bar as Form;
use Foo\Service\Bar as Service;
use Foo\Entity\Bar as Entity;
use Foo\Options\ModuleOptions;
use Zend\Mvc\Controller\AbstractActionController;
class BarController extends AbstractActionController
{
/**
* #var Repository
*/
protected $repository;
/**
* #var Service
*/
protected $service;
/**
* #var Form
*/
protected $form;
/**
* #var ModuleOptions
*/
protected $options;
public function __construct(Repository $repository, Service $service, Form $form, ModuleOptions $options = null)
{
$this->repository = $repository;
$this->service = $service;
$this->form = $form;
if (null !== $options) {
$this->options = $options;
}
}
public function getService()
{
return $this->service;
}
public function getRepository()
{
return $this->repository;
}
public function getForm()
{
return $this->form;
}
public function getOptions()
{
if (null === $this->options) {
$this->options = new ModuleOptions;
}
return $this->options;
}
public function indexAction()
{
$bars = $this->getRepository()->findAll();
return array(
'bars' => $bars,
);
}
public function viewAction()
{
$bar = $this->getBar();
return array(
'bar' => $bar,
);
}
public function createAction()
{
$bar = $this->getBar(true);
$form = $this->getForm();
$form->bind($bar);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
$form->setData($data);
if ($form->isValid()) {
// Bar is populated with form data
$this->getService()->create($bar);
return $this->redirect()->toRoute('bar/view', array(
'bar' => $bar->getId(),
));
}
}
return array(
'form' => $form,
);
}
public function updateAction()
{
$bar = $this->getBar();
$form = $this->getForm();
$form->bind($bar);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
$form->setData($data);
if ($form->isValid()) {
$this->getService()->update($bar);
return $this->redirect()->toRoute('bar/view', array(
'bar' => $bar->getId(),
));
}
}
return array(
'bar' => $bar,
'form' => $form,
);
}
public function deleteAction()
{
if (!$this->getRequest()->isPost()) {
$this->getRequest()->setStatusCode(404);
return;
}
$bar = $this->getBar();
$this->getService()->delete($bar);
return $this->redirect()->toRoute('bar');
}
protected function getBar($create = false)
{
if (true === $create) {
$bar = new Entity;
return $bar;
}
$id = $this->params('bar');
$bar = $this->getRepository()->find($id);
if (null === $bar) {
throw new Exception\BarNotFoundException(sprintf(
'Bar with id "%s" not found', $id
));
}
return $bar;
}
}
I made a gist file on Github with this full code (it's better readable) and the service. The service relies on the interface, so you can even swap out the entity object by another one having the same interface.
Check the full thing out here: https://gist.github.com/juriansluiman/5472787
Thanks all for answering, owing to answers and analyzing, i have reached conclusion which most applicable for my situation. I agree that Service in my case should not wait concrete object, it is should wait an abstraction with getCourse method.
And i completely agree with "Crisp" answer:
All in all, contract by interface is preferable to contract by class name, but both are preferable to no contract at all.
So i need to extract Interface with one method
getCourse
or
getName
, and remove
if (!$createFilter->isValid()) return false;
so Interface:
interface CourseInterface
{
/**
* #return String
**/
public function getName();
}
and Service:
class Create implements EntityManagerAwareInterface
{
/**
* #var \Doctrine\Orm\EntityManager
*/
protected $entityManager = null;
/**
* #param CourseInterface $course
* #param UserInterface $creator
*/
public function create(CourseInterface $course)
{
$courseEntity = new Course();
$courseEntity->setName($course->getName());
$this->entityManager->persist($courseEntity);
$this->entityManager->flush();
.....
Thanks all.