I've been searching and haven't really found much useful information. I'm looking to use the symfony2 routing component (and also yaml component) with my own personal framework but haven't found any documentation on basic setup. The documentation on the symfony site does a good job of explaining how to use the component but not much in the way of standalone setup.
Can anyone recommend a good place to start that could explain how to setup individual symfony2 components?
I ended up looking at the documentation on the symfony site, as well as the source code on github (Route.php, RouteCollection.php, RouteCompiler.php) and came up with my own basic router.
Just like symfony, I allow 4 arguments to be passed to the route class -> 1) the pattern, 2) the default controller/action, 3) regex requirements for each variable in the pattern, and 4) any options - I haven't coded for this yet as it's not in my requirements.
My main/front controller (index.php) only talks to the static Router class which has access to the other route classes in the lib\route namespace.
Router::Map basically compiles a regex string (along with default controller/action and variable names) that gets passed to the compiledRouteCollection which is used in Router::_Process to match the httpRequest against. From there, if there is a match in the process method then i set the controller/action/args based on the values of the matched compiledRoute default controller/action. If there is not a preg_match then i use the default controller/action/args from the request object. The rest is cake...
I hope someone finds this useful and it's able to save them the time I spent today working on this. Cheers!
index.php
require_once 'config/config.php';
require_once 'lib/autoload.php';
lib\Router::Map(
'users_username_id',
'users/{username}/{id}',
array('controller' => 'testController', 'action' => 'users')
);
lib\Router::Route(new lib\Request());
router.php
namespace lib;
class Router {
protected static $_controller;
protected static $_action;
protected static $_args;
protected static $_compiledRouteCollection;
private static $_instance;
private function __construct() {
self::$_compiledRouteCollection = new \lib\route\CompiledRouteCollection();
}
public static function Singleton() {
if(!isset(self::$_instance)) {
$className = __CLASS__;
self::$_instance = new $className;
}
return self::$_instance;
}
public static function Route(Request $request, $resetProperties = false) {
self::Singleton();
self::_Process($request,$resetProperties);
$className = '\\app\\controllers\\' . self::$_controller;
if(class_exists($className, true)) {
self::$_controller = new $className;
if(is_callable(array(self::$_controller, self::$_action))) {
if(!empty(self::$_args)) {
call_user_func_array(array(self::$_controller, self::$_action), self::$_args);
} else {
call_user_func(array(self::$_controller, self::$_action));
}
return;
}
}
self::Route(new \lib\Request('error'),true);
}
public static function Map($name, $pattern, array $defaults, array $requirements = array(), array $options = array()) {
self::Singleton();
$route = new \lib\route\Route($pattern,$defaults,$requirements,$options);
$compiledRoute = $route->Compile();
self::$_compiledRouteCollection->Add($name,$compiledRoute);
}
private static function _Process(Request $request, $resetProperties = false) {
$routes = array();
$routes = self::$_compiledRouteCollection->routes;
foreach($routes as $route) {
preg_match($route[0]['regex'], $request->GetRequest(), $matches);
if(count($matches)) {
array_shift($matches);
$args = array();
foreach($route[0]['variables'] as $variable) {
$args[$variable] = array_shift($matches);
}
self::$_controller = (!isset(self::$_controller) || $resetProperties) ? $route[0]['defaults']['controller'] : self::$_controller;
self::$_action = (!isset(self::$_action) || $resetProperties) ? $route[0]['defaults']['action'] : self::$_action;
self::$_args = (!isset(self::$_args) || $resetProperties) ? $args : self::$_args;
return;
}
}
self::$_controller = (!isset(self::$_controller) || $resetProperties) ? $request->GetController() : self::$_controller;
self::$_action = (!isset(self::$_action) || $resetProperties) ? $request->GetAction() : self::$_action;
self::$_args = (!isset(self::$_args) || $resetProperties) ? $request->GetArgs() : self::$_args;
}
}
request.php
namespace lib;
class Request {
protected $_controller;
protected $_action;
protected $_args;
protected $_request;
public function __construct($urlPath = null) {
$this->_request = $urlPath !== null ? $urlPath : $_SERVER['REQUEST_URI'];
$parts = explode('/', $urlPath !== null ? $urlPath : $_SERVER['REQUEST_URI']);
$parts = array_filter($parts);
$this->_controller = (($c = array_shift($parts)) ? $c : 'index').'Controller';
$this->_action = ($c = array_shift($parts)) ? $c : 'index';
$this->_args = (isset($parts[0])) ? $parts : array();
}
public function GetRequest() {
return $this->_request;
}
public function GetController() {
return $this->_controller;
}
public function GetAction() {
return $this->_action;
}
public function GetArgs() {
return $this->_args;
}
}
route.php
namespace lib\route;
class Route {
private $_pattern;
private $_defaults;
private $_requirements;
public $_options;
public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array()) {
$this->SetPattern($pattern);
$this->SetDefaults($defaults);
$this->SetRequirements($requirements);
$this->SetOptions($options);
}
public function SetPattern($pattern) {
$this->_pattern = trim($pattern);
if($this->_pattern[0] !== '/' || empty($this->_pattern)) {
$this->_pattern = '/'.$this->_pattern;
}
}
public function GetPattern() {
return $this->_pattern;
}
public function SetDefaults(array $defaults) {
$this->_defaults = $defaults;
}
public function GetDefaults() {
return $this->_defaults;
}
public function SetRequirements(array $requirements) {
$this->_requirements = array();
foreach($requirements as $key => $value) {
$this->_requirements[$key] = $this->_SanitizeRequirement($key,$value);
}
}
public function GetRequirements() {
return $this->_requirements;
}
public function SetOptions(array $options) {
$this->_options = array_merge(
array('compiler_class' => 'lib\\route\\RouteCompiler'),
$options
);
}
public function GetOptions() {
return $this->_options;
}
public function GetOption($name) {
return isset($this->_options[$name]) ? $this->_options[$name] : null;
}
private function _SanitizeRequirement($key, $regex) {
if($regex[0] == '^') {
$regex = substr($regex, 1);
}
if(substr($regex, -1) == '$') {
$regex = substr($regex,0,-1);
}
return $regex;
}
public function Compile() {
$className = $this->GetOption('compiler_class');
$routeCompiler = new $className;
$compiledRoute = array();
$compiledRoute = $routeCompiler->Compile($this);
return $compiledRoute;
}
}
routeCompiler.php
namespace lib\route;
class RouteCompiler {
//'#\/tellme\/users\/[\w\d_]+\/[\w\d_]+#'
public function Compile(Route $route) {
$pattern = $route->GetPattern();
$requirements = $route->GetRequirements();
$options = $route->GetOptions();
$defaults = $route->GetDefaults();
$len = strlen($pattern);
$tokens = array();
$variables = array();
$pos = 0;
$regex = '#';
preg_match_all('#.\{([\w\d_]+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
if(count($matches)) {
foreach($matches as $match) {
if($text = substr($pattern, $pos, $match[0][1] - $pos)) {
$regex .= str_replace('/', '\/', $text).'\/';
}
if($var = $match[1][0]) {
if(isset($requirements[$var])) {
$regex .= '('.$requirements[$var].')\/';
} else {
$regex .= '([\w\d_]+)\/';
}
$variables[] = $match[1][0];
}
$pos = $match[0][1] + strlen($match[0][0]);
}
$regex = rtrim($regex,'\/').'#';
} else {
$regex .= str_replace('/', '\/', $pattern).'#';
}
$tokens[] = array(
'regex' => $regex,
'variables' => $variables,
'defaults' => $defaults
);
return $tokens;
}
}
compiledRouteCollection.php
namespace lib\route;
class CompiledRouteCollection {
public $routes;
public function __construct() {
$this->routes = array();
}
public function Add($name, array $route) {
$this->routes[$name] = $route;
}
}
Related
I created 2 fixtures files and want to link them but got the error : Reference to "genre_reference" already exists, use method setReference in order to override it.
Here is my book fixtures :
class LivreFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager): void
{
$faker = Factory::create('fr_FR');
$faker->seed(123);
for ($i = 0; $i < 10; $i++) {
$livre = new Livre();
$livre->setTitre($faker->sentence($nbWords = 4, $variableNbWords = true));
$livre->setAuteur($faker->name);
$livre->setDescription($faker->text);
$livre->setDateDeParution($faker->dateTime($format = 'Y-m-d'));
$livre->setGenre($this->getReference('genre_reference'));
$manager->persist($livre);
}
$manager->flush();
}
public function getDependencies()
{
return array(
GenreFixtures::class,
);
}
}
And my genre fixtures file :
class GenreFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
$faker = Factory::create('fr_FR');
$faker->seed(123);
for ($i=0; $i < 5; $i++) {
$genre = new Genre();
$genre->setName($faker->colorName);
$manager->persist($genre);
$this->addReference('genre_reference', $genre);
}
$manager->flush();
}
}
I don't really understand the setreference method, how to do it. Any help ?
I think you should add $i to your genre reference name.
$this->addReference('genre_reference_' . $i, $genre);
After that, you can use it by
$this->getReference('genre_reference_0');
//OR
$this->getReference('genre_reference_1');
//OR
$this->getReference('genre_reference_' . $i);
I'm trying to unit test my service, containing a dependency Finder Component of symfony2(http://symfony.com/doc/current/components/finder.html).
I'm getting :
[Exception] Objects returned by Mock_Finder_91776c5c::getIterator()
must be traversable or implement interface Iterator
The service :
public function getFile($fileName, $path = '/')
{
if($this->wrapper == null || $this->connection == null)
throw new LogicException("call method setFTP first");
// get file on ftp server
$this->connection->open();
$downloadComplete = $this->wrapper->get($this->tmpDir . $fileName, $path . $fileName);
$this->connection->close();
if($downloadComplete == false)
return false; // TODO exception ?
// return file downloaded
$this->finder->files()->in(__DIR__);
foreach ($this->finder as $file) {
return $file;
}
return false; // TODO exception ?
}
And the test
class FtpServiceTest extends \PHPUnit_Framework_TestCase
{
protected $connectionMock;
protected $ftpWrapperMock;
protected $finderMock;
protected function setUp()
{
$this->connectionMock = $this->getConnectionMock();
$this->ftpWrapperMock = $this->getFTPWrapperMock();
$this->finderMock = $this->getFinderMock();
}
protected function tearDown()
{
}
private function getFinderMock()
{
return $this->getMockBuilder(Finder::class)
->disableOriginalConstructor()
->getMock('Iterator');
}
private function getConnectionMock()
{
return $this->getMockBuilder(Connection::class)
->disableOriginalConstructor()
->getMock();
}
private function getFTPWrapperMock()
{
return $this->getMockBuilder(FTPWrapper::class)
->disableOriginalConstructor()
->getMock();
}
// tests
public function testMe()
{
// arrange
$host = 'localhost';
$user = 'user';
$password = '1234';
$filesArray = new ArrayObject(array(''));
$service = new FtpService('var/tmp/');
$service->setFTP($this->connectionMock, $this->ftpWrapperMock);
$service->setFinder($this->finderMock);
$this->connectionMock
->expects($this->once())
->method('open');
$this->ftpWrapperMock
->expects($this->once())
->method('get')
->will($this->returnValue(true));
$this->connectionMock
->expects($this->once())
->method('close');
$this->finderMock
->expects($this->once())
->method('files')
->will($this->returnValue($this->finderMock));
$this->finderMock
->expects($this->once())
->method('in')
->will($this->returnValue($filesArray));
// act
$file = $service->getFile('/file.zip');
// assert
$this->assertInstanceOf(SplFileInfo::class, $file);
}
}
The mocked instance of the Finder class needs to implement/mock the method getIterator. The getMock method does not accept arguments so don't pass the string 'Iterator'—change the code as follows:
private function getFinderMock()
{
return $this->getMockBuilder(Finder::class)
->disableOriginalConstructor()
->getMock();
}
And add the Mocked expectation in the test method, for example:
$this->finderMock->expects($this->once())
->method('getIterator')
->willReturn(new \ArrayObject([$this->createMock(SplFileInfo::class)]));
Hope this helps.
Multimedia, Gallery and GalleryMultimedia.
Both Multimedia and Gallery have a One-to-Many relationship with GalleryMultimedia which holds multimedia_id, gallery_id and position.
In my MultimediaAdmin, I added list of Galleries as below:
->with('Thematic Galleries')
->add('gallery', 'entity', array(
'class' => 'ACME\MyBundle\Entity\Gallery',
'property' => 'name',
'multiple' => true,
'expanded' => true,
'mapped' => false,))
->end()
Now I am stuck at persisiting the selected Gallery as a GalleryMultimedia object. In my Multimedia Model, I have the function below which I would love to pass the GalleryMultimedia object for persisiting but just can't figure out how.
public function setGalleries($galleries)
{
if (count($galleries) > 0) {
foreach ($galleries as $gallery)
{
$this->addGallery($gallery);
}
}
return $this;
}
Out of desparation, I added the following code in my MultimediaAdmin.php
public function prePersist($multimedia)
{
$this->preUpdate($multimedia);
}
public function preUpdate($multimedia)
{
$multimedia->setFiles($multimedia->getFiles());
$this->saveGalleries($multimedia);
}
public function saveGalleries($multimedia)
{
$galleryies = $this->getForm()->get('gallery')->getData();
$container = $this->getConfigurationPool()->getContainer();
$em = $container->get('doctrine')->getManager();
$existing_arr = array();
$existing = $em->getRepository('ACMEMyBundle:GalleryMultimedia')->findBy(array('multimedia' => $multimedia));
$gals = array();
foreach($existing as $exist)
{
$existing_arr[] = $exist->getGallery()->getId();
}
foreach($galleryies as $gallery)
{
if(in_array($gallery->getId(),$existing_arr))
{
continue;
}
else
{
$gm = new \ACME\MyBundle\Entity\GalleryMultimedia();
$gm->setGallery($gallery);
$gals[] = $gm;
$multimedia->setGalleries($gals);
}
}
}
Can someone please assist my poor soul?
I solved this by making the persisting in postPersist like this
public function postPersist($multimedia)
{
$this->postUpdate($multimedia);
}
public function postUpdate($multimedia)
{
$this->saveGalleries($multimedia);
}
Hope this will help someone out there
My way of doing it with objects:
public function preUpdate($object)
{
$this->updateCountries($object);
}
public function updateCountries(\AppBundle\Entity\Product $object){
$container = $this->getConfigurationPool()->getContainer();
$em = $container->get('doctrine')->getManager();
$form_countries = $this->getForm()->get('Country')->getData();
//form_countries is a Doctrine\Common\Collections\ArrayCollection
$object_countries = $object->getCountries();
//object_countries is a collection of \AppBundle\Entity\CountryProduct
$list = array();
foreach($object_countries as $country){
if(!$form_countries->contains($country->getCountry())){
//remove objects not in the form
$em->remove($country);
} else {
//save the others to not re add them
$list[] = $country->getCountry();
}
}
foreach($form_countries as $country){
if(!in_array($country, $list)){
//add everyone in the form but not in the list
$countryProduct = new \AppBundle\Entity\CountryProduct();
$countryProduct->setProduct($object);
$countryProduct->setCountry($country);
$countryProduct->setValuation(1);
$em->persist($countryProduct);
}
}
$em->flush();
}
In one of my controllers, I have some static variables and two actions:
class ChooseController extends Controller
{
private static $wholedata = array();
private static $currentdata = array();
private static $wholenum = 0;
private static $currentnum = 0;
public function choosefirstAction()
{
$company = $this->getUser()->getCompany();
$em = $this->getDoctrine()->getManager();
self::$wholedata = $this->getDoctrine()
->getRepository('NeejobCompanyBundle:Selected')->findBy(
array("company" => $company),
array("job" => 'ASC')
);
self::$wholenum = count(self::$wholedata);
self::$currentdata = array_slice(self::$wholenum, 0, 3);
self::$currentnum = 3;
return new response(json_encode(self::$currentdata));
}
public function choosemoreAction()
{
//...
return new response(self::$wholenum);
}
}
I still have $wholenum = 0, which is supposed to be 3 or larger. How should I handle the problem?
When you send the data in choosefirstAction your class values are no longer set, you need to store them somewhere (i.e. in the session) :
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\JsonResponse;
class ChooseController extends Controller
{
public function choosefirstAction()
{
$company = $this->getUser()->getCompany();
$doc = $this->getDoctrine();
$wholedata = $doc->getRepository('...Bundle:Selected')->findBy(
array('company' => $company),
array('job' => 'ASC')
);
$wholenum = count($wholedata);
$currentdata = array_slice($wholenum, 0, 3);
$currentnum = 3;
$session = $this->get('session');
$session->set('wholenum', $wholenum);
return new JsonResponse($currentdata);
}
public function choosemoreAction()
{
$wholenum = $this->get('session')->get('wholenum');
return new response($wholenum);
}
}
More on sessions in symfony here
I read that the foreach works for arrays or any objects that implement Traversable interface.
Since Traversable is a mere abstract, I used Iterator.
class EPubHub_PageCollection implements Iterator
{
protected $index0 = 0;
protected $index = 1;
protected $pages = array();
public function __construct()
{
$this->index0 = 0;
$this->index = 1;
$this->pages = array();
}
public function updateIndex()
{
$this->index = $this->index0 + 1;
}
public function rewind()
{
$this->index0 = 0;
$this->updateIndex();
}
public function current()
{
return $this->pages[$this->index0];
}
public function key()
{
return $this->index0;
}
public function index()
{
return $this->index;
}
public function next()
{
++$this->index0;
$this->updateIndex();
}
public function valid()
{
return isset($this->pages[$this->index0]);
}
public function length()
{
return count($this->pages);
}
public function prefixZero($index, $lengthOfKey = 3)
{
return str_pad($index, $lengthOfKey, '0', STR_PAD_LEFT);
}
public function getCurrentPageNumber()
{
$pageNumber = $this->prefixZero($this->index);
return $pageNumber;
}
public function getCurrentPageFilename($pagenumber = '')
{
if ($pagenumber === '')
{
$pagenumber = $this->getCurrentPageNumber();
}
$pageFileName = 'page' . $pagenumber . '.xhtml' ;
return $pageFileName;
}
public function getNth($index)
{
return $this->pages[$index - 1];
}
public function getCurrentPageItemId($pagenumber = '')
{
if ($pagenumber === '')
{
$pagenumber = $this->getCurrentPageNumber();
}
$pageItemId = 'pg' . $pagenumber;
return $pageItemId;
}
public function add(EPubHub_PageInterface $page, $index = null)
{
if ($index === null) {
// we just append to the pages from the end directly
$this->pages[] = $page;
} else {
$this->pages = array_splice( $this->pages, $index - 1, 0, $page);
}
}
/**
*
* delete either by page or by index
* #param mixed $pageOrIndex If $pageOrIndex is instance of EPubHub_PageInterface
* then delete by value. Else if integer delete by index
*/
public function delete($pageOrIndex)
{
if ($pageOrIndex instanceof EPubHub_PageInterface)
{
$page = $pageOrIndex;
if(($key = array_search($page, $this->pages)) !== false)
{
unset($this->pages[$key]);
}
}
if (is_numeric($pageOrIndex))
{
$key = $pageOrIndex;
if((array_key_exists($key - 1, $this->pages)))
{
unset($this->pages[$key - 1]);
}
}
}
public function getImages()
{
$images = new EPubHub_ImageCollection();
foreach($this->pages as $page)
{
$image = $page->getImage();
$images->add($image);
}
return $images;
}
public function getPages()
{
return $this->pages;
}
}
This one did not work.
I want to understand why. I tried running dump(), but dump on the instance broke something.
Currently, my workaround is to use the inner array of this class.
I am asking this question to satisfy my curiosity.
You need to configure the debugging:
services:
acme_hello.twig.extension.debug:
class: Twig_Extension_Debug
tags:
- { name: 'twig.extension' }
Look here: debugging. And than: for loop in twig.
For example:
{{ dump(users) }}
{% for user in users %}
{{ user.username }}
{% endfor %}