Tree Dynamically Form Symfony - forms
I have 5 tables:
1.event - id, name, location
2.location - id, country_id, county_id, city_id
3.country - id, name
4.county - id, name, country_id,
5. city - id, name, county_id
I can get to work for populate city select box
I have 2 form types
EventLocationType and LocationType
Thank you in advance!
I have try to make city box to work but i don;t know how to do it!
Thanks!
class EventLocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
->add('location', LocationType::class, [
'label' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => EventLocation::class,
]);
}
}
class LocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('country', CountryTypeSelect::class, [
'label' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
])
->add('county', ChoiceType::class, [
'label' => false,
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
])
->add('city', ChoiceType::class, [
'label' => false,
'attr' => [ 'class' => 'form-control'],
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
$formModifier = function (FormInterface $form, Country $country = null) {
$counties_array = [];
if($country != null) {
$g = new GeoNamesClient('djmichael');
[$countryGeoNames] = $g->countryInfo([
'country' => $country->getName(),
]);
$country_name = $countryGeoNames->geonameId;
$counties_json = $g->children(['geonameId' => $country_name]);
foreach($counties_json as $counties_j) {
//dd($counties_j->toponymName);
$counties_array[$counties_j->toponymName] = $counties_j->geonameId;
}
//dd($counties);
}
//var_dump($counties);
$counties = null === $counties_array ? [] : $counties_array;
$form->add('county', ChoiceType::class, [
'placeholder' => 'Choose an option',
'required' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $counties,
//'mapped' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
//
$country = null;
if($data != null) {
$country = $data->getCountry();
//dd($data);
}
$formModifier($event->getForm(), $country);
}
);
$builder->get('country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$country = $event->getForm()->getData();
if($country->getName() != null) {
$formModifier($event->getForm()->getParent(), $country);
} else {
$formModifier2($event->getForm(), $county);
}
//dd($country);
}
);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Location::class,
]);
}
}
var $country = $('#event_location_location_country_name');
var $token = $('#event_location__token');
var $county = $('#event_location_location_county');
// When country gets selected ...
$country.change(function () {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected country value.
var data = {};
data[$country.attr('name')] = $country.val();
data[$token.attr('name')] = $token.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
complete: function (html) {
//console.log(html.responseText);
// Replace current state field ...
$('#event_location_location_county').replaceWith(
// ... with the returned one from the AJAX response.
$(html.responseText).find('#event_location_location_county')
);
},
});
});
$county.change(function () {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected country value.
var data = {};
data[$country.attr('name')] = $county.val();
data[$token.attr('name')] = $token.val();
console.log(data);
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
complete: function (html) {
//console.log(html.responseText);
// Replace current state field ...
$('#event_location_city').replaceWith(
// ... with the returned one from the AJAX response.
$(html.responseText).find('#event_location_city')
);
},
});
});
#[ORM\Entity(repositoryClass: EventLocationRepository::class)]
class EventLocation
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\ManyToOne(inversedBy: 'eventLocations', cascade: ['persist', 'remove'])]
#[Assert\NotBlank]
#[ORM\JoinColumn(nullable: false)]
private ?Location $location = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function setLocation(?Location $location): self
{
$this->location = $location;
return $this;
}
}
#[ORM\Entity(repositoryClass: LocationRepository::class)]
class Location
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'locations', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotBlank]
private ?Country $country = null;
#[ORM\ManyToOne(inversedBy: 'locations', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotBlank]
private ?County $county = null;
#[ORM\ManyToOne(inversedBy: 'locations')]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotBlank]
private ?City $city = null;
#[ORM\OneToMany(mappedBy: 'location', targetEntity: EventLocation::class)]
private Collection $eventLocations;
public function __construct()
{
$this->eventLocations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
public function getCounty(): ?County
{
return $this->county;
}
public function setCounty(?County $county): self
{
$this->county = $county;
return $this;
}
public function getCity(): ?City
{
return $this->city;
}
public function setCity(?City $city): self
{
$this->city = $city;
return $this;
}
/**
* #return Collection<int, EventLocation>
*/
public function getEventLocations(): Collection
{
return $this->eventLocations;
}
public function addEventLocation(EventLocation $eventLocation): self
{
if (!$this->eventLocations->contains($eventLocation)) {
$this->eventLocations->add($eventLocation);
$eventLocation->setLocation($this);
}
return $this;
}
public function removeEventLocation(EventLocation $eventLocation): self
{
if ($this->eventLocations->removeElement($eventLocation)) {
// set the owning side to null (unless already changed)
if ($eventLocation->getLocation() === $this) {
$eventLocation->setLocation(null);
}
}
return $this;
}
}
#[ORM\Entity(repositoryClass: CountryRepository::class)]
class Country
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\OneToMany(mappedBy: 'country', targetEntity: County::class)]
private Collection $counties;
#[ORM\OneToMany(mappedBy: 'country', targetEntity: Location::class)]
private Collection $locations;
public function __construct()
{
$this->counties = new ArrayCollection();
$this->locations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection<int, County>
*/
public function getCounties(): Collection
{
return $this->counties;
}
public function addCounty(County $county): self
{
if (!$this->counties->contains($county)) {
$this->counties->add($county);
$county->setCountry($this);
}
return $this;
}
public function removeCounty(County $county): self
{
if ($this->counties->removeElement($county)) {
// set the owning side to null (unless already changed)
if ($county->getCountry() === $this) {
$county->setCountry(null);
}
}
return $this;
}
/**
* #return Collection<int, Location>
*/
public function getLocations(): Collection
{
return $this->locations;
}
public function addLocation(Location $location): self
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setCountry($this);
}
return $this;
}
public function removeLocation(Location $location): self
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getCountry() === $this) {
$location->setCountry(null);
}
}
return $this;
}
}
#[ORM\Entity(repositoryClass: CountyRepository::class)]
class County
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\ManyToOne(inversedBy: 'counties')]
private ?Country $country = null;
#[ORM\OneToMany(mappedBy: 'county', targetEntity: City::class)]
private Collection $cities;
#[ORM\OneToMany(mappedBy: 'county', targetEntity: Location::class)]
private Collection $locations;
public function __construct()
{
$this->cities = new ArrayCollection();
$this->locations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
/**
* #return Collection<int, City>
*/
public function getCities(): Collection
{
return $this->cities;
}
public function addCity(City $city): self
{
if (!$this->cities->contains($city)) {
$this->cities->add($city);
$city->setCounty($this);
}
return $this;
}
public function removeCity(City $city): self
{
if ($this->cities->removeElement($city)) {
// set the owning side to null (unless already changed)
if ($city->getCounty() === $this) {
$city->setCounty(null);
}
}
return $this;
}
/**
* #return Collection<int, Location>
*/
public function getLocations(): Collection
{
return $this->locations;
}
public function addLocation(Location $location): self
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setCounty($this);
}
return $this;
}
public function removeLocation(Location $location): self
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getCounty() === $this) {
$location->setCounty(null);
}
}
return $this;
}
}
#[ORM\Entity(repositoryClass: CityRepository::class)]
class City
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\ManyToOne(inversedBy: 'cities')]
private ?County $county = null;
#[ORM\OneToMany(mappedBy: 'city', targetEntity: Location::class)]
private Collection $locations;
public function __construct()
{
$this->locations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getCounty(): ?County
{
return $this->county;
}
public function setCounty(?County $county): self
{
$this->county = $county;
return $this;
}
/**
* #return Collection<int, Location>
*/
public function getLocations(): Collection
{
return $this->locations;
}
public function addLocation(Location $location): self
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setCity($this);
}
return $this;
}
public function removeLocation(Location $location): self
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getCity() === $this) {
$location->setCity(null);
}
}
return $this;
}
}
After spent some good time, i finally found the solution for
Countries->Counties->Cities
With the help from another stackoverflow post, i create a event Subscriber.
I also use geoname.org and change from post_submit to pre_submit
So the final looks like this:
class DynamicFieldsSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmitData',
];
}
/**
* Handling form fields before form renders.
* #param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
//dd($data);
$country = null;
if($data != null) {
//$country = $data->getCountry();
dd($data);
}
$this->addCountyData($form, $country);
}
/**
* Handling form fields before form renders.
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
$location['country'] = $event->getData();
//dd($location);
if(array_key_exists('country', $location['country'])) {
//dd($country);
$countryObj = new Country();
$countryObj->setName($location['country']['country']);
//dd($country);
$this->addCountyData($event->getForm(), $countryObj);
} else {
$country = null;
$countyGeoId = $location['country']['county'];
//dd($countyGeoId);
$this->addCityData($event->getForm(), $countyGeoId);
}
}
/**
* Method to Add State Field. (first dynamic field.)
* #param FormInterface $form
* #param type $county
*/
private function addCountyData(FormInterface $form, Country $country = null) {
$countiesArray = [];
if($country != null) {
$geoNamesClient = new GeoNamesClient('djmichael');
[$countryGeoNames] = $geoNamesClient->countryInfo([
'country' => $country->getName(),
]);
$countryName = $countryGeoNames->geonameId;
$countiesJson = $geoNamesClient->children(['geonameId' => $countryName]);
foreach($countiesJson as $countyJson) {
$countiesArray[$countyJson->toponymName] = $countyJson->geonameId;
}
}
$counties = null === $countiesArray ? [] : $countiesArray;
$form->add('county', ChoiceType::class, [
'placeholder' => 'Choose an option',
'mapped' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $counties,
// 'data' => 'Tirana',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
}
/**
* Method to Add State Field. (first dynamic field.)
* #param FormInterface $form
* #param type $city
*/
private function addCityData(FormInterface $form, $county = null) {
$citiesArray = [];
if($county != null) {
$geoNamesClient = new GeoNamesClient('djmichael');
$citiesJson = $geoNamesClient->children(['geonameId' => $county]);
foreach($citiesJson as $cityJson) {
$citiesArray[$cityJson->toponymName] = $cityJson->toponymName;
}
}
$cities = null === $citiesArray ? [] : $citiesArray;
$form->add('city', ChoiceType::class, [
'placeholder' => 'Choose an option',
'mapped' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $cities,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
}
}
class LocationType extends AbstractType
{
private $transformer;
public function __construct(IssueToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('country', CountryType::class, [
'label' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'attr' => [
'class' => 'form-control'
],
//'empty_data' => ''
//'mapped' => false,
])
->add('county', EntityType::class, [
'class' => County::class,
'label' => false,
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'mapped' => false,
])
->add('city', ChoiceType::class, [
'label' => false,
'attr' => [ 'class' => 'form-control'],
'placeholder' => 'Choose an option',
//'choices' => ['Tirana' => 'Tirana'],
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'mapped' => false,
]);
$builder->get('city')->resetViewTransformers();
$builder->get('country')->addModelTransformer($this->transformer);
$builder->addEventSubscriber(new DynamicFieldsSubscriber());
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Location::class,
]);
}
}
First part is working, ok, but in the second part I see in controller is not capturing anything - https://prnt.sc/I9Gl0Z6ooCo_
In FormType county post submit is not working.
class LocationType extends AbstractType
{
private $transformer;
public function __construct(IssueToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('country', CountryType::class, [
'label' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'attr' => [
'class' => 'form-control'
],
//'empty_data' => ''
//'mapped' => false,
])
->add('county', ChoiceType::class, [
'label' => false,
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
])
->add('city', ChoiceType::class, [
'label' => false,
'attr' => [ 'class' => 'form-control'],
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
$builder->get('country')
->addModelTransformer($this->transformer);
$formModifier = function (FormInterface $form, Country $country = null) {
$counties_array = [];
// var_dump($country);
if ($country != null) {
$g = new GeoNamesClient('djmichael');
[$countryGeoNames] = $g->countryInfo([
'country' => $country->getName(),
]);
$country_name = $countryGeoNames->geonameId;
$counties_json = $g->children(['geonameId' => $country_name]);
foreach($counties_json as $counties_j) {
//dd($counties_j->toponymName);
$counties_array[$counties_j->toponymName] = $counties_j->geonameId;
}
}
$counties = null === $counties_array ? [] : $counties_array;
//var_dump($counties);
$form->add('county', ChoiceType::class, [
'placeholder' => 'Choose an option',
//'required' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $counties,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
};
$builder->get('country')->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$country = null;
if ($data != null) {
$country = $data->getCountry();
//dd($data);
}
$formModifier($event->getForm()->getParent(), $country);
}
);
$builder->get('country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$country = $event->getForm()->getData();
if ($country != null) {
//dd($country);
$formModifier($event->getForm()->getParent(), $country);
} else {
//dd($country);
}
}
);
$builder->get('county')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$county = $event->getForm()->getData();
dd($county);
if ($county != null) {
//dd($country);
$formModifier($event->getForm()->getParent(), $county);
} else {
//dd($country);
}
}
);
}
}
Related
Custom Choice type with parameters
I make my custom ChoiceType class CountryType extends ChoiceType { public function configureOptions(OptionsResolver $resolver): void { parent::configureOptions($resolver); $resolver->setDefaults([ 'choices' => $this->locationService->getCountries(), ]); } } class StateType extends ChoiceType { public function configureOptions(OptionsResolver $resolver): void { parent::configureOptions($resolver); $choices = isset($countryId)? $this->locationService->getStatesByCountryId($countryId):[]; <--- $resolver->setDefaults([ 'choices' => $choices, ]); } } class FormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('country', CountryType::class, [ 'label' => 'Country', 'required' => false, ])->add('state', StateType::class, [ 'label' => 'State', 'required' => false, ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => FormRequest::class, 'validation_groups' => [], ]); } } But for StateType optionally (when I edit entity) I need $countryId how I can put it? Maybe with options or choice_loader? I also tried to crank out such an idea, but as I understand it, this is not possible {{ form_widget(form.state, { 'countryId': form.country.vars.value, }) }}
Zend3 InputFilter not validating Form
I've found another post (ZF2 InputFilter not validating fieldset) for my issue, but it doesn't helped. I have a Category Entity and i want to validate the length of the title. So my Model (without the existing getter and setter) class Category { /** * #var int */ private $id; /** * #var string */ private $name; } My Form for the Category: class CategoryForm extends Form { public function init() { $this->setHydrator(new ClassMethods(false)); $this->setObject(new Category()); $this->setInputFilter(new CategoryFilter()); $this->add([ 'name' => 'id', 'type' => 'hidden' ]); $this->add([ 'name' => 'name', 'type' => 'text' ]); $this->add([ 'name' => 'submit', 'type' => 'submit' ]); } } And the Filter which currently not working. class CategoryFilter extends InputFilter { public function init() { $this->add([ 'name' => 'name', 'required' => true, 'filters' => [ ['name' => StringTrim::class] ], 'validators' => [ [ 'name' => StringLength::class, 'options' => [ 'min' => 5 ] ] ] ]); } } And if someone needed my addAction in the Controller: public function addAction() { $request = $this->getRequest(); if ($request->isPost()) { $this->form->setData($request->getPost()); if ($this->form->isValid()) { $this->mapper->save($this->form->getData()); $this->redirect()->toRoute('categories'); } } return [ 'form' => $this->form ]; } In every example which i found, it should be working. But my form is never validated nor filtered (with the trim). Did i forget something? Why it doesn't work?
So i've changed a little bit. The Category Form goes to: class CategoryForm extends Form { public function init() { $this->add(array( 'type' => CategoryFieldSet::class, 'options' => array( 'use_as_base_fieldset' => true, ), )); $this->add([ 'name' => 'submit', 'type' => 'submit' ]); } } I changed also the Filter class CategoryFilter extends InputFilter { public function __construct() { $this->add([ 'name' => 'name', 'required' => true, 'filters' => [ ['name' => StringTrim::class] ], 'validators' => [ [ 'name' => StringLength::class, 'options' => [ 'min' => 5 ] ] ] ]); } } And after this i defined the FieldSet with the validators: class CategoryFieldSet extends Fieldset implements InputFilterProviderInterface { /** * */ public function init() { $this->setHydrator(new ClassMethods(false)); $this->setObject(new Category()); $this->add([ 'name' => 'id', 'type' => 'hidden' ]); $this->add([ 'name' => 'name', 'type' => 'text', ]); } /** * Should return an array specification compatible with * {#link Zend\InputFilter\Factory::createInputFilter()}. * * #return array */ public function getInputFilterSpecification() { $filter = new CategoryFilter(); return $filter->getInputs(); } } After changing this i get the expected error message like: The input is less than 5 characters long
Unable to resolve service "Zend\Db\Adapter\AdapterInterface" to a factory
I am a newbie to zf.I downloaded the album project from github and started working on it . I did all the configuration as been written in the tutorial . But i am getting this error continuously . Unable to resolve service "Zend\Db\Adapter\AdapterInterface" to a factory; are you certain you provided it during configuration? I started looking for some solutions and found same issue posted in Stack Overflow and went through them but none of the solution worked for me . Below i am pasting the pages code , pls assist i am stuck here for 2 days now. I am working on my local system . I have windows 10 and xampp installed. Module.php <?php namespace Album; use Zend\Db\Adapter\Adapter; use Zend\Db\Adapter\AdapterInterface; use Zend\Db\ResultSet\ResultSet; use Zend\Db\TableGateway\TableGateway; use Zend\ModuleManager\Feature\ConfigProviderInterface; class Module implements ConfigProviderInterface { public function getConfig() { echo "Module.php"; return include __DIR__ . '/config/module.config.php'; } public function getServiceConfig() { return [ 'factories' => [ Model\AlbumTable::class => function ($container) { $tableGateway = $container->get('Model\AlbumTableGateway'); return new Model\AlbumTable($tableGateway); }, 'Model\AlbumTableGateway' => function ($container) { $dbAdapter = $container->get(AdapterInterface::class); $resultSetPrototype = new ResultSet(); $resultSetPrototype->setArrayObjectPrototype(new Model\Album()); return new TableGateway('album', $dbAdapter, null, $resultSetPrototype); }, ], ]; } public function getControllerConfig() { return [ 'factories' => [ Controller\AlbumController::class => function ($container) { return new Controller\AlbumController( $container->get(Model\AlbumTable::class) ); }, ], ]; } } module.config.php namespace Album; use Zend\Router\Http\Segment; return [ 'router' => [ 'routes' => [ 'album' => [ 'type' => Segment::class, 'options' => [ 'route' => '/album[/:action[/:id]]', 'constraints' => [ 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', 'id' => '[0-9]+', ], 'defaults' => [ 'controller' => Controller\AlbumController::class, 'action' => 'index', ], ], ], ], ], 'view_manager' => [ 'template_path_stack' => [ 'album' => __DIR__ . '/../view', ], ], ]; composer.json { "name": "zendframework/skeleton-application", "description": "Skeleton Application for Zend Framework zend-mvc applications", "type": "project", "license": "BSD-3-Clause", "keywords": [ "framework", "mvc", "zf" ], "homepage": "http://framework.zend.com/", "minimum-stability": "dev", "prefer-stable": true, "require": { "php": "^5.6 || ^7.0", "zendframework/zend-component-installer": "^1.0 || ^0.7 || ^1.0.0-dev#dev", "zendframework/zend-mvc": "^3.0.1", "zfcampus/zf-development-mode": "^3.0" }, "autoload": { "psr-4": { "Application\\": "module/Application/src/", "Album\\": "module/Album/src/" } }, "autoload-dev": { "psr-4": { "ApplicationTest\\": "module/Application/test/", "Album\\": "module/Album/src/" } }, "extra": [], "scripts": { "cs-check": "phpcs", "cs-fix": "phpcbf", "development-disable": "zf-development-mode disable", "development-enable": "zf-development-mode enable", "development-status": "zf-development-mode status", "post-create-project-cmd": [ "#development-enable" ], "serve": "php -S 0.0.0.0:8080 -t public public/index.php", "test": "phpunit" } } global.php return array( 'db' => array( 'driver' => 'Pdo', 'adapters' => array( 'default_db' => array( 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=zf2tutorial;host=localhost', 'username' => 'root', 'password' => '', 'driver_options' => array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' // optional ), ), ), ), ); AlbumController.php <?php namespace Album\Controller; use Album\Form\AlbumForm; use Album\Model\Album; use Album\Model\AlbumTable; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; class AlbumController extends AbstractActionController { private $table; public function __construct(AlbumTable $table) { $this->table = $table; } public function indexAction() { // Grab the paginator from the AlbumTable: $paginator = $this->table->fetchAll(true); // Set the current page to what has been passed in query string, // or to 1 if none is set, or the page is invalid: $page = (int) $this->params()->fromQuery('page', 1); $page = ($page < 1) ? 1 : $page; $paginator->setCurrentPageNumber($page); // Set the number of items per page to 10: $paginator->setItemCountPerPage(10); return new ViewModel(['paginator' => $paginator]); } public function addAction() { $form = new AlbumForm(); $form->get('submit')->setValue('Add'); $request = $this->getRequest(); if (!$request->isPost()) { return ['form' => $form]; } $album = new Album(); $form->setInputFilter($album->getInputFilter()); $form->setData($request->getPost()); if (!$form->isValid()) { return ['form' => $form]; } $album->exchangeArray($form->getData()); $this->table->saveAlbum($album); return $this->redirect()->toRoute('album'); } public function editAction() { $id = (int) $this->params()->fromRoute('id', 0); if (0 === $id) { return $this->redirect()->toRoute('album', ['action' => 'add']); } // Retrieve the album with the specified id. Doing so raises // an exception if the album is not found, which should result // in redirecting to the landing page. try { $album = $this->table->getAlbum($id); } catch (\Exception $e) { return $this->redirect()->toRoute('album', ['action' => 'index']); } $form = new AlbumForm(); $form->bind($album); $form->get('submit')->setAttribute('value', 'Edit'); $request = $this->getRequest(); $viewData = ['id' => $id, 'form' => $form]; if (!$request->isPost()) { return $viewData; } $form->setInputFilter($album->getInputFilter()); $form->setData($request->getPost()); if (!$form->isValid()) { return $viewData; } $this->table->saveAlbum($album); // Redirect to album list return $this->redirect()->toRoute('album', ['action' => 'index']); } public function deleteAction() { $id = (int) $this->params()->fromRoute('id', 0); if (!$id) { return $this->redirect()->toRoute('album'); } $request = $this->getRequest(); if ($request->isPost()) { $del = $request->getPost('del', 'No'); if ($del == 'Yes') { $id = (int) $request->getPost('id'); $this->table->deleteAlbum($id); } // Redirect to list of albums return $this->redirect()->toRoute('album'); } return [ 'id' => $id, 'album' => $this->table->getAlbum($id), ]; } }
configure your "config/autoload/local.php" Or "config/autoload/global.php", return array( 'db' => array( 'driver' => 'Pdo', 'adapters' => array( 'default_db' => array( 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=YOURDBNAME;host=localhost', 'username' => 'xxx', 'password' => 'xxx', 'driver_options' => array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' // optional ), ), ), ), 'service_manager' => array( 'factories' => array( 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory', ), 'abstract_factories' => array( 'Zend\Db\Adapter\AdapterAbstractServiceFactory', ), ), ); You can set your credential on "config/autoload/local.php". public function getServiceConfig() { return [ 'factories' => [ Model\AlbumTable::class => function ($container) { $tableGateway = $container->get('Model\AlbumTableGateway'); return new Model\AlbumTable($tableGateway); }, 'Model\AlbumTableGateway' => function ($container) { $dbAdapter = $container->get('default_db'); // return driver instance $resultSetPrototype = new ResultSet(); $resultSetPrototype->setArrayObjectPrototype(new Model\Album()); return new TableGateway('album', $dbAdapter, null, $resultSetPrototype); }, ], ]; } In config/application.config.php, return array( 'modules' => array( 'Zend\Db', // make sure you have `'Zend\Db'` to top update your composer.json file as below, "require": { "php": "^5.6 || ^7.0", "zendframework/zend-component-installer": "^1.0 || ^0.7 || ^1.0.0-dev#dev", "zendframework/zend-mvc": "^3.0.1", "zfcampus/zf-development-mode": "^3.0", "zendframework/zendframework": "^3.0", "zendframework/zend-db": "^2.8.2", }, for rendering update "module.config.php" with below, 'view_manager' => array( 'template_path_stack' => array( __DIR__ . '/../view', ), ),
I have also faced the same problem. I used minimal installation of zend framework. Check the composer.json whether you have installed the zend-db. If zend-db doesn't exist, install zend-db via composer, composer require zendframework/zend-db During installation, it will ask for configuration file injection, choose 1 for config.modules.php.
Submit array of tag names and convert to entities
I want to do such a trick: submit tag names as an array and then convert them to entities. Submitted data: {"title":"Title","description":"Desc", "tags": ["first", "second"]} Form: class ImageType extends AbstractType { /** * {#inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title') ->add('description') ->add('imageFile') ->add('tags', CollectionType::class, [ 'entry_type' => ImageTagType::class, 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false, ]); } /** * {#inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => Image::class, 'csrf_protection' => false, ]); } /** * {#inheritdoc} */ public function getBlockPrefix() { return 'appbundle_image'; } } Form for image tag: class ImageTagType extends AbstractType { /** * {#inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); } /** * {#inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => 'AppBundle\Entity\ImageTag' )); } /** * {#inheritdoc} */ public function getBlockPrefix() { return 'appbundle_imagetag'; } } The entities are connected with many-to-many. One image can have many tags and one tag can have many images. I am building an API and want to simplify tags submission. What should I do?
Use DataTransformer. Example: .... $builder->get('YOUR_FILED') ->addModelTransformer(new CallbackTransformer( function ($array) { // your implementation here }, function ($array) { if (!$array) { return; } return new ArrayCollection(array_map(function ($data) { return $this->someRepository->findOneBy(['FIELD' => $data]); }, $array)); } )); ....
How to change migration db connection
I have more than one connection setting in config/database.php, I can and have to connect to several database in the same case, this is how I try to do but not work. <?php class Migration_Create_loggers_table extends CI_Migration { public function __construct() { parent::__construct(); $this->load->database('master'); } public function up() { $this->dbforge->add_field([ 'id' => [ 'type' => 'int', 'unsigned' => true, 'auto_increment' => true ], 'name' => [ 'type' => 'varchar', 'constraint' => 50 ] ]); $this->dbforge->add_key('id', true); $this->dbforge->create_table('members'); } public function down() { $this->dbforge->drop_table('members'); } }
I haven't tried but I think you are missing parent::__construct(); at your __construct method So your __construct method should be like this class Migration_Create_loggers_table extends CI_Migration { public function __construct() { $this->load->database('master'); parent::__construct();//add this line } public function up() { $this->dbforge->add_field([ 'id' => [ 'type' => 'int', 'unsigned' => true, 'auto_increment' => true ], 'name' => [ 'type' => 'varchar', 'constraint' => 50 ] ]); $this->dbforge->add_key('id', true); $this->dbforge->create_table('members'); } public function down() { $this->dbforge->drop_table('members'); } }