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');
}
}