Symfony3: Default parameter gets first entry even when defaulted to null - symfony

I am trying to create an action in my controller that handles a null value for category if it's not passed in.
I've tried annotating 2 routes (one without /{category}) but in every case, when I do not supply a category in the URL, Symfony retrieves the 1st category it can find.
#Route("/accounts/{id}/categories/{category}", defaults={"category" = null})
The action definition looks like this:
public function categoryAction(Request $request, Account $account, Category $category)
I have also tried $category = null in the action, but that does not make a difference.
How can I make this action have a $category with a value of null if the category is not defined in the url?
Update:
To be clear, here is the full annotation and function definition with comments on my xdebug results:
/**
* #Route("/accounts/{id}/categories")
* #Route("/accounts/{id}/categories/{category}, defaults={"category" = null}")
*/
public function categoryAction(Request $request, Account $account, Category $category = null)
{
// When I set a breakpoint here, $category is populated with
// the first category result in the database.
// This is when visiting: http://localhost:8000/accounts/1/categories

You can define multiple routes for same actions, maybe try something like :
/**
* #Route("/accounts/{id}/categories")
* #Route("/accounts/{id}/categories/{category}")
* #Template()
*/
public function categoryAction(Request $request, Account $account, Category $category)
{
And if you try to access /accounts/{id}/categories, $category will be null

I believe this is the change you should make:
/**
* #Route("/accounts/{id}/categories", defaults={"category" = null}")
* #Route("/accounts/{id}/categories/{category})
*/
public function categoryAction(Request $request, Account $account, Category $category)
{
...
Can you try it and let us know? I haven't tested it, but I think it's right.

Related

Is there a way to define first item of pagination results?

I'm setting up a Symfony project displaying paginated blogposts with an admin interface to manage all this stuff. On this admin it is also possible to "highlight" one of those public blogposts so that this highlighted one is displayed at first position only on the first page.
I need the same item count on each page and that is the problem I'm dealing with.
I'm using PagerFanta so I created an AbstractRepository with a "paginate" function.
protected function paginate(QueryBuilder $qb, $limit = 20, $offset = 0)
{
if ($limit == 0) {
throw new \LogicException('$limit must be greater than 0.');
}
//Instantiates the pagination object with the result of the query
$pager = new Pagerfanta(new DoctrineORMAdapter($qb));
//Sets max data per page
$pager->setMaxPerPage($limit);
//Sets the current page
$pager->setCurrentPage($offset);
return $pager;
}
In my blogpost repository I made a querybuilder to get all public blogpost excluding the highlighted one because I can get it in another way to display it on top of the first page.
public function findAllVisible($id, $limit = 3, $offset = 1, $order = 'DESC')
{
$qb = $this
->createQueryBuilder('a')
->select('a')
->where('a.website = :website')
->setParameter('website', 'blog')
->andWhere('a.public = :public')
->setParameter('public', true)
->andWhere('a.id != :id')
->setParameter('id', $id)
->orderBy('a.dateInsert', $order)
;
return $this->paginate($qb, $limit, $offset);
}
So I first tried to change the limit and the offset according to the current page but I logically lost one item between the first and the second page.
Then I tried to include the highlighted blogpost in querybuilder but I don't know how to define it as the first result if the current page is the first one.
Any idea of how to force the first result to be the highlighted blogpost only on first page? Or another clean and appropriate way to display results as expected?
I answer to myself because I managed to do what I needed to. In case of someone is dealing with the same issue, here is how I did.
I don't use PagerFanta anymore but Doctrine Paginator tool.
Instead of excluding my highlighted article from my query I replaced my initial ORDER BY by a.id = :highlightedId DESC, a.dateInsert DESC.
Now it's working as expected.
Here is my new repository function:
/**
* Finds all visible articles
*
* #param int $highlightedTipId the highlighted tip id
* #param int $page current page
* #param int $limit max items per page
*
* #throws InvalidArgumentException
* #throws NotFoundHttpException
*
* #return Paginator
*/
public function findAllVisible($highlightedTipId, $limit = 3, $page)
{
if (!is_numeric($page)) {
throw new InvalidArgumentException('$page value is incorrect (' . $page . ').');
}
if ($page < 1) {
throw new NotFoundHttpException('Page not found');
}
if (!is_numeric($limit)) {
throw new InvalidArgumentException('$limit value is incorrect (' . $limit . ').');
}
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
"SELECT
a,
CASE WHEN a.id = :id THEN 1 ELSE 0 END AS HIDDEN sortCondition
FROM App\Entity\Item a
WHERE
a INSTANCE OF App\Entity\TipArticle
AND
a.website = :website
AND
a.public = :public
ORDER BY
sortCondition DESC,
a.dateInsert DESC
"
);
$query->setParameter(':website', 'blog');
$query->setParameter(':public', true);
$query->setParameter(':id', $highlightedTipId);
$firstResult = ($page - 1) * $limit;
$query
->setFirstResult($firstResult)
->setMaxResults($limit);
$paginator = new Paginator($query);
if (($paginator->count() <= $firstResult) && $page != 1) {
throw new NotFoundHttpException('Page not found');
}
return $paginator;
}
A word about this line CASE WHEN a.id = :id THEN 1 ELSE 0 END AS HIDDEN sortCondition: it is the only way I found to do a ORDER BY a.id = :highlightedId DESC with Doctrine. As you can see I made a DQL but I is also possible with QueryBuilder.
Hope it will help! :)
Nice, well done. If I may offer some advice though. In a repo you shouldn't need to get the ObjectManager ($this->getEntityManager()) as the repo is already for a type of Entity, Item in this case. You should use Criteria instead. practical example docs
You should have the ObjectManager in whichever controller you've got, so then you'd do:
$items = $this-getObjectManager()->getRepository(Item::class)->findAllVisible($params)
For use of the Paginator you should use the QueryBuilder, with Expressions, like here
As a practical example, an indexAction of mine:
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator as OrmPaginator;
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as OrmAdapter;
use Zend\Paginator\Paginator;
public function indexAction()
{
// get current page, defaults to 1
$page = $this->params()->fromQuery('page', 1);
// get current page size, defaults to 10
$pageSize = $this->params()->fromQuery('pageSize', 10);
// get ordering, defaults to 'createdAt'
$orderBy = $this->params()->fromQuery('orderBy', 'createdAt');
// get order direction, defaults to Criteria::DESC
$orderDirection = ($this->params()->fromQuery('orderDirection') === Criteria::ASC)
? Criteria::ASC
: Criteria::DESC;
$criteria = new Criteria();
$criteria->setFirstResult($page * $pageSize);
$criteria->setMaxResults($pageSize);
$criteria->orderBy([$orderBy => $orderDirection]);
/** #var QueryBuilder $queryBuilder */
$queryBuilder = $this->getObjectManager()->createQueryBuilder();
$queryBuilder->select('a')->from(Article::class, 'a');
$queryBuilder->addCriteria($criteria);
$paginator = new Paginator(new OrmAdapter(new OrmPaginator($queryBuilder)));
$paginator->setCurrentPageNumber($page); // set current page
$paginator->setItemCountPerPage($pageSize); // set item count per page
return [
'paginator' => $paginator,
'queryParams' => $this->params()->fromQuery(), // pass these for your pagination uri's
];
}
NOTE: In the above the $this is an instance of a Zend Framework controller, where ->fromQuery returns (if present) a given key from the Query bit if a URI, else return the 2nd param default (or null). You should do something similar.

Symfony parameters Routes (Easy to answer)

I try to pass my user id in parameter because I wanted to use it in an other function to link them but don't know why that don't work (I think that I do a mistake and will be easy to answer thx :)
return $this->redirect('/profile/new/', array(
'id' => $user->getId(),
));
My receiver :
/**
* Creates a new profile entity.
*
* #Route("/new/{id}", name="profile_new")
*/
public function newProfileAction(Request $request)
{
when I do /profile/new/8 for example it works! But when I click in the button submit that don't redirect with the id ... (of course the routes are good and when I do - it works :
return $this->redirect('/profile/new');
my receiver (when it works) :
/**
* Creates a new profile entity.
*
* #Route("/new", name="profile_new")
*/
public function newProfileAction(Request $request)
{
You should use $this->redirectToRoute('ROUTENAME',[PARAMETERS]) means:
$this->redirectToRoute('profile_new',['id'=>$ID])
If you use $this->redirect('URL') you have to parse the URL so you need to "/profile/new/ID"

Sylius, search products by slug not working

i'm trying to filter products by slug, using:
$this->get('sylius.repository.product')->findOneBy(array('slug' => $slug));
I've tried using findBy and findOneBySlug, but it always says that Product does not have a "slug" property:
Unrecognized field: slug
or
Entity 'Sylius\Component\Core\Model\Product' has no field 'slug'. You can therefore not call 'findOneBySlug' on the entities' repository
but the documentation on their website says it should be working:
http://docs.sylius.org/en/latest/components_and_bundles/bundles/SyliusProductBundle/product.html
$product = $repository->findOneBy(array('slug' => 'my-super-product')); // Get one product by defined criteria.
I think that doesn't work, because the slug is available on the translations of the product. There are some default methods in the repository available that might be helpful to you, e.g.: findOneByChannelAndSlug or findByName.
Alternatively, you can construct it yourself when extending the product repository:
/**
* #param string $name
* #param string $locale
* #return array
*/
public function findBySlug(string $slug, string $locale): ?ProductInterface
{
return $this->createQueryBuilder('o')
->innerJoin('o.translations', 'translation', 'WITH', 'translation.locale = :locale')
->andWhere('translation.slug = :slug')
->setParameter('slug', $slug)
->setParameter('locale', $locale)
->getQuery()
->getOneOrNullResult()
;
}

Symfony2 render controller from service?

I want to render controller from my custom class. I know that I should use forward function but I don't know with service I have to use?
I have found something like that
$subRequest = $this->container->get('request')->duplicate(
array(),
null,
array('topicId' => $topicId,'_controller' => 'SomeBundle:Topic:close'));
return $this->container->get('http_kernel')
->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
It's a forward function but if I use it I get headers.
How to hide header from forward function?
I need it because I want to render custom logic ( get from DB and other ). It's my idea for modules.
After reading a post about controller utils on whitewashing.de
I created my own utils function I inject in every controller.
The forward function in there works fine up to sf 2.7 and looks like this:
/**
* Forwards the request to another controller.
*
* #param string $controller The controller name (a string like BlogBundle:Post:index)
* #param array $path An array of path parameters
* #param array $query An array of query parameters
*
* #return Response A Response instance
*/
public function forward($controller, array $path = array(), array $query = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request_stack')->getCurrentRequest()->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
Take a look at the documentation for forwarding requests.

Symfony2: Deleting record in m:n-relationship

I have a problem, while deleting an entry in a m:n-table. I tried this solution, but I get the error: "ContextErrorException: Catchable Fatal Error: Argument 1 passed to Pso\ProjectBundle\Entity\Project::removeUser() must be an instance of Pso\LogBundle\Entity\User, string given, called in C:\xampp\htdocs\pso\src\Pso\ProjectBundle\Controller\ProjectController.php on line 59 and defined in C:\xampp\htdocs\pso\src\Pso\ProjectBundle\Entity\Project.php line 452"
I have a Class Project with the function:
/**
* Remove users
*
* #param \Pso\LogBundle\Entity\User $users
*/
public function removeUser(\Pso\LogBundle\Entity\User $users)
{
$this->users->removeElement($users);
}
Now I want to delete an entry from the table project_user. This table implements the n:m-relationship from user and project
Im my Controller I tried to adapt the linked solution:
public function deleteUserProjectAction($id, $projectid, Request $request)
{
$em = $this->getDoctrine()->getManager();
$project = $em->find('PsoProjectBundle:Project', $projectid);
$project->removeUser($id);
$em->persist($project);
$em->flush();
}
$id is the id of the user in the n:m table and $projectid the related project. Any hint for solution would be appreciated.
The error is extremly explicit. You are giving to the removeUser() function a string which is the user's id whereas the expected parameter type is a User.
You must retrieve the user from the DB thanks to $id and then pass this user to the function.

Resources