I'm trying to use Doctrine2 with ZF3. All components have been installed with composer.
When I try to use an Entity I have an exception :
Doctrine\Common\Persistence\Mapping\MappingException
Class 'Application\Entity\Concours' does not exist
The exception is thrown when I use in the Controller's action :
$concours = $this->entityManager->getRepository(Concours::class);
I have the same problem if i use :
$entity = new Concours();
or
$entity = new \Application\Entity\Concours();
I really don't understand why...
Thanks for help...
My configuration :
config/local.php
use Doctrine\DBAL\Driver\PDOMySql\Driver as PDOMySqlDriver;
return [
'doctrine' => [
'connection' => [
'orm_default' => [
'driverClass' => PDOMySqlDriver::class,
'params' => [
'host' => '127.0.0.1',
'user' => 'xxxx',
'password' => 'xxxx',
'dbname' => 'goch',
],
],
],
],
];
config/modules.config.php
return [
'Zend\Cache',
'Zend\Form',
'Zend\InputFilter',
'Zend\Filter',
'Zend\Paginator',
'Zend\Hydrator',
'Zend\Router',
'Zend\Validator',
'DoctrineModule',
'DoctrineORMModule',
'Application',
];
For Application Module,
module/Application/config/module.config.php
namespace Application;
use Zend\Router\Http\Literal;
use Zend\Router\Http\Segment;
use Zend\ServiceManager\Factory\InvokableFactory;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
return [
'router' => [
'routes' => [
'home' => [
'type' => Literal::class,
'options' => [
'route' => '/',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
],
'application' => [
'type' => Segment::class,
'options' => [
'route' => '/application[/:action]',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
],
],
],
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\Factory\IndexControllerFactory::class,
],
],
'view_manager' => [
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => [
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
],
'template_path_stack' => [
__DIR__ . '/../view',
],
],
'doctrine' => [
'driver' => [
__NAMESPACE__ . '_driver' => [
'class' => AnnotationDriver::class,
'cache' => 'array',
'paths' => [__DIR__ . '/../src/Entity']
],
'orm_default' => [
'drivers' => [
__NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
]
]
]
]
];
My Controller (module/Application/src/Controller/IndexController.php) is :
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Doctrine\ORM\EntityManager;
use Application\Entity\Concours;
class IndexController extends AbstractActionController {
/**
* Entity manager.
* #var Doctrine\ORM\EntityManager
*/
private $entityManager;
// Constructor method is used to inject dependencies to the controller.
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function indexAction() {
return new ViewModel();
}
public function concoursAction(){
$concours=array();
$concours = $this->entityManager->getRepository(Concours::class);
// Render the view template
return new ViewModel([
'concours' => $concours
]);
}
}
And the associated Factory
namespace Application\Controller\Factory;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Controller\IndexController;
/**
* This is the factory for IndexController. Its purpose is to instantiate the
* controller.
*/
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container,
$requestedName, array $options = null)
{
$entityManager = $container->get('doctrine.entitymanager.orm_default');
// Instantiate the controller and inject dependencies
return new IndexController($entityManager);
}
}
All my Entities class files are in module/Application/src/Application/Entity/,
here is the Concours Entity Class:
<?php
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Concours
*
* #ORM\Table(name="Concours", uniqueConstraints={#ORM\UniqueConstraint(name="numero_UNIQUE", columns={"numero"})})
* #ORM\Entity
*/
class Concours
{
/**
* #var integer
*
* #ORM\Column(name="ref", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $ref;
...
}
Here is your problem:
All my Entities class files are in
module/Application/src/Application/Entity/
Zend 3 uses PSR-4 instead of PSR-0 which is considered as depracated. You can confirm it by checking composer.json file. You should see following declaration:
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/",
}
},
In PSR-0 if you define Foo\Bar namespace is achored in src/ it will look for class in src/Foo/Bar/{your_class}.php while in PSR-4 it will look in src/{your_class}.php.
So... to fix your problem. Move Concours entity from:
module/Application/src/Application/Entity/
to:
module/Application/src/Entity/
Related
I need to set values for default address fields(langcode, country_code, administrative_area, address_locality ect.) when I create a node. I used below code in the submitForm function of a Form class which is extends by Drupal\Core\Form\FormBase class. But it not works for me.
$venueNode = Node::create([
'type' => 'venue',
'title' => 'Venue',
'field_address' => [
'country_code' => 'US',
'address_line1' => '1098 Alta Ave',
'locality' => 'Mountain View',
'administrative_area' => 'US-CA',
'postal_code' => '94043',
],
]);
$venueNode->save();
I made a mistake here. There should be a 0 index for field_address. Therefore the code should be like below.
$venueNode = Node::create([
'type' => 'venue',
'title' => 'Venue',
'field_address' => [
0 => [
'country_code' => 'US',
'address_line1' => '1098 Alta Ave',
'locality' => 'Mountain View',
'administrative_area' => 'US-CA',
'postal_code' => '94043',
],
],
]);
$venueNode->save();
The purpose is to create a module in which to insert all the entities and then use them in the other modules. This is why I created a module called Entity in which I entered the entities. Unfortunately when I try to use one of these entities within the basic controller I get an error: class not found
This is my composer
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/",
"Entity\\": "module/Entity/src/"
}
},
I already execute this command
composer dump-autoload
My entity class is under
module/Entity/src/Model/
This is my class
<?php
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* SysUserRole
*
* #ORM\Table(name="sys_user_role")
* #ORM\Entity
*/
class SysUserRole
{
/**
* #var int
*
* #ORM\Column(name="ID_SYS_USER_ROLE", type="integer", nullable=false, options={"unsigned"=true})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idSysUserRole;
/**
* #var string
*
* #ORM\Column(name="NAME", type="string", length=100, nullable=false)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="DESCRIPTION", type="string", length=255, nullable=false)
*/
private $description;
/**
* #return int
*/
public function getIdSysUserRole()
{
return $this->idSysUserRole;
}
/**
* #param int $idSysUserRole
*/
public function setIdSysUserRole($idSysUserRole)
{
$this->idSysUserRole = $idSysUserRole;
}
/**
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* #param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
}
This is my config\autoload\doctrine.global.php
'doctrine' => [
'connection' => [
'orm_default' => [
'driverClass' => PDOMySqlDriver::class,
'params' => [
'host' => 'localhost',
'user' => '***',
'password' => '****',
'dbname' => 'mydb',
'charset' => 'utf8',
'driverOptions' => array(
1002 => 'SET NAMES utf8'
)
]
],
],
'authentication' => [
'orm_default' => [
'object_manager' => 'Doctrine\ORM\EntityManager',
'identity_class' => 'Application\Entity\User',
'identity_property' => 'email',
'credential_property' => 'password',
],
],
'driver' => [
'entity_driver' => [
'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => [__DIR__ . '/../../module/Entity/src/Model/']
],
'orm_default' => [
'drivers' => [
'\Entity' => 'entity_driver'
]
]
],
],
In controller action i try this
$entity = new SysUserRole();
The error is
Class 'Entity\SysUserRole' not found
edit:
the error is changed
I move this code inside a module.config.php
'doctrine' => [
'driver' => [
__NAMESPACE__ . '_driver' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Model')
),
'orm_default' => [
'drivers' => [
//was \Entity
'Entity\Model' => __NAMESPACE__ . '_driver'
]
]
],
]
new error is
The class 'Entity\Model\SysUserRole' was not found in the chain configured namespaces \Entity\
Folder structure and psr-4 registered namespace. I would expect the Entity namespace to be Entity\Model with then the class name.
Btw, using a module for a purpose would probably be a better approach. Having Entities per module, for the purpose of the module (e.g. User and Profile Entity objects in the User module), makes sense.
namespace SomeSpace
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
return [
'doctrine' => [
'driver' => [
__NAMESPACE__ . '_driver' => [
'class' => AnnotationDriver::class,
'cache' => 'array',
'paths' => [
__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'src'
. DIRECTORY_SEPARATOR . 'Entity',
]
],
'orm_default' => [
'drivers' => [
__NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
],
],
],
],
];
The above config would be required per module, due to the usage of the __NAMESPACE__ and __DIR__ global constants.
NOTE: Make sure that namespace names match folder names, e.g.:
module\Address\Entity\Address (last is class name for Address.php)
Folder structure here is:
module\Address\Entity\**.php
Example: Entity class & associated composer.json
From example links:
composer.json
"autoload": {
"psr-4": {
"Keet\\Form\\Examples\\": "src/"
},
Address.php
<?php
namespace Keet\Form\Examples\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="addresses")
* #ORM\Entity
*/
class Address extends AbstractEntity
{
...
}
The default validation error message for Regex is
"The input does not match against pattern '%pattern%'"
I can replace it by a custom one like
"Please make an input according to the pattern '%pattern%'"
But then I still have a not really user-friendly message with the internal regex in it. I also can write
"Only capital letters are allowed"
But in this case I need to write a new message for every single regex field.
Is it possible / How to create own, but still flexible/reusable/parameterizable messages?
An example of what I want:
public function getInputFilterSpecification()
{
return [
'foo' => [
'validators' => [
[
'name' => 'Regex',
'options' => [
'pattern' => '/^[A-Z0-9:]*$/',
'pattern_user_friendly' => 'capital letters, numbers, and colons',
'message' => 'The input may only contain the following characters: %pattern_user_friendly%.'
]
],
]
],
'bar' => [
'validators' => [
[
'name' => 'Regex',
'options' => [
// new pattern
'pattern' => '/^[A-Z~:\\\\]*$/',
// new user friendly pattern description
'pattern_user_friendly' => 'capital letters, tildes, colons, and backslashes',
// still the same message
'message' => 'The input may only contain the following characters: %pattern_user_friendly%.'
]
],
]
],
];
}
The solution is to create a custom Validator (extending the Regex), to extend there the list of messageVariables, and to add the logic for setting the value as a property:
class Regex extends ZendRegex
{
protected $patternUserFriendly;
public function __construct($pattern)
{
// s. https://github.com/zendframework/zend-validator/blob/master/src/Regex.php#L34-L36
$this->messageVariables['patternUserFriendly'] = 'patternUserFriendly';
$this->messageTemplates[self::NOT_MATCH] =
'The input may only contain the following characters: %patternUserFriendly%.'
;
parent::__construct($pattern);
if (array_key_exists('patternUserFriendly', $pattern)) {
$this->patternUserFriendly = $pattern['patternUserFriendly'];
}
}
}
class MyFieldset extends ZendFieldset implements InputFilterProviderInterface
{
...
public function init()
{
parent::init();
$this->add(
[
'type' => 'text',
'name' => 'foo',
'options' => [
'label' => _('foo')
]
]);
$this->add(
[
'type' => 'text',
'name' => 'bar',
'options' => [
'label' => _('bar')
]
]);
}
public function getInputFilterSpecification()
{
return [
'bar' => [
'validators' => [
[
'name' => 'MyNamespace\Validator\Regex',
'options' => [
'pattern' => '/^[a-zA-z]*$/',
'patternUserFriendly' => '"a-z", "A-Z"'
]
]
]
]
];
}
}
So I have a controller with a route already configured my action looks like this
/**
* List of brands
*
* #return array
*/
public function listAction()
{
$brandIds = \Drupal::entityQuery('node')
->condition('type', 'brand')
->sort('title', 'asc')
->execute();
return [
'addition_arguments' => [
'#theme' => 'page--brands',
'#brands' => is_array($brandIds) ? Node::loadMultiple($brandIds) : [],
'#brands_filter' => \Drupal::config('field.storage.node.field_brand_categories')->get()
]
];
}
I would like to use #brands and #brands_filter in my twig template theme file page--brands, but I never see it go through.
Can anyone help?
Thank you
UPDATE
Worked it out
In you modules my_module.module file add the following
function module_theme($existing, $type, $theme, $path)
{
return [
'module.brands' => [
'template' => 'module/brands',
'variables' => ['brands' => []],
],
];
}
In your controller use
return [
'#theme' => 'mymodule.bands',
'#brands' =>is_array($brandIds) ? Node::loadMultiple($brandIds) : []
]
This will inject the variable Hope this helps omeone else who has this problem, wish the docs were better :)
Please refer below sample code :
mymodule.module
<?php
/**
* #file
* Twig template for render content
*/
function my_module_theme($existing, $type, $theme, $path) {
return [
'mypage_template' => [
'variables' => ['brands' => NULL, 'brands_filter' => NULL],
],
];
}
?>
mycontorller.php
<?php
/**
* #file
* This file use for access menu items
*/
namespace Drupal\mymodule\Controller;
use Drupal\Core\Controller\ControllerBase;
class pagecontroller extends ControllerBase {
public function getContent() {
return [
'#theme' => 'mypage_template',
'#brands' => 'sampleBrand',
'#brands_filter' => 'sampleBrands_filter',
];
}
}
Twig File Name inside templates folder - mypage-template.html.twig
I am working on a shopping checkout page which requires:
OrderPerson
PersonAddress (delivery)
Order (notes, time etc. not all items are on the form at present to keep it simple)
I have more or less worked out how to put this together however it requires some tidying up.
My form looks as follows:
To make the forms play nicely together I have setup a CheckoutForm that includes the OrderPerson, PersonAddress and Order Fieldsets:
<?php
namespace MyCart\Form;
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterInterface;
class CheckoutForm extends Form
{
public function __construct(
InputFilterInterface $orderFilter,
InputFilterInterface $orderPersonFilter,
InputFilterInterface $personAddressFilter,
$name = null,
$options = array()
) {
parent::__construct('checkout', $options);
$this->orderFilter = $orderFilter;
$this->orderPersonFilter = $orderPersonFilter;
$this->personAddressFilter = $personAddressFilter;
}
public function init()
{
$this->add(
[
'name' => 'csrfcheck',
'type' => 'csrf'
]
);
$this->add(
[
'name' => 'order',
'type' => OrderFieldset::class,
'options' => [
'use_as_base_fieldset' => true
]
]
);
$this->add(
[
'name' => 'order-person',
'type' => OrderPersonFieldset::class,
'options' => [
'use_as_base_fieldset' => true
]
]
);
$this->add(
[
'name' => 'person-address',
'type' => PersonAddressFieldset::class,
'options' => [
'use_as_base_fieldset' => true
]
]
);
$this->add(
[
'name' => 'submit',
'type' => 'submit',
'attributes' => [
'value' => 'Update',
'class' => 'form-element'
]
]
);
$this->getInputFilter()->add($this->orderFilter, 'order');
$this->getInputFilter()->add($this->orderPersonFilter, 'order-person');
$this->getInputFilter()->add($this->personAddressFilter, 'person-address');
}
}
The Checkout Form has a factory:
<?php
namespace MyCart\Form\Factory;
use MyCart\Form\CheckoutForm;
use MyCart\InputFilter\OrderFilter;
use MyCart\InputFilter\OrderPersonFilter;
use MyCart\InputFilter\PersonAddressFilter;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CheckoutFormFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$inputFilter = $serviceLocator->getServiceLocator()->get('InputFilterManager');
return new CheckoutForm(
$inputFilter->get(OrderFilter::class),
$inputFilter->get(OrderPersonFilter::class),
$inputFilter->get(PersonAddressFilter::class)
);
}
}
AS you can see the Checkout Form references the three input filters that are all constructed in the same manner. I have the OrderPerson fieldset here as a reference:
<?php
namespace MyCart\Form;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use MyCart\Entity\OrderPerson;
use Zend\Form\Element;
use Zend\Form\Fieldset;
class OrderPersonFieldset extends Fieldset
{
/**
* #var \Doctrine\Common\Persistence\ObjectManager
* #access protected
*/
protected $objectManager;
/**
* #param ObjectManager $objectManager
* #param OrderPerson $orderPrototype
* #param null $name
* #param array $options
*/
public function __construct(
ObjectManager $objectManager,
OrderPerson $orderPrototype,
$name = null,
$options = array()
) {
parent::__construct($name, $options);
$this->objectManager = $objectManager;
$this->setHydrator(new DoctrineObject($objectManager));
$this->setObject($orderPrototype);
}
public function init()
{
$this->add(
[
'name' => 'id',
'type' => 'hidden',
]
);
$this->add(
[
'name' => 'rbu_user_id',
'type' => 'hidden',
]
);
$this->add(
[
'type' => 'text',
'name' => 'email',
'attributes' => [
'class' => 'form-control',
'required' => 'required',
],
'options' => [
'label' => 'Email Address',
'instructions' => 'Please enter your email address',
],
]
);
//etc...
}
}
On execution of the form the CheckoutController is called and this is where I am unsure of what to do or how to save the various objects, this is what I have so far:
public function indexAction()
{
//Pre check stuff here
//Start the form processing
$prg = $this->prg();
if ($prg instanceof Response) {
return $prg;
} elseif ($prg === false) {
//If logged in
if ( $this->authService->hasIdentity()) {
//Update the OrderPerson entity
$user_id = $this->identity()->getId();
$userObject = $this->userService->find($user_id);
$orderPersonEntity = new OrderPerson();
$orderPersonEntity->setRbuUser($userObject);
$orderPersonEntity->setFirstname($userObject->getFirstname());
$orderPersonEntity->setSurname($userObject->getSurname());
$this->checkoutForm->bind($orderPersonEntity);
//Update the person address entity
$personAddress = new PersonAddress();
$personAddress->setAddress1($userObject->getAddress1());
$personAddress->setAddress2($userObject->getAddress2());
$this->checkoutForm->bind($personAddress);
//Update the order entity
}
return new ViewModel(
array(
'cart' => $cart,
'form' => $this->checkoutForm
)
);
}
$this->checkoutForm->setData($prg);
if (!$this->checkoutForm->isValid()) {
return new ViewModel(
array(
'cart' => $cart,
'form' => $this->checkoutForm
)
);
}
$checkoutObject = $this->checkoutForm->getData();
die(var_dump($checkoutObject));
Dumping the result here outputs only the person address object:
object(MyCart\Entity\PersonAddress)[1177]
private 'id' => null
private 'address1' => string 'xx' (length=2)
private 'address2' => string 'xx' (length=2)
private 'region' => null
private 'postCode' => string 'xx' (length=2)
private 'city' => string 'xx' (length=2)
private 'country' => string 'xx' (length=2)
private 'created' => null
private 'modified' => null
private 'orderPerson' => null
So my question is simply, is this the right thinking and how do I complete this so that I can save the three entities in a single go?