Symfony 4 + Doctrine: organize entities in subfolders - php

I'm trying to make a website with Symfony 4 and Doctrine. I'm a complete beginner (both with Symfony and PHP in general), so I apologise if my question is trivial.
I want to create a database with doctrine, which means that I have to create classes in src/Entity. But I also want to add forms to the site, and they also require classes in src/Entity. I'd like to separate these classes in two subfolders: src/Entity/database and src/Entity/forms. I tried to edit config/packages/doctrine.yaml as follows:
doctrine:
#...
orm:
#...
mappings:
App:
#...
dir: '%kernel.project_dir%/src/Entity/database'
prefix: 'App\Entity\database'
But I when I use bin/console make:entity Entity it creates the file in src/Entity and gives the following error:
[ERROR] Only annotation mapping is supported by make:entity, but the
<info>App\Entity\Entity</info> class uses a different format. If you
would like this command to generate the properties & getter/setter
methods, add your mapping configuration, and then re-run this command
with the <info>--regenerate</info> flag.
When I run bin/console make:entity Entity --regenerate it says:
[ERROR] No entities were found in the "Entity" namespace.
I also tried bin/console make:entity database/Entity, but it fails with:
[ERROR] "App\Entity\Database/Entity" is not valid as a PHP class name (it must start with a letter or underscore,
followed by any number of letters, numbers, or underscores)
If I do the same with a backslash (database\Entity) it creates a DatabaseEntity.php file in the wrong directory and gives the same error as the first one.

Be very careful, because with such approach you might mess your architecture up. This question is a bit opinionated, but I'm gonna tell you how we make it with entities and forms.
First, my strong belief, Entities and Forms should be separated. Therefore, we contain Entites in src/Entity and Forms in src/Form. The connection between them is a FormType, we contain those in src/FormType.
Here's an example User entity contained in src/Entity/User.php:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #UniqueEntity("username")
*
* #ORM\Entity()
* #ORM\Table(name="users")
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #var int
*/
private $id;
/**
* #Assert\NotBlank()
* #Assert\Email
* #Assert\Length(max="255")
*
* #ORM\Column(type="string", length=255, unique=true)
*
* #var string
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*
* #var string
*/
private $password;
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #return string The username
*/
public function getUsername()
{
return $this->username;
}
/**
* #param null|string $username
*
* #return User
*/
public function setUsername(?string $username): User
{
$this->username = (string) $username;
return $this;
}
/**
* #return string
*/
public function getPassword(): string
{
return $this->password;
}
/**
* #param null|string $password
*
* #return User
*/
public function setPassword(?string $password): User
{
$this->password = (string) $password;
return $this;
}
}
Now, we need a user to be able to register. For this we create a FormType and a Form. Take a look at src/FormType/User.php:
namespace App\FormType;
use App\Entity;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as NativeType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class User extends AbstractType
{
public function getParent(): string
{
return BaseType::class;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// This maps `Entity\User::username` to the respective field
$builder->add(
'username',
NativeType\EmailType::class,
['label' => 'username']
);
// This maps `Entity\User::password` to the respective field
$builder->add(
'password',
NativeType\RepeatedType::class,
[
'constraints' => [new NotBlank()],
'invalid_message' => 'nonMatchingPasswords',
'first_options' => ['label' => 'password'],
'second_options' => ['label' => 'password again'],
'type' => NativeType\PasswordType::class,
]
);
}
// This tells Symfony to resolve the form to the `Entity\User` class
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => Entity\User::class]);
}
}
And now the Form itself, it's src/Form/UserRegistration.php:
namespace App\Form;
use App\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as NativeType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints;
class UserRegistration extends AbstractType
{
public function getParent()
{
// Note this!
return FormType\User::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'fields' => ['username', 'password'],
'translation_domain' => 'forms',
]
);
}
}
And a final stroke on this. In src/Controller/Registration.php we do this:
$form = $this->createForm(
Form\UserRegistration::class,
$user = new Entity\User()
);
The rest (how to handle forms etc.) you know. If you don't, read Symfony docs, they cover it perfectly.
I have cut out / edited some sensitive or non-essential things from this example. For instance, we do not bind password to password, we ask for plain password and then encrypt it. I have not tested the above, so it might not be stable. But for a demonstration on how your architecture should be done it's a good example, IMO.

Related

Adding new form and entities to Symfony does not work

I've forked a Symfony 3 project and I'm trying to add a new Entity and the matching form type, but I can't make it work.
I've tried with the generator and manually, both won't work. A solution with the generator would be the best option
I've generated my entity School with php bin/console doctrine:generate:entity, but when I'm trying to generate the Form, I get the error Class GreenBundle\Entity\School does not exist.
I then tried to create the Form manually and got : Expected to find class "GreenBundle\Form\SchoolType" in file "/var/www/symfony/src/GreenBundle/Form/SchoolType.php" while importing services from resource "../../src/GreenBundle/*", but it was not found! Check the namespace prefix used with the resource.
My school entity generated by the command line is simple
<?php
namespace GreenBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* School
*
* #ORM\Table(name="school")
* #ORM\Entity(repositoryClass="GreenBundle\Repository\SchoolRepository")
*/
class School
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*/
private $nom;
(... Some other variables, the getters and setters )
}
The SchoolType I added is :
<?php
namespace GreenBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SchoolType extends AbstractType
{
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'GreenBundle\Entity\School',
));
}
/**
* #return string
*/
public function getName()
{
return 'green_mainbundle_school';
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom', TextType::class, array(
'label' => "Nom de l'école",
'required' => false,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'ex: École Saint-Exupéry')
));
}
}
And the block In services.yml the error code is referring to is :
# this creates a service per class whose id is the fully-qualified class name
GreenBundle\:
resource: '../../src/GreenBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/GreenBundle/{Entity,Repository,Tests}'
Do you know what I'm doing wrong to get the server to run with the new SchoolType and be able to use it in a controller ?
Thanks
======== Edit
I do have Symfony 3
php bin/console --version
Symfony 3.4.14 (kernel: app, env: dev, debug: true)
The project architecture is classic I guess, I have the default folders of Symfony
In the src folder I have :
AppBundle (unused)
GreenBundle
Controller
DataFixtures
DBAL
DependencyInjection
Entity
Form
Repository (Empty, I don't think they created entities with the generator)
Service
Tests
Twig
GreenBundle.php
======== Edit 2
The namespace I use in GreenBundle.php is GreenBundle, you can see the file below
namespace GreenBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class GreenBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
In case anybody ever have the same kind of issue, I was running my project in a Docker container and it was a synch problem. The Symfony file themselves are correct. Docker detected that new file were added but didn't set the content properly. Rebuild and/or changing the way I use docker with this app fixes the issue

Using data of Doctrine Collection from OneToMany association in JSON

I've seen many examples of how to set up a OneToMany association between Entities. However, I have not seen anything on how to output the data from an association. (such as converting to JSON or just having a clean array)
So, here is some sample code:
declare(strict_types=1);
namespace Banks\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html
*
* #ORM\Entity
* #ORM\Table(name="bank")
**/
class Banks implements \JsonSerializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer", name="id", nullable=false)
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* A Bank could have Many Branches
*
* #ORM\OneToMany(targetEntity="Branches\Entity\Branches", mappedBy="bank")
*
*/
protected $branches;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
*
* #return array|mixed
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'name' => $this->name,
'branches' => $this->getBranches()
];
}
public function __construct()
{
$this->branches = new ArrayCollection();
}
public function getBranches(): Collection
{
return $this->branches;
}
// ... Other getter/setters removed
}
Then we also have the Branches Entity:
declare(strict_types=1);
namespace Branches\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html
*
* #ORM\Entity
* #ORM\Table(name="branches")
**/
class Branches implements \JsonSerializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* A Branch has one Bank
*
* #ORM\ManyToOne(targetEntity="Banks\Entity\Banks", inversedBy="branches")
* #ORM\JoinColumn(name="bank_id", referencedColumnName="id")
*/
protected $bank;
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $bank_id;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
*
* #return array|mixed
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'bank_id' => $this->bank_id,
'name' => $this->name,
'bank' => $this->getBank()
];
}
public function getBank()
{
return $this->bank;
}
// ... Other getter/setters removed
}
Querying both Entities work fine overall, with calls to $result->jsonSerialize(), then returning with return new JsonResponse($result) to get a JSON object. Though querying a Branch has the expected result, where I receive the Branch along with the associated Bank as part of the output, the query to Bank is not returning the associated Branches and instead only displays as "branches": {}
I know this is because $branches is a Collection, but how to output it in a way to be part of the resulting JSON object?
I've tried $this->branches->toArray(), but that results in an array of Objects that cannot be encoded to JSON, therefore, ending in an error.
NOTE: The contents (Object) of $this->getBranches() does contain the Branches as expected, which can be seen by $this->branches->count(). But how to reach them in such a way to allow JsonSerializable to create the JSON?
As requested, here is middleware code leaving up to Entity usage:
A factory is used to create what is needed by the Handler:
class BanksViewHandlerFactory
{
public function __invoke(ContainerInterface $container) : BanksViewHandler
{
$entityManager = $container->get(EntityManager::class);
$entityManager->getConfiguration()->addEntityNamespace('Banks', 'Banks\Entity');
$entityRepository = $entityManager->getRepository('Banks:Banks');
return new BanksViewHandler($entityManager, $entityRepository);
}
}
The Factory calls the Handler:
class BanksViewHandler implements RequestHandlerInterface
{
protected $entityManager;
protected $entityRepository;
public function __construct(
EntityManager $entityManager,
EntityRepository $entityRepository,
) {
$this->entityManager = $entityManager;
$this->entityRepository = $entityRepository;
}
public function handle(ServerRequestInterface $request) : ResponseInterface
{
$return = $this->entityRepository->find($request->getAttribute('id'));
$result['Result']['Banks'] = $return->jsonSerialize();
return new JsonResponse($result);
}
}
The handler returns the JSON.
It's important to note that, when implementing the \JsonSerializable interface, calling jsonSerialize() directly does not return JSON, and you do not call this method explicitly.
As stated in the docs:
Objects implementing JsonSerializable can customize their JSON representation when encoded with json_encode().
The intent of implementing this interface is to enforce the jsonSerialize() method, which is called internally when passing the object(s) to json_encode(); e.g:
$result = $banksRepository->find($id);
$json = json_encode($result);
Additionally, if you want to serialize the child Branch entities as well you need to:
Implement \JsonSerializable for this entity (which you have done)
Doctrine will return these Branches as an ArrayCollection object, containing all child Branch objects. In order to ensure that json_encode() encodes these to JSON properly you need to convert the ArrayCollection to an array using toArray().
To illustrate - (as you pointed out you also implemented this):
public function jsonSerialize()
{
return [
'id' => $this->id,
'name' => $this->name,
'branches' => $this->getBranches()->toArray(), // <--
];
}
This should serialise your Bank and associated Branch entities as expected. Hope this helps :)

Transform address string to (new) address entity before persisting data to database using a Symfony DataTransformer

I have a problem defining a special kind of form using Symfony2 3.3.
General description of the problem:
Storing postal address data in a flexible and clean way with reduced duplicates is a difficult task, because there are so many combinations of address components like street, streetnumber, zipcode, locality, county, country etc. possible. There's no standardized international address format, 'address composition' differs among the countries. Besides that, data entered by users often contains typing errors.
Examples:
Platzl 9, 80331 Munic, Germany
221B Baker Street, London, United Kingdom
3-1 Kōkyogaien, Chiyoda-ku, Tōkyō-to 100-0002, Japan
Because address components vary between almost all countries (in Germany for example, 'county' is of no real interest, whereas in other countries it is needed for mailing purposes), I do not want to use a form with separate fields for every address component. Life would be much easier if there existed a standard address form that covers ALL countries ;-)
As there is none, I want to implement a form with only one single 'address string' text field like Google Maps' address search field. This should give more flexibility to the user and make it easier for him to enter his (international) address.
Idea:
I'd like to use a data transformer to transform the address string into an address object, so its values can be validated (e.g. checked for an already existing matching address) and persisted into the database. My address table has 5 columns at the moment (street, streetnumber, zipcode, locality and country), other columns may be added to the model later. In order to get the components out of an address string, I use a web based 'Geodata address API'. This way, the address components come in a standardized form and therefore - as additional bonus - I can check for duplicates easily. The good thing is that variations in spelling or typing errors in the address string do not result in address duplicates, because the API returns 'standardized' address component values where typing errors are removed.
Workflow, when a user submits the 'address' form:
-> see comments of file 'AddressEntityToAddressStringTransformer.php' in the sources below for further details
Check for empty address string value and add a form error when value is empty
The address string is sent to the API
If a technical problem (e.g. timeout) or a parsing error occurs, an error message has to be added to the form
If the API returns an address object as result, check if a record with the same data already exists in the database table
If data already exists, add an error to the form. The value that is shown in the address string field should then be a composition of the returned standardized values, so typing errors are removed
If data does not exist in the database, create a new address entity object and persist it
Problem:
First of all, I'm not sure if using a data transformer is the right way to implement such a solution, because in the examples that I found in the documentation and other tutorials only already existing data seems to be transformed into another kind of '(visual) representation'. For my solution, also new address objects (not yet existing in a database table) have to be created (+validated and persisted).
With the sources I added to this post, I get the following result when submitting a new address (please have a look to the attached screenshot of the debug toolbar):
Screenshot of web debug toolbar
The form object (dumped in AddressController.php) has a child form named 'address' (upper red box in the screenshot) containing the standardized values as 'modelData' (green box in the screenshot), but the 'modelData' of the actual address form (type = 'Address', lower red box in the screenshot) is empty. Whatever I tried, I never got the transformed data into the 'modelData' object of the $form object. As a result, a new but empty 'Address' entity is persisted to the database table so the table columns contain NULL values.
I think I'm still missing a little 'something', but I don't know what. That's why I need your help ;-)
If a data transformer is not suitable for realizing what I need: Is there another 'Symfony way' for solving my problem?
Here are the PHP sources that I have so far:
src/AppBundle/Controller/AddressController.php (nothing special here...)
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Address;
//use ...
/**
* Address controller.
*
* #Route("/{_locale}/address")
*/
class AddressController extends Controller
{
// ...
/**
* Creates a new address entity.
*
* #Route("/new", name="address_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$address = new Address();
$form = $this->createForm('AppBundle\Form\Type\AddressType', $address);
$form->handleRequest($request);
dump($form);
if ($form->isSubmitted() && $form->isValid()) {
dump($address);
$em = $this->getDoctrine()->getManager();
$em->persist($address);
$em->flush();
return $this->redirectToRoute('address_show', array('id' => $address->getId()));
}
return $this->render('address/new.html.twig', array(
'address' => $address,
'form' => $form->createView(),
));
}
// ...
}
src/AppBundle/Entity/Address.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Validator\Constraints as AddressAssert;
/**
* Address
*
* #ORM\Table(name="address")
* #ORM\Entity
' #AddressAssert\AddressUniqueConstraint()
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="street", type="string", length=255, precision=0, scale=0, nullable=true, unique=false)
*/
private $street;
/**
* #var string
*
* #ORM\Column(name="housenumber", type="string", length=255, precision=0, scale=0, nullable=true, unique=false)
*/
private $housenumber;
/**
* #var string
*
* #ORM\Column(name="zipcode", type="string", length=255, precision=0, scale=0, nullable=true, unique=false)
*/
private $zipcode;
/**
* #var string
*
* #ORM\Column(name="locality", type="string", length=255, precision=0, scale=0, nullable=true, unique=false)
*/
private $locality;
/**
* #var string
*
* #ORM\Column(name="country", type="string", length=255, precision=0, scale=0, nullable=true, unique=false)
*/
private $country;
// getter and setter methods
}
app/config/services.yml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
address_type:
class: AppBundle\Form\Type\AddressType
tags:
- { name: form.type }
address_string_type:
class: AppBundle\Form\Type\AddressStringType
tags:
- { name: form.type }
src/AppBundle/Form/Type/AddressType.php
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Doctrine\ORM\EntityManagerInterface;
class AddressType extends AbstractType
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('address', AddressStringType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'mapped' => false,
));
}
public function getBlockPrefix()
{
return 'appbundle_address';
}
}
src/AppBundle/Form/Type/AddressStringType.php
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Form\DataTransformer\AddressEntityToAddressStringTransformer;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class AddressStringType extends AbstractType
{
private $transformer;
public function __construct(AddressEntityToAddressStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addViewTransformer($this->transformer)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'Error :-(',
'mapped' => false,
));
}
public function getParent()
{
return TextType::class;
}
}
src/AppBundle/Form/DataTransformer/AddressEntityToAddressStringTransformer.php
<?php
namespace AppBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Entity\Address;
use AppBundle\Validator\AddressValidator;
class AddressEntityToAddressStringTransformer implements DataTransformerInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* Transforms a non-standardized address string into a standardized address string
*
* #param string $adrObjStandardized
* #return string|null
*/
public function transform($adrObjStandardized)
{
if ($adrObjStandardized === null)
{
return null;
}
$street_housenumber_str = trim($adrObjStandardized->getStreet().' '.$adrObjStandardized->getHousenumber());
$zipcode_locality_str = trim($adrObjStandardized->getZipcode().' '.$adrObjStandardized->getLocality());
$country_str = trim($adrObjStandardized->getCountry());
$tmp_arr = array($street_housenumber_str, $zipcode_locality_str, $country_str);
$tmp_arr = array_diff($tmp_arr, array(''));
$adrStrStandardized = implode(', ', $tmp_arr);
//$adrStrStandardized = 'Platzl 9, 80331 Munic, Germany';
return $adrStrStandardized;
}
/**
* Transforms an address string to an address object
* If a matching address already exists in the address table, return it. When creating a new entry, AddressUniqueConstraint will later tell us 'Address already exists.'
* If no address in the address table matches, return a new address object to be persisted into the address table
*
* #param string $adrStrNotStandardized
* #return object
* #throws TransformationFailedException (if address could not be standardized by address validation service (via an API))
*/
public function reverseTransform($adrStrNotStandardized)
{
// TODO: Implement workflow as follows:
// 1. Try to standardize data by sending the address string to an address validation service (API)
// 1.1. If standardizing was successful
// 1.1.1. Look for an already existing record in the database table 'address'
// 1.1.1.1. If address in table 'address' matches, return it
// 1.1.1.2. If NO address in table 'address' matches, create new Address object containing standardized data and return it
// 1.2. If standardizing was NOT successful
// 1.2.1. Throw TransformationFailedException
$validator = new AddressValidator();
try
{
// 1. Try to standardize data by sending the address string to an address validation service (API)
$standardized_data = $validator->validateAddressFromString($adrStrNotStandardized);
$addr_data = array(
'street' => $standardized_data['street'],
'housenumber' => $standardized_data['housenumber'],
'zipcode' => $standardized_data['zipcode'],
'locality' => $standardized_data['locality'],
'country' => $standardized_data['country'],
);
}
catch (\Exception $e) // 1.2. Standardizing was NOT successful
{
// 1.2.1. Throw TransformationFailedException
throw new TransformationFailedException(
'Die Adress-Daten konnten nicht standardisiert werden.'
);
}
// 1.1. We are still here, so standardizing was successful
// 1.1.1. Look for an already existing record in the database table 'address'
$address = $this->em
->getRepository(Address::class)
->findOneBy($addr_data)
;
if ($address === NULL)
{
// 1.1.1.2. If NO address in table 'address' matches, create new Address object containing standardized data and return it
$new_addr_obj = new Address();
$new_addr_obj->setStreet($addr_data['street']);
$new_addr_obj->setHousenumber($addr_data['housenumber']);
$new_addr_obj->setZipcode($addr_data['zipcode']);
$new_addr_obj->setLocality($addr_data['locality']);
$new_addr_obj->setCountry($addr_data['country']);
return $new_addr_obj;
}
else
{
// 1.1.1.1. If address in table 'address' matches, return it
return $address;
}
}
}
src/AppBundle/Validator/Constraints/AddressUniqueConstraint.php
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class AddressUniqueConstraint extends Constraint
{
public $message_already_exists = 'Address already exists.';
public $message_invalid_form_data = 'Invalid form data.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
/**
* #return string
*/
public function validatedBy()
{
return AddressUniqueConstraintValidator::class;
}
}
src/AppBundle/Validator/Constraints/AddressUniqueConstraintValidator.php
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Entity\Address;
class AddressUniqueConstraintValidator extends ConstraintValidator
{
/**
* #var EntityManagerInterface
*/
protected $em;
/**
* Construct
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
public function validate($value, Constraint $constraint)
{
if (!$value instanceof Address)
{
$this->context->buildViolation($constraint->message_invalid_form_data)
->addViolation();
}
$search = array('street' => $value->getStreet(), 'housenumber' => $value->getHousenumber(), 'zipcode' => $value->getZipcode(), 'locality' => $value->getLocality(), 'country' => $value->getCountry());
$adresses = $this->em->getRepository('AppBundle:Address')->findBy($search);
if ($adresses && count($adresses) > 0)
{
$this->context->buildViolation($constraint->message_already_exists)
->addViolation();
}
}
}

Symfony3, Missing argument 1 for Doctrine\ORM\EntityRepository::__construct(), how and why

I'm making my first small app in symfony, a simple blog.
Now I have been using the documentation for both symfony and doctrine and want to preform a simple, beginner task: display a json encoded simple table
Yet somehow I cant seem to get along with doctrine.
Here is my data (apart form the view which does nothing but display the value):
//AppBundle/Controller/DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Post;
use Doctrine\Common\Persistence;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$database = new Post();
$output = $database->findAll();
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
'x' => json_encode($output)
]);
}
}
<?php
//AppBundle/Entity/Post.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\EntityRepository;
/**
* #ORM\Entity
* #ORM\Table(name="sqltest")
*/
class Post extends EntityRepository
{
//The post for now uses data from a temponary test table
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="integer", scale=2)
*/
private $number;
/**
* Get id
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
* #param string $name
* #return Post
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set number
* #param integer $number
* #return Post
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
* #return integer
*/
public function getNumber()
{
return $this->number;
}
}
Problem is when I try to display the website i get this exception
Warning: Missing argument 1 for
Doctrine\ORM\EntityRepository::__construct(), called in
C:\Users\Alan\Desktop\symf-blog\src\AppBundle\Controller\DefaultController.php
on line 19 and defined
Problematic line being the one with $database = new Post();
I am very new in this and am aware that the response is very simple and I just don't see it. When answering please provide and explanation which even a dead rabbit could understand.
Pretty thanks for your patience.
PS: Also an explanation about what the $em variable I've seen so much about is for and from where do I get it would be nice
If you're want a repository class for custom DB functions then this is the right way to do it:
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class PostRepository extends EntityRepository
{
public function findAll()
{
return $this->findBy(array(), array('id' => 'DESC', 'createdAt' => 'DESC'));
}
....
}
Then in your controller:
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository("AppBundle:Post")->findAll();
Remove the annotations (them belongs to the entity). Also pay attention to what #james_bond told you. Try that!
As stated in the documentation, you access custom repository classes through doctrine entity manager.
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('YourBundle:Post')->findAll();
Also you're mixing your entity definition with your repository definition, which is not a good idea.
Please refer to the doctrine documentation in symfony for proper usage.

Expected argument of type "string", "Vendor\NameBundle\Form\EntitynameType" given Symfony 3.0

Because of the problems I had with symfony version 2.7 (404 page error right away after installing a project) I started using Symfony version 3.0. After some minor problems I figured out that "app/console" is replaced by "bin/console". So I'm working now on a new project and I have already build a new bundle with 1 entity called
Codeit/RestaurantBundle && CodeitRestaurantBundle:Reserveren
Format is annotation, and the entity has an id and 1 field called "naam" (string, 255). I updated the schema's, I generate the entities of Codeit and after that was succesfully done I generated a crud with write actions. The format was again annotation and the prefix is /reserveren.
So if I visit the page web/reserveren I am getting a show page of my entity. Unfortunately if I try to add a new entry I am getting the following error:
Expected argument of type "string", "Codeit\RestaurantBundle\Form\ReserverenType" given
My Bundle/Form/ReserverenType.php
<?php
namespace Codeit\RestaurantBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ReserverenType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('naam')
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Codeit\RestaurantBundle\Entity\Reserveren'
));
}
}
My entity code
<?php
namespace Codeit\RestaurantBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Reserveren
*
* #ORM\Table(name="reserveren")
* #ORM\Entity(repositoryClass="Codeit\RestaurantBundle\Repository\ReserverenRepository")
*/
class Reserveren
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Naam", type="string", length=255)
*/
private $naam;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set naam
*
* #param string $naam
*
* #return Reserveren
*/
public function setNaam($naam)
{
$this->naam = $naam;
return $this;
}
/**
* Get naam
*
* #return string
*/
public function getNaam()
{
return $this->naam;
}
}
Forms have changed quite a bit in 3.0. You might be better off sticking with 2.8 for now.
You did not show it but I suspect, based on the error message, that your controller code looks like:
$form = $this->createForm(new ReservernType(), $item);
That is the 2.x way of doing things. For 3.x use:
$form = $this->createForm(ReservernType::class, $item);
http://symfony.com/doc/current/book/forms.html#creating-form-classes
try with:
$builder
->add('naam', TextType::class);
// If you use PHP 5.3 or 5.4 you must use
// ->add('naam','Symfony\Component\Form\Extension\Core\Type\TextType')
instead of this
$builder
->add('naam');
And add the use statement:
use Symfony\Component\Form\Extension\Core\Type\TextType;
Motivation: from the upgrade guide:
Type names were deprecated and will be removed in Symfony 3.0. Instead
of referencing types by name, you should reference them by their
fully-qualified class name (FQCN) instead. With PHP 5.5 or later, you
can use the "class" constant for that:
Hope this help
Try like this:
/.../
use Symfony\Component\Form\Extension\Core\Type\TextType;
/.../
class ReserverenType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('naam', TextType::class);
}
}
add this to your entity's class replace name with an attribute that suits you.
public function __toString() {
return $this->name;
}

Categories