I have a dynamic category navigation. In the navigation factory I want to get a param from the route. How can I do this?
In my view
<?php echo $this->navigation('CategoryNavigation')->menu()->setUlClass('list-group'); ?>
In my module.php:
public function getServiceConfig()
{
return array(
'factories' => array(
'CategoryNavigation' => 'Application\Navigation\CategoryNavigation',
)
);
}
This is my navigation factory. I need to get the slug from the route where {{ store slug }} (2x) is defined.
<?php
namespace Application\Navigation;
use Zend\ServiceManager\ServiceLocator;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Navigation\Service\DefaultNavigationFactory;
class CategoryNavigation extends DefaultNavigationFactory
{
protected $sl;
protected function getPages(ServiceLocatorInterface $serviceLocator)
{
if (null === $this->pages) {
$em = $serviceLocator->get('Doctrine\ORM\EntityManager');
$storerepository = $em->getRepository('ApplicationShared\Entity\Store');
$store = $storerepository->findOneBy(array('slug' => '{{ store slug }}'));
$fetchMenu = $store->getCategories();
$configuration['navigation'][$this->getName()] = $this->buildNavigation($fetchMenu);
if (!isset($configuration['navigation'])) {
throw new Exception\InvalidArgumentException('Could not find navigation configuration key');
}
if (!isset($configuration['navigation'][$this->getName()])) {
throw new Exception\InvalidArgumentException(sprintf(
'Failed to find a navigation container by the name "%s"',
$this->getName()
));
}
$application = $serviceLocator->get('Application');
$routeMatch = $application->getMvcEvent()->getRouteMatch();
$router = $application->getMvcEvent()->getRouter();
$pages = $this->getPagesFromConfig($configuration['navigation'][$this->getName()]);
//
$this->pages = $this->injectComponents($pages, $routeMatch, $router);
}
return $this->pages;
}
protected function buildNavigation($elements, $parentid = null){
foreach ($elements as $index => $element) {
if (!$element->getParent()) {
$branch[$element->getCategoryName()] = $this->navigationItem($element);
}
}
return $branch;
}
protected function navigationItem($element){
$branch['label'] = $element->getCategoryName();
$branch['route'] = 'store/type/catalog';
$branch['params'] = array('slug' => '{{ store slug }}','category' => $element->getSlug());
if(count($element->getChildren()) > 0){
foreach($element->getChildren() as $element){
$branch['pages'][] = $this->navigationItem($element);
}
}
return $branch;
}
}
Or are there better way's to achieve this?
Searching for a few hours, in the end asking it on stack, and now I find the answer in a half hour.
I just had to ad this in my factory
$router = $serviceLocator->get('router');
$request = $serviceLocator->get('request');
// Get the router match
$routerMatch = $router->match($request);
$this->slug = $routerMatch->getParam("slug");
Or the following for ZF3:
$container->get('Application')->getMvcEvent()->getRouteMatch()->getParam('whateverYouWant', false);
Related
I'm currently upgrading from Symfony 2.3 to 2.8 and are deprecating for the 3.0 update.
Under the link below, I rewrite Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList and Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList.
Among them, the following error occurred.
I wondered if I should add getChoices(), so I added it to ChoiceLoader.php, but I still got an error.
How should I solve it?
Also, choice_list will be abolished soon, so I plan to rewrite it.
https://github.com/symfony/symfony/blob/2.7/UPGRADE-2.7.md
Error
Attempted to call an undefined method named "getChoices" of class
"Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory"
ChoiceList->ChoiceLoader.php
namespace Ahi\Sp\AdminBundle\Form\ChoiceList;
//Remove
//use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
//use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
//Add
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class StaffChoiceLoader implements ChoiceLoaderInterface
{
private $staffService;
private $loginStaff;
private $currentStaff;
public function __construct($staffService, $loginStaff)
{
$this->staffService = $staffService;
$this->loginStaff = $loginStaff;
}
public function loadChoiceList($value = null)
{
// Get the same shop staff as the login staff
$staffs = $this->staffService->getStaffByShop($this->loginStaff->getShop());
// If the current staff is not included in the acquired staff (due to transfer etc.), add it to the end
if ($this->currentStaff && !array_search($this->currentStaff, $staffs)) {
$staffs[] = $this->currentStaff;
}
//Remove
//return new ChoiceList($staffs, $staffs);
//Add
return new DefaultChoiceListFactory($staffs, $staffs);
}
//Add
public function loadChoicesForValues(array $values, $value = null)
{
if (empty($choices)) {
return array();
}
$values = array();
foreach ($choices as $person) {
$values[] = (string) $staff->getId();
}
return $values;
}
public function loadValuesForChoices(array $choices, $value= null)
{
if (empty($values)) {
return array();
}
return $this->staffService->getStaffByShop($this->loginStaff->getShop());
}
public function getChoices()
{
}
}
ArticleType.php
//Remove
//$authorChoiceList = new StaffChoiceLoader($this->staffService, $options['login_staff']);
//Add
$factory = new DefaultChoiceListFactory();
$authorChoiceList = $factory->createListFromLoader(new StaffChoiceLoader($this->staffService, $options['login_staff']));
$builder->add("author", "entity", array(
"required" => true,
"class" => "AhiSpCommonBundle:Staff",
"choice_list" => $authorChoiceList,
"empty_value" => "Please select",
));
I have making a custom twig tag called "story_get_adjacent" to get the next/prev articles based on the input id. But for the life of me I cant get the actual data from the object pulled into the tag for look up. it always gives me back the name not the data. I know this can be done because i tested it with the set tag and it returns the data not the name. Thoughts????
Object on page
Object >>
Title = "This is a test story"
StoryID = 1254
Content ....
tag usage Example:
{% story_get_adjacent Object.StoryID as adjacent %}
Twig Extension:
class Story_Get_Adjacent_TokenParser extends Twig_TokenParser
{
public function parse(Twig_Token $token)
{
$parser = $this->parser; //story_get_adjacent
$stream = $parser->getStream(); // space
$value = $parser->getExpressionParser()->parseExpression(); //story id
$names = array();
try {
$as = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //as
$ObjName = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //object name
array_push($names, $ObjName);
} catch (Exception $e) {
throw new Exception( 'error: ' . $e);
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Story_Get_Adjacent_Node($names, $value, $token->getLine(), $this->getTag());
}
public function getTag()
{
return 'story_get_adjacent';
}
}
Twig Extension:
class Story_Get_Adjacent_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
}
public function compile (Twig_Compiler $compiler)
{
$Name = $this->getAttribute('name')[0];
$StoryAutoID = $this->getNode('value')->getAttribute('value');
$compiler->addDebugInfo($this);
$compiler->write('$context[\''. $Name .'\'] = NewsController::TwigStoryGetAdjacent("'.$StoryAutoID.'");')->raw("\n");
}
}
Alain Tiemblo solves your problem. However, why do you make your life so hard? Why don't you simply use a twig function?
{% set adjacent = story_get_adjacent(Object.StoryID) %}
class StoryExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
\Twig_SimpleFunction('story_get_adjacent', array($this, 'getAdjacent'), array('is_safe' => array('html'))),
);
}
public function getAdjacent($storyId)
{
return NewsController::TwigStoryGetAdjacent($storyId);
}
public function getName()
{
return 'story';
}
}
(the is_safe option tells twig it's safe HTML, so you don't have to disable escaping when using {{ story_get_adjacent(Object.StoryID) }}).
The problem is located in the compiler, when you try to access the value attribute of your expression.
The expression parser need to be subcompiled into an expression in PHP.
As the compiled expression is a non-evaluated expression, you shouldn't put quotes ( ' ) when you call TwigStoryGetAdjacent.
Try with the following class:
<?php
class Story_Get_Adjacent_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array ('value' => $value), array ('name' => $name), $line, $tag);
}
public function compile(Twig_Compiler $compiler)
{
$Name = reset($this->getAttribute('name'));
$compiler->addDebugInfo($this);
$compiler
->write("\$context['$Name'] = NewsController::TwigStoryGetAdjacent(")
->subcompile($this->getNode('value'))
->write(");")
->raw("\n")
;
}
}
Working demo
test.php
<?php
require(__DIR__ . '/vendor/autoload.php');
require("TestExtension.php");
require("TestTokenParser.php");
require("TestNode.php");
class NewsController
{
static public function TwigStoryGetAdjacent($id)
{
return "I return the story ID = {$id}.";
}
}
$loader = new Twig_Loader_Filesystem('./');
$twig = new Twig_Environment($loader, array (
'cache' => __DIR__ . '/gen'
));
$object = new \stdClass;
$object->StoryID = 42;
$twig->addExtension(new Test_Extension());
echo $twig->render("test.twig", array ('Object' => $object));
test.twig
{% story_get_adjacent Object.StoryID as adjacent %}
{{ adjacent }}
TestExtension.php
<?php
class Test_Extension extends \Twig_Extension
{
public function getTokenParsers()
{
return array (
new Test_TokenParser(),
);
}
public function getName()
{
return 'test';
}
}
TestTokenParser.php
<?php
class Test_TokenParser extends Twig_TokenParser
{
public function parse(Twig_Token $token)
{
$parser = $this->parser; //story_get_adjacent
$stream = $parser->getStream(); // space
$value = $parser->getExpressionParser()->parseExpression(); //story id
$names = array();
try {
$as = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //as
$ObjName = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //object name
array_push($names, $ObjName);
} catch (Exception $e) {
throw new Exception( 'error: ' . $e);
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Test_Node($names, $value, $token->getLine(), $this->getTag());
}
public function getTag()
{
return 'story_get_adjacent';
}
}
TestNode.php
<?php
class Test_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array ('value' => $value), array ('name' => $name), $line, $tag);
}
public function compile(Twig_Compiler $compiler)
{
$Name = reset($this->getAttribute('name'));
$compiler->addDebugInfo($this);
$compiler
->write("\$context['$Name'] = NewsController::TwigStoryGetAdjacent(")
->subcompile($this->getNode('value'))
->write(");")
->raw("\n")
;
}
}
Run
$ composer require "twig/twig" "~1.0"
$ php test.php
Result
I return the story ID = 42.
Bonus The compiled doDisplay method corresponding to the template
protected function doDisplay(array $context, array $blocks = array())
{
// line 1
$context['adjacent'] = NewsController::TwigStoryGetAdjacent($this->getAttribute((isset($context["Object"]) ? $context["Object"] : null), "StoryID", array()) );
// line 2
echo twig_escape_filter($this->env, (isset($context["adjacent"]) ? $context["adjacent"] : null), "html", null, true);
}
I using zf2's tableGateway and I'm unsure of the design it leads to.
Here is the canonical example of how to use zf2's tableGateway to do an insert (this from the docs):
public function saveAlbum(Album $album)
{
$data = array(
'artist' => $album->artist,
'title' => $album->title,
);
$id = (int)$album->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getAlbum($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
But defining the $data array seems redundant because I already have an Album class that looks like this:
class Album
{
public $id;
public $artist;
public $title;
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
}
In my own project I have a model with about 25 properties (a table with 25 columns). It seems redundant to have to define the class with 25 properties and than also write a $data array inside of the method of a class implementing tableGateway with an element for every one of those properites. Am I missing something?
Another way is to use RowGateway http://framework.zend.com/manual/2.3/en/modules/zend.db.row-gateway.html
Briefly, I'd extend album class from \Zend\Db\RowGateway\AbstractRowGateway class.
<?php
namespace Module\Model;
use Zend\Db\RowGateway\AbstractRowGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
class Album extends AbstractRowGateway
{
protected $primaryKeyColumn = array( 'id' );
protected $table = 'album';
public function __construct( Adapter $adapter )
{
$this->sql = new Sql( $adapter, $this->table );
$this->initialize();
}
}
And then you can do like this
$album->title = "Some title";
$album->save();
Or
$album->populate( $dataArray )->save();
You may want to take a look at my QuickStart 101 Tutorial.
Basically you could do:
saveAlbum(Album $albumObject)
{
$hydrator = new ClassMethods(false);
$albumArray = $hydrator->extract($albumObject);
// v-- not too sure if that one-liner works; normal if() in case it doesn't
isset($albumArray['id']) ? unset($albumArray['id']) :;
// insert into tablegateway
}
I tried to follow the recommendations from this topic: zend framework 2 + routing database
I have a route class:
namespace Application\Router;
use Zend\Mvc\Router\Http\RouteInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\Mvc\Router\RouteMatch;
class Content implements RouteInterface, ServiceLocatorAwareInterface {
protected $defaults = array();
protected $routerPluginManager = null;
public function __construct(array $defaults = array()) {
$this->defaults = $defaults;
}
public function setServiceLocator(\Zend\ServiceManager\ServiceLocatorInterface $routerPluginManager) {
$this->routerPluginManager = $routerPluginManager;
}
public function getServiceLocator() {
return $this->routerPluginManager;
}
public static function factory($options = array()) {
if ($options instanceof \Traversable) {
$options = ArrayUtils::iteratorToArray($options);
} elseif (!is_array($options)) {
throw new InvalidArgumentException(__METHOD__ . ' expects an array or Traversable set of options');
}
if (!isset($options['defaults'])) {
$options['defaults'] = array();
}
return new static($options['defaults']);
}
public function match(Request $request, $pathOffset = null) {
if (!method_exists($request, 'getUri')) {
return null;
}
$uri = $request->getUri();
$fullPath = $uri->getPath();
$path = substr($fullPath, $pathOffset);
$alias = trim($path, '/');
$options = $this->defaults;
$options = array_merge($options, array(
'path' => $alias
));
return new RouteMatch($options);
}
public function assemble(array $params = array(), array $options = array()) {
if (array_key_exists('path', $params)) {
return '/' . $params['path'];
}
return '/';
}
public function getAssembledParams() {
return array();
}
}
Pay attention that the match() function returns object of the instance of Zend\Mvc\Router\RouteMatch
However in the file Zend\Mvc\Router\Http\TreeRouteStack it checks for object to be the instance of RouteMatch (without prefix of namespace)
if (
($match = $route->match($request, $baseUrlLength, $options)) instanceof RouteMatch
&& ($pathLength === null || $match->getLength() === $pathLength)
)
And the condition fails in my case because of the namespace.
Any suggestions?
Ok, i figured out what the problem was.
Instead of returning Zend\Mvc\Router\RouteMatch I should return Zend\Mvc\Router\Http\RouteMatch
This fixed my problem
Some time ago, Matthew Weier O'Phinney posted this article on his blog about creating composite form elements in Zend Framework 1.
I'm trying to create the same element for my custom library in Zend Framewor 2 but I'm having issues finding the form view helper when rendering the form.
Here is my element (DateSegmented.php):
<?php
namespace Si\Form\Element;
use Zend\Form\Element;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
class DateSegmented extends Element implements ViewHelperProviderInterface
{
public function getViewHelperConfig(){
return array( 'type' => '\Si\Form\View\Helper\DateSegment' );
}
protected $_dateFormat = '%year%-%month%-%day%';
protected $_day;
protected $_month;
protected $_year;
/**
* Seed attributes
*
* #var array
*/
protected $attributes = array(
'type' => 'datesegmented',
);
public function setDay($value)
{
$this->_day = (int) $value;
return $this;
}
public function getDay()
{
return $this->_day;
}
public function setMonth($value)
{
$this->_month = (int) $value;
return $this;
}
public function getMonth()
{
return $this->_month;
}
public function setYear($value)
{
$this->_year = (int) $value;
return $this;
}
public function getYear()
{
return $this->_year;
}
public function setValue($value)
{
if (is_int($value)) {
$this->setDay(date('d', $value))
->setMonth(date('m', $value))
->setYear(date('Y', $value));
} elseif (is_string($value)) {
$date = strtotime($value);
$this->setDay(date('d', $date))
->setMonth(date('m', $date))
->setYear(date('Y', $date));
} elseif (is_array($value)
&& (isset($value['day'])
&& isset($value['month'])
&& isset($value['year'])
)
) {
$this->setDay($value['day'])
->setMonth($value['month'])
->setYear($value['year']);
} else {
throw new Exception('Invalid date value provided');
}
return $this;
}
public function getValue()
{
return str_replace(
array('%year%', '%month%', '%day%'),
array($this->getYear(), $this->getMonth(), $this->getDay()),
$this->_dateFormat
);
}
}
And here is my form view helper:
<?php
namespace Si\Form\View\Helper;
use Zend\Form\ElementInterface;
use Si\Form\Element\DateSegmented as DateSegmented;
use Zend\Form\Exception;
class DateSegmented extends FormInput
{
/**
* Render a form <input> element from the provided $element
*
* #param ElementInterface $element
* #throws Exception\InvalidArgumentException
* #throws Exception\DomainException
* #return string
*/
public function render(ElementInterface $element)
{
$content = "";
if (!$element instanceof DateSegmented) {
throw new Exception\InvalidArgumentException(sprintf(
'%s requires that the element is of type Si\Form\Input\DateSegmented',
__METHOD__
));
}
$name = $element->getName();
if (empty($name) && $name !== 0) {
throw new Exception\DomainException(sprintf(
'%s requires that the element has an assigned name; none discovered',
__METHOD__
));
}
$view = $element->getView();
if (!$view instanceof \Zend\View\View) {
// using view helpers, so do nothing if no view present
return $content;
}
$day = $element->getDay();
$month = $element->getMonth();
$year = $element->getYear();
$name = $element->getFullyQualifiedName();
$params = array(
'size' => 2,
'maxlength' => 2,
);
$yearParams = array(
'size' => 4,
'maxlength' => 4,
);
$markup = $view->formText($name . '[day]', $day, $params)
. ' / ' . $view->formText($name . '[month]', $month, $params)
. ' / ' . $view->formText($name . '[year]', $year, $yearParams);
switch ($this->getPlacement()) {
case self::PREPEND:
return $markup . $this->getSeparator() . $content;
case self::APPEND:
default:
return $content . $this->getSeparator() . $markup;
}
$attributes = $element->getAttributes();
$attributes['name'] = $name;
$attributes['type'] = $this->getInputType();
$attributes['value'] = $element->getCheckedValue();
$closingBracket = $this->getInlineClosingBracket();
if ($element->isChecked()) {
$attributes['checked'] = 'checked';
}
$rendered = sprintf(
'<input %s%s',
$this->createAttributesString($attributes),
$closingBracket
);
if ($element->useHiddenElement()) {
$hiddenAttributes = array(
'name' => $attributes['name'],
'value' => $element->getUncheckedValue(),
);
$rendered = sprintf(
'<input type="hidden" %s%s',
$this->createAttributesString($hiddenAttributes),
$closingBracket
) . $rendered;
}
return $rendered;
}
/**
* Return input type
*
* #return string
*/
protected function getInputType()
{
return 'datesegmented';
}
}
This question describes adding the view helper as an invokable, but it's already being declared, as my custom library (Si) has been added to the 'StandardAutoLoader'.
OK, figured this one out eventually.
Copy Zend/Form/View/HelperConfig.php to the same location in your custom library. Adjust contents to reflect your view helpers.
Add the following to an event or bootstrap in your Module.php
$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
$phpRenderer = $serviceManager->get('ViewRenderer');
$plugins = $phpRenderer->getHelperPluginManager();
$config = new \Si\Form\View\HelperConfig;
$config->configureServiceManager($plugins);
Update the 'Si' namespace with your custom one.
The 'class already exists' error was actually down to the includes at the top of my view helper file. I have updated it with:
use Zend\Form\View\Helper\FormElement;
use Zend\Form\Element;
use Zend\Form\ElementInterface;
use Zend\Form\Exception;
I also updated the instanceof statement to an absolute location due to the duplicate class names:
if (!$element instanceof \Si\Form\Element\DateSegmented) {
There were further errors in the translation from ZF1 to 2 but they are not related to this issue.
The way i understand your code is: You are creating a new Form\Element as well as a new Form\View\Helper. In this case the following information is needed for you:
The StandardAutoloader only takes care of actually finding the classes. The declaration of the invokables inside the getViewHelperConfig() is there, so the framework knows what Class to load when the ViewHelper is called.
In your case you'd do it like this:
public function getViewHelperConfig()
{
return array(
'invokables' => array(
'dateSegmented' => 'Si\Form\View\Helper\DateSegmented'
)
);
}
Zend Framework 2 does this for it's own ViewHelpers inside /Zend/Form/View/HelperConfig.php