Why I have "Not found" /admin in symfony easyAdminBundle 4 (php) - php

I can't open /admin page after installing easyAdminBundle in symfony app.
I do:
symfony composer req "admin:^4"
then
symfony console make:admin:dashboard
This line generate this code.
<?php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return parent::index();
// Option 1. You can make your dashboard redirect to some common page of your backend
//
// $adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);
// return $this->redirect($adminUrlGenerator->setController(OneOfYourCrudController::class)->generateUrl());
// Option 2. You can make your dashboard redirect to different pages depending on the user
//
// if ('jane' === $this->getUser()->getUsername()) {
// return $this->redirect('...');
// }
// Option 3. You can render some custom template to display a proper dashboard with widgets, etc.
// (tip: it's easier if your template extends from #EasyAdmin/page/content.html.twig)
//
// return $this->render('some/path/my-dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('Symfony App');
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
// yield MenuItem::linkToCrud('The Label', 'fas fa-list', EntityClass::class);
}
}
But when I try to open /admin page I get this:
"Not Found
The requested URL was not found on this server."
This lines doesn't help:
symfony console cache:clear
symfony composer dump-autoload
rm -rf var/cache/*
I want to see the start page at easyAdminBundle like in symfony documentation. Why I can't get this?

I just solved this by changing the route for DashboardController::index from '/admin' to '/admin/d' then going to http://127.0.0.1:8000/admin/d
class DashboardController extends AbstractDashboardController
{
#[Route('/admin/d', name: 'admin')]
public function index(): Response
{
return parent::index();
}
/* ... */
}

Related

Laravel Inertia Vite

In production mode (on the server), there is an error on my site in the Chrome console:
Uncaught (in promise) Error: Page not found: ./Pages/Posts/Show.vue
Also, the Dashboard page isn't updating to changes with text and a new pagination table I've introduced in local development.
Everything works fine locally, but pushing to Digital Ocean Server doesn't show the latest changes.
I checked the source code online and they raw code is there. I can see the changes in text, the pagination table and the new routes. But they are not showing when I load the website. I suspect something to do with the cache or the build process?
I have done:
php artisan cache:clear
php artisan config:clear
php artisan view:clear
npm run build (new vite build of assets)
Can anyone help?
Shared Files:
resources/js/app.js
import './bootstrap';
import '../css/app.css';
import { createApp, h } from 'vue';
import { createInertiaApp } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(ZiggyVue, Ziggy)
.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });
Post Controller
<?php
namespace App\Http\Controllers\Post;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Post;
class PostController extends Controller
{
/**
* Display all posts
*
* #return \Inertia\Response
*/
public function index(Request $request)
{
$posts = Post::paginate(10);
return Inertia::render('Dashboard', ['posts' => $posts]);
}
/**
* Display a post
*
* #return \Inertia\Response
*/
public function show(Request $request, $id)
{
$post = Post::findOrFail($id);
return Inertia::render('Posts/Show', ['post' => $post]);
}
}
This was an docker/nginx issue. The files generated by the app weren't routing correctly and so the static files from the original build weren't being replaced.
I changed to using volumes to sync data between the containers and it worked.

Next js - Call a function on server side each time a route is changed

I am moving an old stack to next js and i need some help.
I have a php backend and I want to check a controller from this backend each time a route is changed, and I want to do it on server side.
For changing route, I use Link of next-routes, which have an effect only on the client side.
Is there any way I can call the controller on server-side without refreshing the app?
You can take advantage of Router's events and Custom App watching for a route change and perform the actions you need. Below you can find two sample implementations, one using class component and one using hooks. Both should do the trick.
// class component
import NextApp from 'next/app';
import Router from 'next/router';
class App extends NextApp {
componentDidMount() {
Router.events.on('routeChangeStart', () => {
// call to your backend
});
}
render() {
const { Component, pageProps } = this.props;
return <Component {...pageProps} />;
}
}
export default App;
// using hooks
import Router from 'next/router';
const App = ({ Component, pageProps }) => {
React.useEffect(() => {
const doMagic = () => {
// do your thing
}
Router.events.on('routeChangeStart', doMagic); // add listener
return () => {
Router.events.off('routeChangeStart', doMagic); // remove listener
}
}, []);
return <Component {...pageProps} />;
}
export default App;

How to replace all 403 status codes with 404 in Symfony

In Symfony, when a user attempts to access a route which is forbidden for that specific user (according to the user roles), a page with response code 403 will be returned.
So the user can still see that there is a valid route there.
I would like to overwrite this behavior by replacing the status code 403 with 404, so the user will just see that there is no valid route when she/he is not allowed to access that resource.
How can I accomplish that?
This is doable, however almost undocumented. I'm aware of two ways but there might be even more:
Using access_denied_url configuration option. See security config reference. With this option you can set URL where the user is redirected when the user in unauthorized (I think it should work also with route name). See a similar question: Symfony2 Redirection for unauthorised page with access_denied_url
There're also "Entry Points" as mentioned in The Firewall and Authorization. However, no examples, no explanation how to use it.
I looks like this option expects a service name as can be seen in security config reference (search for entry_point option).
One possible solution, as partially explained here, can be the following:
1) Defining a new service controller in services.yml
exception_controller:
class: Path\To\MyBundle\Controller\MyExceptionController
arguments: ['#twig', '%kernel.debug%']
2) Creating the new class which overrides Symfony\Bundle\TwigBundle\Controller\ExceptionController:
namespace Path\To\MyBundle\Controller;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MyExceptionController extends ExceptionController
{
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC
$code = $exception->getStatusCode();
if ($code == 403) {
$code = 404;
// other customizations ...
}
return new Response($this->twig->render(
(string) $this->findTemplate($request, $request->getRequestFormat(), $code, $showException),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}
}
3) Setting the following in config.yml under twig:
twig:
exception_controller: 'exception_controller:showAction'
Even though my original goal was to prevent such an exception to be thrown at all with that code.
Another solution can be overwriting the AccessListener service of the Symfony Security component.
The generic procedure about how to override a service of a bundle is documented here. The following is the concrete example about this particular situation.
First of all let's create the class which overrides the AccessListener class:
<?php
namespace Path\To\My\Bundle\Services;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class OverrideAccessListener extends AccessListener
{
public function handle(GetResponseEvent $event)
{
try {
parent::handle($event);
} catch (AccessDeniedException $e) {
$request = $event->getRequest();
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
}
throw new NotFoundHttpException($message);
}
}
}
then we need to create a Compiler Pass in order to change the class attribute of the original service with the new class:
<?php
namespace Path\To\My\Bundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('security.access_listener');
$definition->setClass('Path\To\My\Bundle\Services\OverrideAccessListener');
}
}
finally we need to register the Compiler Pass in the build method of the bundle:
<?php
namespace Path\To\My\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Path\To\My\Bundle\DependencyInjection\Compiler\OverrideServiceCompilerPass;
class MyBundleName extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideServiceCompilerPass());
}
}
Finally I found a simpler solution: using an access denied handler.
Unfortunately there is no much documentation about how to create an access denied handler, but it is very simple.
First create a class that implements the AccessDeniedHandlerInterface and set it as a service (for example naming it my_access_denied_handler_service).
In the handle method a Response should be created and returned (in my case I wanted a 404 response).
Then in the security.yml configuration file we have to set the access_denied_handler under the firewall:
...
firewalls:
my_firewall:
...
access_denied_handler: my_access_denied_handler_service
...
...

LARAVEL 5.0 + Unit Test - assertSessionHasErrors with different bags

I am writing a unit test in Laravel 5.0 and in my request class I am using a different bag to show the validation error messages.
I am using this in my file:
/* ExampleRequest.php */
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Support\Facades\Auth;
class ExampleRequest extends Request {
protected $errorBag = 'otherbag';
public function rules(){
return [
'my_field' => 'required'
];
}
}
In my test file, I am testing using this:
/* ExampleTest.php */
class ExampleTest extends TestCase {
public function testPostWithoutData(){
$response = $this->call('POST', 'url/to/post',[
'my_field' => ''
]);
$this->assertSessionHasErrors('my_field');
}
}
If I run the tests, it can't get the right assert and return this problem:
Session missing error: my_field
Failed asserting that false is true.
If I take out the $errorBag attribute from the request file, I have no problems.
I can give more details as needed.
You can get an alternate bag from the session store like this:
$myBag = $this->app('session_store')->getBag('otherBag');
$this->assertTrue($myBag->any());
However, Laravel does not use an alternate bag by default, so I'm assuming you're doing something in your code to register your App\Request::$errorBag with the session handler.
I don't know if you are setting your session elsewhere but I guess you may do something like:
$this->session(['foo' => 'bar']);
Before you can assert something in session. See testing helpers section for Laravel 5.0

Zend framework 2: How to redirect from a plugin?

I worte a plugin IdentityPlugin to check login status of a user. If the user session gets logout, I want to redirect them to login page. My code is given below.
public function checkLogin($logout=true,$next='login'){
if($this->auth->hasIdentity()){
}elseif ($logout){
return $this->getController()->redirect()->toRoute($next);
}
}
in my controller
// Check identity, if not found- redirect to login
$this->IdentityPlugin()->checkLogin();
Any idea?
You're returning a response to the controller but you're not returning it from the controller itself.
For example you could try this is your controller:
$check = $this->IdentityPlugin()->checkLogin();
if ($check instanceof Response) {
return $check;
}
A more complex solution could be to stop the propagation of the controller's MvcEvent, set whatever response you want, and return directly.
Hi you need to config you plugin in factories in module.config.php and pass service manager to __construct, like below:
'controller_plugins' => array(
'factories' => array(
'CheckLogin' => function($sm){
$checkLogin = new Application\Plugin\CheckLogin($sm);
return $checkLogin;
},
),
),
Then in your plugin you will be able to call all you need using service Manager:
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class CheckLogin extends AbstractPlugin
{
public function __construct($sm)
{
$auth = $sm->getServiceLocator()->get("Zend\Authentication\AuthenticationService");
if( !$auth->hasIdentity()){
$sm->getController()->plugin('redirect')->toUrl('/login');
}
}
}

Categories