OctoberCMS Routing Plugin Unresolvable dependency resolving - php

I'm currently working with OctoberCMS and am creating a plugin which has a custom page with a widget which shows a grid
As you can see in this image this grid can be managed and can be saved with the "Save changes" button. This will send a POST request to the server, but I have problems with "listening" for this POST request. Since the documentation of octoberCMS is not very good I'm trying to do this the way it would be done in Laravel. But even that doesn't work like it should.
Unresolvable dependency resolving [Parameter #0 [ <required> $app ]] in class Illuminate\Support\ServiceProvider
ftsf/grid/routes.php
<?php
Route::post('/backend/ftsf/grid', 'Ftsf\Grid\Widgets\GridManager#saveGrid');
ftsf/grid/widgets/GridManager.php
<?php namespace Ftsf\Grid\Widgets;
use App;
use Backend\Classes\WidgetBase;
use Cms\Classes\Content;
use Cms\Classes\Controller;
use Cms\Classes\Theme;
use Cms\Twig\Extension;
use Ftsf\Grid\Models\PatternOrder;
use Illuminate\Http\Request;
use System\Twig\Engine as TwigEngine;
class GridManager extends WidgetBase {
protected $defaultAlias = 'ftsf_grid_manager';
public function init() {
}
public function render() {
$env = App::make('twig.environment');
$env->addExtension(new Extension(new Controller(Theme::getActiveTheme())));
return (new TwigEngine($env))->get($this->getViewPath('_gridmanager.htm'),
['patterns' => PatternOrder::orderBy('order')->with('pages')->get(),
'contents' => Content::all()]);
}
public function loadAssets() {
$this->addCss('css/gridmanager.css', 'Ftsf.Grid');
$this->addJs('js/gridmanager.js', 'Ftsf.Grid');
}
public function saveGrid(Request $request){
return dd($request);
}
}
If more information is needed just tell me what.

You should use the Octobers native AJAX handlers. The documentation is quite good for that in my opinion.
In that case the handler should look like this:
public function onSaveGrid(){
return dd(post());
}
You can make the request like this:
$.request('onSaveGrid', {
success: function() {
console.log('Finished!');
}
})
Of course you could also use the data-attributes API or call the handler on a DOM element. The documentation covers all these cases.

Related

Codeigniter 4 Route Resource For Put Not Working Correctly

Why I need to put /1 in front of the url for put (update) in codeigniter 4 version 4.2.6 ?
routes.php :
$routes->resource('ApiManageProfile', ['controller' =>'App\Controllers\ApiData\ApiManageProfile']); // get, put, create, delete
ApiManageProfile.php
<?php
namespace App\Controllers\ApiData;
use App\Controllers\BaseController;
use CodeIgniter\RESTful\ResourceController;
use Codeigniter\API\ResponseTrait;
class ApiManageProfile extends ResourceController
{
use ResponseTrait;
function __construct()
{
}
// equal to get
public function index()
{
}
// equal to post
public function create() {
}
// equal to get
public function show($id = null) {
}
// equal to put
public function update($id = null) {
$id = $this->request->getVar('id');
$birthday = $this->request->getVar('birthday');
$phonenumber = $this->request->getVar('phonenumber');
echo "TESTING";
}
// equal to delete
public function delete($id = null) {
}
}
Then I use postman to call put with /1 :
https://testing.id/index.php/ApiManageProfile/1?id=71&phonenumber=1122211&birthday=2023-01-20
The code run correctly.
But if I use postman to call put without /1 :
https://testing.id/index.php/ApiManageProfile?id=71&phonenumber=1122211&birthday=2023-01-20
Then I got this error :
"title": "CodeIgniter\\Exceptions\\PageNotFoundException",
"type": "CodeIgniter\\Exceptions\\PageNotFoundException",
"code": 404,
"message": "Can't find a route for 'put: ApiManageProfile'.",
For the previous version codeigniter 4 Version 4.1.2 it is working without a problem
I cannot change all my Rest API to use /1 in front of the url for put because my Application is already launch. If I change the code in react native it will need a time to update the application. And people cannot update the data.
Codeigniter 4 seem change something in newest update version 4.2.6. Causing my routes broken in the application.
Seriously need help for this. What I can do ?
$routes->resource('ApiManageProfile', ['controller' =>'\App\Controllers\ApiData\ApiManageProfile']); // get, put, create, delete
generates RESTFUL routes including the following for the PUT action.
$routes->put('ApiManageProfile/(:segment)', '\App\Controllers\ApiData\ApiManageProfile::update/$1');
The segment isn't optional.
If you would like to implement the segment to be optional, exclude it from the generated routes and declare it explicitly that way.
$routes->resource(
'ApiManageProfile',
['controller' =>'\App\Controllers\ApiData\ApiManageProfile', 'except' => 'update']
); // get, create, delete
$routes->put(
'ApiManageProfile',
'\App\Controllers\ApiData\ApiManageProfile::update'
);

enum class in ternary expression

Enums were introduced to PHP very recently. I'm trying them out in a laravel project. I've got my enum class here:
namespace App\Enums;
enum AlertType
{
case SUCCESS;
case ERROR;
}
I'm trying to create an alert class that will take the enum in the constructor to set the severity of the alert, which will decide what colour it is rendered as to the user. Here is that class:
<?php
namespace App\View\Components;
use App\Enums\AlertType;
use Illuminate\View\Component;
class Alert extends Component
{
public string $contextClass;
public function __construct(public string $message, AlertType $alertType = AlertType::SUCCESS)
{
$this->setContextClassFromAlertType($alertType);
}
public function setContextClassFromAlertType(AlertType $alertType)
{
$this->contextClass = ($alertType === AlertType::SUCCESS ? 'success' : 'error');
}
public function getClassListFromType()
{
return [
'border-' . $this->contextClass,
'text-' . $this->contextClass
];
}
public function render()
{
return view('components.alert', [
'class' => implode(' ', $this->getClassListFromType())
]);
}
}
This alert will be used in a login form which is built using Laravel Livewire and blade components:
<form class="grid grid-cols-min-auto-1 gap-x-2" id="login" action="/login" method="post">
<x-alert :message="$errorMessage"/>
#csrf
<label for="email" class="mb-2 text-lg text-sans w-min mt-2 font-thin">Email</label>
<x-basic-input wire:model="email" placeholder="{{ $emailPlaceholder }}"/>
<label for="password" class="mb-2 text-lg text-sans w-min mt-2 font-thin">Password</label>
<x-basic-input wire:model="password"/>
</form>
When I come to display the login form I am getting the following error:
Cannot instantiate enum App\Enums\AlertType (View: /resources/views/livewire/forms/login.blade.php)
I think there is something wrong with my enum usage in the Alert component, but I'm not sure where. Can anyone point me in the right direction. I've looked at the rfc for enums but I can't see anything obvious that I'm doing wrong
I was able to reproduce this error; in my case the stack trace led back to the barryvdh/laravel-debugbar package, not sure if this is the same for you. I was able to resolve it by changing the enum to a backed enum.
I'd recommend making this change regardless, as I expect in a lot of cases strings will be easier to work with than enum instances. (Though TBH this looks like trying to use a new feature just because it's there, not because it makes sense.)
namespace App\Enums;
enum AlertType: string
{
case SUCCESS = 'success';
case ERROR = 'error';
case INFO = 'info';
}
In a backed enum, each item has a string representation that can be accessed using the value property, which we do in the constructor:
<?php
namespace App\View\Components;
use App\Enums\AlertType;
use Illuminate\View\Component;
class Alert extends Component
{
public string $contextClass;
public function __construct(
public string $message,
AlertType $alertType = AlertType::SUCCESS,
)
{
$this->contextClass = $alertType->value;
}
public function getClassListFromType()
{
return [
'border-' . $this->contextClass,
'text-' . $this->contextClass
];
}
public function render()
{
return view('components.alert', [
'class' => implode(' ', $this->getClassListFromType())
]);
}
}
You're now able to use the from() or tryFrom() methods which can add flexibility with alert types saved in a variable. For example:
<x-alert :message="$errorMessage" :type="App\Enums\AlertType::from($errorType)"/>
I think the problem here is due to dependency injection. In the constructor I'm typehinting an enum, so the Laravel container is trying to create an instance of that class to pass in which doesn't work because enums are instantiable.
If I update the container to manually resolve the typehinted instance like this:
$this->app->bind(Alert::class, function ($app) {
return new Alert('this is a message', AlertType::SUCCESS);
});
Then the issue is resolved and it works as expected. I'll need to change the way I'm using enums here as #miken32 suggests to use a backed enum and rely on the strings instead. Otherwise I'd need to override the container injection for every method where I wanted to pass an enum.

Rendered view is not displaying in my back office in PS 1.7

I am trying to create a view in the back office tab that I created in the installation of my Module. My Module adds the tab like this:
protected function _installTabs()
{
if(!$tabId = \Tab::getIdFromClassName('IezonPortfolio')) {
$tab = new \Tab();
$tab->class_name = 'IezonPortfolio';
$tab->module = $this->name;
$tab->id_parent = \Tab::getIdFromClassName('ShopParameters');
$tab->active = 1;
foreach (Language::getLanguages(false) as $lang):
$tab->name[(int) $lang['id_lang']] = 'My Portfolio';
endforeach;
return $tab->save();
}
new \Tab((int) $tabId);
return true;
}
This works fine and I can navigate to my Shop Parameters and click the My Portfolio tab. The issue I'm having is that it is blank. My ModuleAdminController looks like this:
class IezonPortfolioController extends ModuleAdminController {
private $_module;
public function __construct()
{
$this->bootstrap = true;
parent::__construct();
$this->_module = \Module::getInstanceByName('iezonportfolio');
}
public function indexAction()
{
return $this->render('#Modules/iezonportfolio/views/templates/admin/display.html.twig', array(
'contents_iezonportfolio' => $this->_module->selectAll()
));
}
}
My display.html.twig just has test in it to see if it would output anything which it didn't. On looking at the Docs it doesn't mention anything other than using the render function and returning it. Any help would be appreciated. I just get a blank Tab.
EDIT: After looking at some of the pre-installed modules and referencing them to the Docs, I saw that I was missing my route configuration. My Controller is in the documented directory set-up: iezonportfolio/controllers/admin/iezonportfolio.php so I made my route like this:
iezonportfolio:
path: iezonportfolio
methods: [GET]
defaults:
_controller: 'IezonPortfolio\Controllers\Admin\Controller::indexAction'
_legacy_controller: 'IezonPortfolioController'
_legacy_link: 'IezonPortfolioController:index'
This has still not yet fixed the blank display so I tried to dig deeper into some other modules and have now updated my display.html.twig to show this:
{% extends '#PrestaShop/Admin/layout.html.twig' %}
{% block content %}
Test
{% endblock %}
This did not fix the blank display either. I hope this addition is useful for future viewers.
This is not how the modern controllers works, you are extending legacy ModuleAdminController, take a look here:
https://github.com/PrestaShop/example-modules
you have a plenty of module examples, here's a little snippet from one of those modules:
declare(strict_types=1);
namespace PrestaShop\Module\DemoControllerTabs\Controller\Admin;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use Symfony\Component\HttpFoundation\Response;
class MinimalistController extends FrameworkBundleAdminController
{
/**
* #return Response
*/
public function indexAction()
{
return $this->render('#Modules/democontrollertabs/views/templates/admin/minimalist.html.twig');
}
}
I recommend you to think wether you want to use, or not, modern controller. It depends on wether you want to sell your module, use it in client projects etc.

How to refactor long php file of routes (I'm using Slim Framework)

I am using the Slim Framework for a simple crud-style application. My index.php file has become quite long and unwieldy with all the different routes. How can I clean up / refactor this code? For example I have code like the following for all the different routes and GET, POST, PUT, DELETE etc.
$app->get("/login", function() use ($app)
{//code here.....});
What I like to do is group routes, and for each group, I create a new file under a subdir called routes. To illustrate with some example code from the Slim docs:
index.php:
$app = new \Slim\Slim();
$routeFiles = (array) glob(__DIR__ . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . '*.php');
foreach($routeFiles as $routeFile) {
require_once $routeFile;
}
$app->run();
routes/api.php:
// API group
$app->group('/api', function () use ($app) {
// Library group
$app->group('/library', function () use ($app) {
// Get book with ID
$app->get('/books/:id', function ($id) {
});
// Update book with ID
$app->put('/books/:id', function ($id) {
});
// Delete book with ID
$app->delete('/books/:id', function ($id) {
});
});
});
You can even do this for multiple levels, just be sure you don't overcomplicate yourself for this.
You can also do this for hooks off course.
You can for example move code the inner code to class:
$app->get("/login", function() use ($app)
{
$user = new User();
$user->login();
});
or even create your own class that will handle routing
class Router {
public function __construct($app) {
$this->app = $app;
}
public function createRoutes() {
$this->app->get("/login", function() use ($this->app)
{
$user = new User();
$user->login();
});
// other routes, you may divide routes to class methods
}
}
and then in your index.php
$router = new Router($app);
$router->createRoutes();
You can move index.php content into diferent files and just include them. For example:
index.php:
$app = new \Slim\Slim();
...
require_once 'path_to_your_dir/routes.php';
...
$app->run();
routes.php:
$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
...
Or you can even create different files for different routes types:
index.php:
$app = new \Slim\Slim();
...
require_once 'path_to_your_dir/routes.php';
require_once 'path_to_your_dir/admin_routes.php';
require_once 'path_to_your_dir/some_other_routes.php';
...
$app->run();
Same approach is also ok for DI services initialization etc. (everything from your index.php)
This is how i use Slim. I do have a single file with the routes but i use a three layer approach.
index.php - i don't handle any logic here. i just register the routes and consume the api methods (post, put, delete, etc).
$app->post('/bible/comment/', function() use($ioc) {
$ioc['commentApi']->post();
});
The API layers inherit a base class where it's inject the slim app object. From there i use helper methods to extract the data from requests using arrays with the required fields, optional fields, etc. I don't do validation here. This is also where i clean the requests for xss.
Most important, i handle the exceptions here. Invalid requests throws exceptions, which are caught and transformed in a error response.
class CommentApi extends BaseApi {
public function post() {
$fields = array(array('message', 'bookId', 'chapter', 'verseFrom', 'verseTo')):
$dtoModel = new Models\CreateComment();
$data = $this->extractFormData();
Utils::transformDto($dtoModel, $data, $fields):
try {
$result = $this->commentService->create($this->getUserId(), $dtoModel);
$response->success("You've added a book to the bible."); // helper from BaseApi to set the response 200
$response->setResult($result);
}
catch(\Exceptions\CommentRepeatedException $ex) {
$response->invalid('The foo already exist. Try a new one');
}
catch(\Exceptions\CommentsClosedException $ex) {
UtilsExceptions::invalidRequest($dtoModel, $ex);
$response->invalid('Invalid request. Check the error list for more info.');
}
$this->respond($response); // encode the response in json, set the content type, etc
}
This layer class consume the business layers which will use repositories and others resources. I test the projects against the business layers, the api layers just extract the data, create the dto models and handle the response.
The request/response models implements interfaces to return a status code, erros messages to be consumed in for the clients (respect is cool to automate this).
class CommentBusiness {
public function create($userId, Models\CreateComment $model) {
// Validate the request object
// Assert all logic requirements
$dataRes = $this->repository->create('message' => $model->getMessage(), 'bookId' => $model->getUserId(), 'chapter' => $model->getChapter(), 'verseFrom' => $mode->getVerseFrom(), 'verseTo' => $model->getVerseTo());
if($dataRes->isInvalid()) {
throw new \Exceptions\DataException($dataRes->getExModel());
}
return $dataRes;
}
}
I like to use classes as route callbacks, and combine that with grouping of routes. Doing that, your index.php file (or wherever you choose to define your routes) will probably become very "Slim":
$app->group('/user', function() use ($app) {
$app->map('/find/:id', '\User:search')->via('GET');
$app->map('/insert', '\User:create')->via('POST');
// ...
});
In this example, the /user/find/:id route will call the search method (passing the value of :id as the first parameter) of the User class. So your callback class might look a bit like this:
class User {
public function search($userId) {
// ...
}
}
There are a number of advantages to this approach, especially if you are writing a CRUD-style application (that needs database access). For example, you can contain all your database logic in a base class and use inherited child classes for the route callbacks. You can also encapsulate all route-group-related logic into separate classes, e.g. having a User class that handles all the /user routes, and so on.

Problems with redeclaring classes when using PHP's class_alias in a functional test

I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
class_alias('MyFormPage1Form', 'FormAlias');
break;
...
}
$this->form = new FormAlias($obj);
}
This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:
$browser->info('1 - Edit Form Page 1')->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end()->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end();
I get a 500 response to the second request, with the following error:
last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)
This makes it very hard to test form submissions (which typically post back to themselves).
Presumably this is because Symfony's tester hasn't cleared the throughput in the same way.
Is there a way to 'unalias' or otherwise allow this sort of redeclaration?
As an alternate solution you can assign the name of the class to instantiate to a variable and new that:
public function executeEdit(sfWebRequest $request) {
$formType;
switch($request->getParameter('page')) {
case 'page-1':
$formType = 'MyFormPage1Form';
break;
...
}
$this->form = new $formType();
}
This doesn't use class_alias but keeps the instantiation in a single spot.
I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?
From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
$form = new MyFormPage1Form($obj);
break;
...
}
$this->form = $form;
}
You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.
public function executeEdit(sfWebRequest $request) {
$mapping = array(
'page-1' => 'MyFormPage1Form',
// more mappings
);
$form = NULL;
$id = $request->getParameter('page');
if(array_key_exists($id, $mapping)) {
$className = $mapping[$id];
$form = new $className($obj);
}
$this->form = $form;
}
This way, you could also put the entire mapping in a config file. Or you could create FormFactory.
public function executeEdit(sfWebRequest $request) {
$this->form = FormFactory::create($request->getParameter('page'), $obj);
}
If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.
function class_alias_once($class, $alias) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.

Categories