Help With Embedded BaseFormDoctrine Forms - symfony1

I have the following code blocks:
class MerchantStoreForm extends sfForm
{
public function configure()
{
$this->disableCSRFProtection();
$this->setWidgets(array(
'brand_id' => new sfWidgetFormDoctrineChoice(array('label'=> 'Store Brand','model'=>'Brand','add_empty'=>'-Select Brand-','method'=>'getName','key_method'=>'getId','order_by'=>array('name','asc'))),
'newbrand' => new sfWidgetFormInputCheckbox(array('label' => 'New'),array('value'=>'Y'))
));
$this->setValidators(array(
'newbrand' => new sfValidatorString(array('required'=>false)),
'brand_id' => new sfValidatorDoctrineChoice(array('model'=>'Brand'))
));
$brand = new Brand();
$brand_form = new BrandForm();
$brand_form->widgetSchema['name']->setAttribute('style','display:none');
$this->embedForm('brand', $brand_form);
$this->getWidgetSchema()->setNameFormat('store[%s]');
}
public function execute()
{
$form_values = $this->getValues();
if($form_values['newbrand'])
{
$brand_form = $this->getEmbeddedForm('brand');
$brand_form->save();
$brand = $brand_form->getObject();
}
else
{
$brand = doctrine::getTable('Brand')->findOneById($form_values['brand_id']);
}
return $brand->getId();
}
}
Two questions:
1) The magic of $brand_form->save() doesn't work for me. I get a 500 Internal Server Error sfValidatorErrorSchema error pointing to the following piece of code in my symfony generated BaseBrandForm.class.php:
...
$this->widgetSchema->setNameFormat('brand[%s]');
$this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
...
This works instead in replacement though:
$brand_form->updateObject($form_values['brand']);
$brand_form->getObject()->save();
Why is this?
2) Why do I get an undefined method error while calling getter method on the object of the BaseFormDoctrine embedded form:
return $brand->getId();
Thanks in advance for your help.
Sharmil

1) BrandForm throws an exception because it doesn't have any values. Classes that extend sfFormObject don't play nicely when embedded directly into non object forms (like sfForm).
What is MerchantStoreForm doing? Depending on the situation, it should probably be extending sfFormObject or BrandForm should be the top level form. If this isn't possible, you'll have to write add a save method to MerchantStoreForm that calls updateObject and save. To better understand what's happening, go through the logic that takes place in sfFormObject - it's worth knowing especially if you're using embedded forms.
2) No clue here. I would see what $brand is actually an instance of. If it's a record and that record has an id field, there's no reason that shouldn't work.

Related

Are Factories Using An IoC Container A Service Locator?

Lets say I have a factory returning different classes via methods.
class CarFactory
{
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function createCarOne() : CarInterface
{
return $this->container->make(CarOneClass::class);
}
// Vs
public function createCarTwo() : CarInterface
{
return new CarTwoClass({Inject Dependencies Here});
}
}
When would this be considered a service locator or anti-pattern and why? I am considering the first method solely for the dependency resolution provided by the container. All car's have the same typed interface dependencies the main difference of the entities come from how they transform the data provided.
Whenever one of these methods are called I need a new instance of the specified car so the data set can be transformed based on the choice.
This is not the implementation but the easiest example I can provide.
$output = [];
foreach ($car as $key => $data) {
$newCar = $this->factory->createCar{$key}();
// Pass Some Data To The New Car Methods So It Can Be Transformed
$output[] = $newCar;
}
return $output;
If this is the wrong approach what would be the alternative option?
Edit
After further digging I see some IoC containers pass factory callables as dependencies. I was going to bind each Car to a callable but thanks to the ability to type hint data from method returns (php7) I can configure factories using a provider then call the 'callable factory' from within the CarFactory. Requires additional binding but prevents the need to reference/dependency inject the IoC container within every factory.
Still researching I would love to hear feedback from those with more experience.
Ex:
// Within Some Registered Provider
// I Will Have To Wire Each Car
$one = function() use ($app) {
return $app->make(CarOne::class);
};
$two = function() use ($app) {
return $app->make(CarTwo::class);
};
$app->bind(ICarFactory::class, function($app) use ($one, $two) {
return $app->make($concrete, [$one, $two]);
});
// Car Factory Constructor
public function __construct(callable $carOne, callable $carTwo) {
$this->one = $carOne;
$this->two = $carTwo;
}
Since get methods are type hinted ( view original car factory ) an error is thrown when the returned item does not implement CarInterface, each factory method would just have to call the 'callable factory' ( something like this return ($this->one)();).
I believe i solve my problem of outsourcing creation of dependencies ( avoiding creating within factory was bothering the hell out of me ) while still following 'best practices'. Still looking for advice if anyone has any to offer.

Selected attributes of genemu_jqueryselect2_entity not stored to database

I'm using genemu_jqueryselect2_entity for a multiple selection field within a form (located in an Sonata admin class) for a so called Uni (university) entity:
->add('courses', 'genemu_jqueryselect2_entity',array('multiple' => true, 'class' => 'PROJECT\UniBundle\Entity\Course'))
But the selected entries are not filled into my entity. With firebug I was able to detect, that the ids of the courses are passed correctly via POST.
Maybe the field is not correctly mapped to the Uni entity, but I have no idea why.
This is the adding method of my Uni entity, which doesn't even get called:
public function addCourse(\PROJECT\UniBundle\Entity\Course $courses)
{
$this->courses[] = $courses;
return $this;
}
How can I get the field to be mapped with the courses attribute of Uni? How could I debug this?
Any help will be appriciated!
Try writing that method like this:
public function addCourse(\PROJECT\UniBundle\Entity\Course $course)
{
$this->courses[] = $course;
$course->setUniversity($this); // Or similar.
return $this;
}
Otherwise foreign key is not set on a course row in the DB.
Try to create method setCourses
public function setCourses(\Doctrine\Common\Collections\Collection $courses)
{
$this->courses = $courses;
...
I don't know why, but the method addCourse isn't called.
Anyway, Tautrimas Pajarskas's answer was usefull to me so I gave an upvote.
The foreign key relationship was the necessary and missing part of my code.
I implemented it in the university sonata admin like this:
private function addUniToCourses ($university) {
foreach($university->getCourses() as $course) {
if(!$course->getUniversities()->contains($university)) {
$course->addUniversity($university);
}
}
}
public function prePersist($university) {
$this->addUniToCourses($university);
}
public function preUpdate($university) {
$this->addUniToCourses($university);
}
This was the solution to my problem.
I had the same problem a while ago: Symfony2, $form->bind() not calling adder methods of entity
Solution:
For the adder (addCourse()) to be called, you have to disable the by_reference option of the field:
->add('courses', 'genemu_jqueryselect2_entity',
array(
'by_reference' => false, // This line should do the trick
'multiple' => true,
'class' => 'PROJECT\UniBundle\Entity\Course'))

Set layout variables for use by the (404) error pages in ZF2

At present I set a couple of variables to be used by the app's overall layout.phtml, using the onDispatch method of a BaseController, which all my other controllers extend:
public function onDispatch(MvcEvent $e)
{
$config = $this->getServiceLocator()->get('config');
$this->layout()->setVariable('platformName', $config['platform']['name']);
$this->layout()->setVariable('platformYear', $config['platform']['year']);
}
This works fine, until I test some error pages and find that these pages do not get provided with the variables, as it's not using the base controller.
How can I get around this problem and provide the error pages with the same variables?
Change the event you're listening for.
In this case, I'd move this logic to the application bootstrap event or the application render event (I haven't tested this, but it would probably work fine).
One example, in your Module.php
public function onBootstrap($e)
{
$config = $e->getApplication()->getServiceManager()->get('config');
//$e->getViewModel()->setVariable();
}
Haven't tested that commented out line, but it should get you headed in the right direction.
EDIT: Found an example of using the render event
public function onBootstrap($e)
{
$event = $e->getApplication()->getEventManager();
$event->attach('render', function($e) {
$config = $e->getApplication()->getServiceManager()->get('config');
$e->getViewModel()->setVariable('test', 'test');
});
}
(Necro)
When using onDispatch in a Controller, remember to return the parent with the event and all:
public function onDispatch(MvcEvent $e)
{
// Your code
return parent::onDispatch($e);
}
Otherwise, the logic on your Actions in that Controller will be ignored.

Zend Framework 2: Cannot attach to 'dispatch' event

I am currently learning/experimenting with the stable version of ZF2. The last couple of days has been used trying to find a solution to my problem, which is: I want to be able to write some setup logic general to a set of controllers. In ZF! I would then just write a general controller and derive from it, using the init() method for my setup logic. After a bit of searching I found that the init() method was removed in ZF2 and that there were alternative approaches to get the same functionality.
I tried to follow the guide by M. W. O'Phinney: http://mwop.net/blog/2012-07-30-the-new-init.html
In my case I have to be able to retrieve and check route params for my setup logic, so the method overriding alternatives didn't work due to one not having access to the MvcEvent at that point. So, I tried the Update: serviceManager solution, and this is where I got stuck. First I just tried to copy the code from the guide into my Module class and echo some text to see if the callback was at all called. Which it isn't.
After more searching on the web i found a possible solution; attaching the callback in the constructor of the general controller. The same problem appeared to be here as well. The constructor gets called of course, but the callback is either not attached or triggered properly (or at all).
I'll attach some of my code from the two different solutions:
In Module.php:
public function getControllerConfig() {
return array(
'factories' => array(
'Game\Controller\Mapsquare' => function($controllers) {
$serviceManager = $controllers->getServiceLocator();
$eventManager = $serviceManager->get('EventManager');
$controller = new Controller\MapsquareController();
echo "this text is echoed";
$eventManager->attach('dispatch', function ($e) use ($controller) {
echo "this text is NOT echoed";
$request = $e->getRequest();
$method = $request->getMethod();
if (!in_array($method, array('PUT', 'DELETE', 'PATCH'))) {
// nothing to do
return;
}
if ($controller->params()->fromRoute('id', false)) {
// nothing to do
return;
}
// Missing identifier! Redirect.
return $controller->redirect()->toRoute(/* ... */);
}, 100); // execute before executing action logic
$controller->setEventManager($eventManager);
return $controller;
}
)
);
}
In MapsquareController.php (the general controller):
public function __construct() {
$this->getEventManager()->attach('dispatch', array($this, 'preDispatch'), 1000);
echo "construct";
}
public function preDispatch() {
echo "This is preDispatch()!";
}
Is there someone out there that can help me with this problem, eventually tell what I'm missunderstanding here? Any help is appreciated :)
You cannot attach to dispatch from a factory because factory is called a long time after the dispatch has been processed.
To make it work, open your Module.php and edit onBootstrap(), so that you attach there.
For example:
public function onBootstrap($e)
{
...
$eventManager = $e->getApplication()->getEventManager();
$eventManager->attach("dispatch", function($e) {
echo "Dispatch!";
});
}
Alternatively, you can also do this from a specific controller. Not in the constructor, but by overriding setEventManager:
public function setEventManager(EventManagerInterface $events) {
parent::setEventManager($events);
$controller = $this;
$events->attach("dispatch", function($e) use ($controller) {
echo "Dispatch!";
});
}
Hope this helps!
Pls note that the accepted is not correct. The code posted by OP is simply not functional. Never polute your onBootstrap with cross-cutting concerns. The reason this:
public function __construct() {
$this->getEventManager()->attach('dispatch', array($this, 'preDispatch'), 1000);
echo "construct";
}
didn't work is because the event manager gets attached AFTER construction, and fires on dispatch.
The comment about "factory is called a long time after the dispatch" makes no sense also. Do you know what a factory does?
The suggestion by "Mr. O'phinney" is the correct way, maybe read the manual better next time before you decide someone like Matthew disappointed you..
Solution is here: http://mwop.net/blog/2012-07-30-the-new-init.html
Evan Coury's post on Module-specific layouts in Zend Framework 2 seems to give a solution, it works for me.
http://web.archive.org/web/20140623024023/http://blog.evan.pro/module-specific-layouts-in-zend-framework-2

Are the Drivers and Controllers in Orchard somewhat the same?

I'm creating a new widget in the Orchard CMS. The way I do this is by adding a Route and Controller first and try out the functionality by running the code on an url defined in the Route, like http://localhost:30320/Index
My Routes.cs is set up so it routes this request to the specified controller and stuff is happening on the screen. This all works quite well.
Now that I'm happy with the result I tried placing all of this in a new Widget. For this I've created new Migrations class which sets up the widget. This is fairly straightforward and now the widget has been added to the Homepage layer.
The thing I'm running into is the Controller isn't executed anymore. Not very strange as I haven't set up any routes which specify the Controller should be executed. I'm wondering, should I move the Controller logic to the Driver method, so the View still gets the necessary information?
The driver I've got at the moment looks like this:
public class FrontpageDrivers : ContentPartDriver<FrontpageModelPart>
{
protected override DriverResult Display(FrontpageModelPart part, string displayType, dynamic shapeHelper)
{
//return base.Display(part, displayType, shapeHelper);
if (displayType.StartsWith("Detail"))
return ContentShape("Parts_Index", () => shapeHelper.Parts_Index(
LatestPostCollection: part.LatestPostCollection,
TopRatedPostCollection: part.TopRatedPostCollection,
TotalMonthCollection: part.TotalMonthCollection,
ContentPart: part
));
return null;
}
}
The PartsController method which needs to be executed looks like this:
[HttpGet]
public ActionResult Detail()
{
//Do something to get blogposts
var getter = new GetBlogPost(_blogService, _blogPostService, _votingService);
getter.Initialize();
var latestPosts = getter.GetLatestPosts();
var highestRankedPosts = getter.GetHighestRankedPosts();
var archiveData = getter.GetTotalPostsPerMonth();
var viewModel = new FrontpageModelPart();
viewModel.LatestPostCollection = latestPosts;
viewModel.TopRatedPostCollection = highestRankedPosts;
viewModel.TotalMonthCollection = archiveData;
return View("Index", viewModel);
}
I've tried renaming the method to Index and Detail, both won't do the trick.
The view which is shown is /Views/Parts/Index.cshtml. If I put some static text in the file, I can see this view is being rendered correctly.
So, should I move the Controller logic to the Driver, or am I forgetting something in the setup?
Note: I've got the placement, module, migrations and handler in place already.
Edit:
If I'm using this code, everything works quite well:
protected override DriverResult Display(FrontpageModelPart part, string displayType, dynamic shapeHelper)
{
var controller = new PartsController(Services, _blogService, _blogPostService, _votingService);
part = controller.GetIndexViewModel();
if (displayType.StartsWith("Detail"))
return ContentShape("Parts_Index", () => shapeHelper.Parts_Index(
LatestPostCollection: part.LatestPostCollection,
TopRatedPostCollection: part.TopRatedPostCollection,
TotalMonthCollection: part.TotalMonthCollection,
ContentPart: part
));
return null;
}
Even though this works quite well, it just feels like 'hacking' to me...
Use controllers when you want to take over everything that appears in the Content zone, but return a shape result so that the theme, widgets, etc can still chime in. That's what your controller fails to do.

Resources