I'm currently learning ZF2 by developing a small MVC application roughly based on the skeleton app. Right now I'm trying to hide some fixed HTML elements based on the route matched: just as an example, I don't want the main menu to show during the login phase.
I can do that easily by passing toggle parameters as return values from the controller actions, but it doesn't feel right, so I'd like to just check the matched route from the layout and compose the layout accordingly.
Problem is, I don't know how to get the matched route in the template. Is this possible? Are there other solutions to avoid adding layout logic into controllers?
Edit after some good Frankenstein work, I was able to find a solution for this. I like the idea of using a helper, so I just tried to pass it the Application object, from the boostrap function in the main module:
$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
....
$serviceManager->get('viewhelpermanager')->setFactory('getRoute', function($sm) use ($app) {
return new Helper\GetRoute($app);
});
and the helper function:
use Zend\View\Helper\AbstractHelper;
class GetRoute extends AbstractHelper {
private $sm;
public function __construct($app) {
$this->sm = $app->getServiceManager();
}
public function echoRoute() {
$router = $this->sm->get('router');
$request = $this->sm->get('request');
$routeMatch = $router->match($request);
if (!is_null($routeMatch))
echo $routeMatch->getMatchedRouteName();
}
}
perhaps there's a cleaner, more ZF2ish way to do this...
Another solution without a new match
$routeMatch = $serviceLocator->get('Application')->getMvcEvent()->getRouteMatch();
echo $routeMatch->getMatchedRouteName();
There is a way to get service manager in layout:
$sm = $this->getHelperPluginManager()->getServiceLocator();
and then you can access $sm->get('router') etc.
You could create a View helper that implements ServiceManagerAwareInterface. Then inside the View helper using the ServiceManager instance to retrieve both the router and request objects then reconstruct the route match.
$services = $this->getServiceManager();
$router = $services->get('router');
$request = $services->get('request');
$routeMatch = $router->match($request);
echo $routeMatch->getMatchedRouteName();
I'd also recommend writing the View helper so that code only triggers once per request.
When moving to ZF3, you should consider use this method... since getLocator isn't available anymore (and it's not correct inject it).
Create the Helper
namespace Application\View\Helper;
use Zend\Http\Request;
use Zend\Router\RouteStackInterface;
use Zend\View\Helper\AbstractHelper;
/**
* Helper to get the RouteMatch
*/
class RouteMatch extends AbstractHelper
{
/**
* RouteStackInterface instance.
*
* #var RouteStackInterface
*/
protected $router;
/**
* #var Request
*/
protected $request;
/**
* RouteMatch constructor.
* #param RouteStackInterface $router
* #param Request $request
*/
public function __construct(RouteStackInterface $router, Request $request)
{
$this->router = $router;
$this->request = $request;
}
/**
* #return \Zend\Router\RouteMatch
*/
public function __invoke()
{
return $this->router->match($this->request);
}
}
Create a Factory for this helper
namespace Application\View\Helper;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class RouteMatchFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$router = $container->get('router');
$request = $container->get('request');
return new RouteMatch($router, $request);
}
}
Call your Factory on your Module.php and create an Alias for it.
public function getViewHelperConfig()
{
return array(
'factories' => array(
RouteMatch::class => RouteMatchFactory::class,
),
'aliases' => array(
'routeMatch' => RouteMatch::class,
)
);
}
That's it... you have a RouteMatch Helper using the new ZF3 standards.
Bye!
In view you can use:
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->getPath();
or
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->toString();
I believe you can solve it by finding the action/controller names:
$controller = $this->getRequest()->getControllerName();
$action = $this->getRequest()->getActionName();
Once you know the action, you can have specific conditions to enable sections of the layout.
I view you can use
$this->getHelperPluginManager()->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getMatchedRouteName();
Additional information about "Rodrigo Boratto" post for integrating getRouteMatch in ZF3 (I can't comment because I have under 50 repo...)
In view helper file these line:
use Zend\Mvc\Router\RouteMatch as MvcRouteMatch;
use Zend\Mvc\Router\RouteStackInterface;
should be:
use Zend\Router\RouteMatch as MvcRouteMatch;
use Zend\Router\RouteStackInterface;
I don't know when they made that change but the files are in Zend\Router namespace.
P.S. I use composer if that matters.
My controller:
<?PHP
namespace SomeName\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class SomeController extends AbstractActionController
{
public function getIdAction()
{
$id = $this->params()->fromRoute('id', 0);
return new ViewModel(array(
'id' => $id,
));
}
}
My Router:
<?php
return array(
'controllers' => array(
'invokables' => array(
'SomeName\Controller\Some' => 'SomeName\Controller\SomeController',
),
),
'router' => array(
'routes' => array(
'testId' => array(
'type' => 'segment',
'options' => array(
'route' => '/[:id]',
'constraints' => array(
'id' => '\d*',
),
'defaults' => array(
'controller' => 'SomeName\Controller\Some',
'action' => 'getId',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'album' => __DIR__ . '/../view',
),
),
);
At any view or layout, you are able to test route with this function:
<?php function itsRoute($routeName){
$flag = false;
if($this->serverUrl(true) == $this->url($route,[],['force_canonical'=>true]))){
$flag = true;
}
return $flag;
}
Related
I can't seem to get data transferred after I submit a create form of a model record to a mailable. I think I'm right on most of it, but I'm stuck at passing the data through the gap between a controller and my mailable.
I have a shipment controller with the following function:
public function store(Request $request)
{
$this->validate(request(), [
'pro_number' => 'required',
'shipment_origin' => 'required'
/*'piecesNumber' => 'required' (had to remove for now, but must review)*/
]);
$user_id = Auth::id();
$input = $request->all();
//Save Initial Shipment Data
$shipment = new Shipment();
$shipment->pro_number = request('pro_number');
$shipment->shipment_origin = request('shipment_origin');
$shipment->date = request('date');
$shipment->due_date = request('due_date');
$shipment->tractor_id = request('tractor_id');
$shipment->trailer_id = request('trailer_id');
$shipment->driver_id = request('driver_id');
$shipment->notes = request('notes');
$shipment->shipper_no = request('shipper_no');
$shipment->ship_to = request('ship_to');
$shipment->ship_from = request('ship_from');
$shipment->bill_to = request('bill_to');
$shipment->bill_type = request('bill_type');
$shipment->load_date = request('load_date');
$shipment->shipment_status = 0;
$shipment->created_by = $user_id;
$shipment->save();
//Save Shipment Details
for ($i = 0; $i < count($request->shipment_details['piecesNumber']); $i++) {
Shipment_Detail::create([
'shipment_id' => $shipment->id,
'pieces_number' => $request->shipment_details['piecesNumber'][$i],
'pieces_type' => $request->shipment_details['piecesType'][$i],
'rate_type' => $request->shipment_details['rateType'][$i],
'charge' => $request->shipment_details['charge'][$i],
'weight' => $request->shipment_details['weight'][$i],
'hazmat' => $request->shipment_details['hazmat'][$i],
'description' => $request->shipment_details['description'][$i] ]);
}
$user = Auth::user()->email;
Mail::to($user)->send(new newFreightBill($shipment));
Session::flash('success_message','Freight Bill Successfully Created'); //<--FLASH MESSAGE
//Return to Register
return redirect('/shipments/i/'.$shipment->url_string);
}
Now as you can see there is a line near the bottom "Mail::to($user)->send(new newFreightBill($shipment));", what I would like to be doing is moving the copying the data from the submitted $shipment to the mailable
My newFreightBill mailable:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Shipment;
class newFreightBill extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $shipment;
public function __construct(Shipment $shipment)
{
$shipment = $this->shipment;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('MYEMAIL')
->view('emails.shipments.created')->with(['shipment', $this->shipment]);
}
}
and my mail template (which I do receive in my tests):
<div>
Pro Number: {{ $shipment['pro_number'] }}
</div>
As you can see the template is VERY simple to say the least. I get in my emails to me the following:
Pro Number:
but absolutely nothing else, no actual number. SO I know the issue is passing the data from the form submit to the mailable variables.
If you are using properties on your Mail class such as public $shipment, you shouldn't use with() method. The properties on your class are available on your view without the need of using with.
And then you should do this: Pro Number: {{ $shipment->pro_number }}
Oh yes, and this: $shipment = $this->shipment; is backwards, it should be $this->shipment = $shipment;.
I have a model that has a one to many relationship to the versions of the description.
In my Controller
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
]);
$tag->update([
'content' => $request->get('description')
]);
In my Model:
public function setContentAttribute(string $value)
{
$this->versions()->create([
'user_id' => \Auth::id(),
'value' => $value
]);
}
So I can't put content directly as an attribute in the create method because there is no Model right now.
But is it possible to overwrite the create Method?
When I try to overwrite something like this in my Model it will do an infinity loop
public static function create($attr) {
return parent::create($attr);
}
So my question is if it is possible to have something like this:
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
'content' => $request->get('content')
]);
and in the Model:
public static function create($attr) {
$value = $attr['content'];
$attr['content'] = null;
$object = parent::create($attr);
$object->content = $value;
$object->save();
return $object;
}
Update
I didn't overwrite the create method but called it customCreate. So there is no infinity loop anymore and I can pass all variables to the customCreate function that handles the relationships for me.
Solution
After reading the changes from 5.3 to 5.4 it turns out that the create method was moved so you don't have to call parent::create() anymore.
The final solution is:
public static function create($attr) {
$content = $attr['content'];
unset($attr['content']);
$element = static::query()->create($attr);
$element->content = $content;
$element->save();
return $element;
}
I don't see why not and you could probably implement a more general approach? Eg. checking if set{property}Attribute() method exists, if it does - use it to assign a value, if it doesn't - use mass assigning.
Something like:
public static function create($attr) {
$indirect = collect($attr)->filter(function($value, $property) {
return method_exists(self::class, 'set' . camel_case($property) . 'Attribute');
});
$entity = parent::create(array_diff_key($attr, $indirect->toArray()));
$indirect->each(function($value, $property) use ($entity) {
$entity->{$property} = $value;
});
$entity->save();
return $entity;
}
I haven't really tested it but it should work. I use something like this in one of my Symfony apps.
sorry if this has been asked before, I looked around but haven't found this specific question on StackOverFlow.com.
I have a view called 'view-post-wall' which I'm trying to add the form that submits posts to this view called 'post' via ajax submit, though I haven't begun adding ajax yet.
My module's name is 'friendicate'
I don't understand what I'm missing here, I'm following a tutorial and have been unable to get past this issue for 2 days now.
I don't get any errors either.
Here is the module code in full
function _form_post_ajax_add() {
$form = array();
$form['title'] = array(
'#type' => 'textfield',
'#title' => 'Title of post',
);
$form['body'] = array(
'#type' => 'textarea',
'#title' => 'description',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit post',
);
return $form;
}
function post_ajax_preprocess_page(&$variables) {
//krumo($variables);
$arg = arg();
if($arg[0] == 'view-post-wall') {
$variables['page']['content']['system_main']['main']['#markup'] = drupal_render(drupal_get_form('_form_post_ajax_add'));
}
}
There are multiple ways to accomplish this, and I'll outline those methods below. Also, if nothing works from my suggestions below, it's possible that you have an invalid form function name. Im not sure if that throws off Drupal or not. The correct format for the function name should end in _form and contain the arguments $form and $form_state, like so:
_form_post_ajax_add_form($form, &$form_state) { ... }
Also, if you want to use a hook, Steff mentioned in a comment to your question that you'll need to use your module name in the function name.
friendicate_preprocess_page(&$variables) { ... }
Ok, now for a few ideas how to get the form on the page.
Block
You can create a custom block within your module, and then assign it to a region in admin/structure/blocks
<?php
/**
* Implements hook_block_info().
*/
function friendicate_block_info() {
$blocks = array();
$blocks['post_ajax'] = array(
'info' => t('Translation Set Links'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function friendicate_block_view($delta = '') {
$block = array();
if ($delta == 'post_ajax') {
$form = drupal_get_form('_form_post_ajax_add_form');
$block['content'] = $form;
}
return $block;
}
Clear the cache and your block should appear in admin/structure/blocks
Views attachment before/after
You can add markup before and after a view using the Views hook hook_views_pre_render()
<?php
/**
* Implements hook_view_pre_render().
*/
function frendicate_views_pre_render(&$view) {
if($view->name == 'view_post_wall') { // the machine name of your view
$form = drupal_get_form('_form_post_ajax_add_form');
$view->attachment_before = render($form);
}
}
Or maybe use view post render
function friendicate_views_post_render(&$view, &$output, &$cache) {
//use the machine name of your view
if ($view->name == 'view_post_wall') {
$output .= drupal_render(drupal_get_form('_form_post_ajax_add'));
}
}
I would like to add some strings/values to the end of the generated pagination link.
For example, I get this
http://localhost/products/lists/5
I would like to have
http://localhost/products/lists/5/value/anothervalue
So, I need to send those values somehow... :)
Thank u all.
The pagination class has an undocumented configuration option called suffix that you can use. Here's how I use it in one of my apps:
// get the current url segments and remove the ones before the suffix
// http://localhost/products/lists/5/value/anothervalue
$args = $this->uri->segment_array();
unset($args[1], $args[2], $args[3]);
$args = implode('/', $args);
// $args is now 'value/anothervalue'
$base_url = 'http://localhost/products/lists';
$this->pagination->initialize(array(
'base_url' => $base_url,
'suffix' => '/'.$args,
'first_url' => $base_url.'/1/'.$args,
'uri_segment' => 3
));
The application/config/routes.php
$route['products/lists/(:num)/value/(:any)'] = "products/lists/$1/$2";
The controller code application/controllers/products.php
class Products extends CI_Controller {
public function index() {
$this->load->view('welcome_message');
}
public function lists($page = 1, $value = null) {
$this->load->view('product_lists', array('page' => $page, 'value' => $value));
}
}
In this way if your url is like http://localhost/products/lists/5/value/anothervalue
in function lists will be $page = 5 and $value = 'anothervalue' and they will be available in template product_lists ($page, $value)
i'm building a mobile app talking to my symfony2 app via webservices
I can't find a way to disable csrf protection on a specific controller/action
i want to post registration data to this action and use sf2 form validation. I do not call the form in my mobile app
Can't change container parameters in action, throw an exception because it is a frozen parameter...
I do not want to disable form protection for whole my application
any clue ?
thanks !
update: with symfony 2.1.x
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
));
}
If you're looking for a bit easier and faster solution than suggested in answer above, here's how:
<?php
// ...
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
));
}
}
.. or if you're using older versions (Symfony 2.0.*):
<?php
// ...
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class MyType extends AbstractType
{
// ....
public function getDefaultOptions(array $options)
{
$options = parent::getDefaultOptions($options);
$options['csrf_protection'] = false;
return $options;
}
}
Consult the Symfony documentation for additional information.
Edit: updated answer to latest Symfony version, thanks
naitsirch
Using the form factory
For those who want to create a simple form in a controller:
$form = $this->container->get('form.factory')
->createNamedBuilder(null, 'form', null, array('csrf_protection' => false))
->add('yourField','text', array(
'label' => false,
'mapped' => false
))
->getForm();
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
Using the form factory in Symfony 3
use Symfony\Component\Form\Extension\Core\Type\FormType;
$form = $this->container->get('form.factory')
->createNamedBuilder(null, FormType::class, null, array('csrf_protection' => false))
->add('yourField','text', array(
'label' => false,
'mapped' => false
))
->getForm();
Adapted from Mick's answer
I can't be 100% sure but I think I read somewhere that you can pass csrf_provider option while creating form.
All providers are subtypes of interface Symfony\Component\Form\Extension\Csrf\CsrfProvider and you should be able to create your own:
class MyNonCsrfProvider extends DefaultCsrfProvider{
public function isCsrfTokenValid($intention, $token)
{
return true;
}
}
and in controller:
$this->createForm(new CustomFormType(), array(
'csrf_provider' => new MyNonCsrfProvider()
));
I haven't tried this myself but this sounds like a possible solution...