Slim php error 404

If your Slim Framework application does not have a route that matches the current HTTP request URI, the application invokes it’s Not Found handler and returns a HTTP/1.1 404 Not Found response to the HTTP client.

If your Slim Framework application does not have a route that matches the current HTTP request URI, the application invokes it’s Not Found handler and returns a HTTP/1.1 404 Not Found response to the HTTP client.

Default Not Found handler

Each Slim Framework application has a default Not Found handler. This handler sets the Response status to 404, it sets the content type to text/html, and it writes a simple explanation to the Response body.

Custom Not Found handler

A Slim Framework application’s Not Found handler is a Pimple service. You can substitute your own Not Found handler by defining a custom Pimple factory method with the application container before you instantiate the App object.

$c = new SlimContainer(); //Create Your container

//Override the default Not Found Handler before creating App
$c['notFoundHandler'] = function ($c) {
    return function ($request, $response) use ($c) {
        return $response->withStatus(404)
            ->withHeader('Content-Type', 'text/html')
            ->write('Page not found');
    };
};

//Create Slim
$app = new SlimApp($c);

//... Your code

In this example, we define a new notFoundHandler factory that returns a callable. The returned callable accepts two arguments:

  1. A PsrHttpMessageServerRequestInterface instance
  2. A PsrHttpMessageResponseInterface instance

The callable MUST return an appropriate PsrHttpMessageResponseInterface instance.

If you wish to override the default Not Found handler after you instantiate the App object you can unset the default handler then overwrite it.

$c = new SlimContainer(); //Create Your container

//Create Slim
$app = new SlimApp($c);

//... Your code

//Override the default Not Found Handler after App
unset($app->getContainer()['notFoundHandler']);
$app->getContainer()['notFoundHandler'] = function ($c) {
    return function ($request, $response) use ($c) {
        $response = new SlimHttpResponse(404);
        return $response->write("Page not found");
    };
};

These days i’m using Slim Framework as my simplest tool to develop the php web api.
Using these two articles:

  • Coenraets
  • CodingThis

I follow some of the steps from there. Downloading the Slim Framework, putting the correct directory & files. Adjusting the initation statements such as;

//1. Require Slim
require('Slim/Slim.php');

//2. Instantiate Slim
$app = new Slim();

//3. Define routes
$app->get('/books', function ($id) {
    //Show book with id = $id
});

And then, I modify the rest accordingly.

Such as my checklist that already done:

  • LoadModule rewrite_module modules/mod_rewrite.so -> enabled
  • Slim .htaccess:

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule
^(.*)$ bootstrap.php [QSA,L]

  • httpd.conf: (Shared Link).

But, after Once I run this statement;

$app->run();

And I run it on my browser…. then, I got 404 Error while testing it on my Localhost. What’s the solution for fixing that?

FYI, here is my simplest PHP file that i’m currently using it. (shared Link)

asked Mar 17, 2012 at 5:51

gumuruh's user avatar

1

Problem is solved!

My apache is actually normal, and the .htaccess file provided earlier also normal.

The clue is the URL that I used.
Previously I used the invalid URL, thus it returned the 404 page error.
I just realized it when I Tried to access the newer GET URL via browser with this one;

http://localhost/dev/index.php/getUsers/user1

and now that works!

I just realized it once I found these statements;

If Slim does not find routes with URIs that match the HTTP request
URI, Slim will automatically return a 404 Not Found response.

If Slim finds routes with URIs that match the HTTP request URI but not
the HTTP request method, Slim will automatically return a 405 Method
Not Allowed response with an Allow: header whose value lists HTTP
methods that are acceptable for the requested resource.

answered Mar 19, 2012 at 9:32

gumuruh's user avatar

gumuruhgumuruh

2,5484 gold badges32 silver badges53 bronze badges

1

I found this post while googling «slimframework 404». The post led me to a solution to my problem.

The Problem

I set up a site with the Slim framework using Composer and create an index.php with the following example code form slimframework.com:

<?php
$app = new SlimSlim();
$app->get('/hello/:name', function ($name) {
    echo "Hello, $name";
});
$app->run();

Then I try to access the page using http://localhost/hello/bob. I get back a 404 Page Not Found page.

I was able to get to access the page using http://localhost/index.php/hello/bob.

The Solution

I found the solution by looking at /vendor/slim/.htaccess (included w/ Composer Slim install) and the URL Rewriting section of the Slim framework documentation.

I added a copy of the /vendor/slim/.htaccess file to the same folder as my index.php file. The contents of the file are:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L] 

Now I can access the page using http://localhost/hello/bob.

answered Jun 4, 2015 at 1:45

Tod Birdsall's user avatar

Tod BirdsallTod Birdsall

17.1k4 gold badges38 silver badges40 bronze badges

4

You’re right about to include the index.php on your file, but that happen because you’re not working properly with your mod-rewrite
Please check the following link:

https://github.com/codeguy/Slim

In the Get Started part you could see how to configure your server (in case that you were using apache / lighttpd / ngynx)

answered Jun 5, 2012 at 18:09

Darkaico's user avatar

DarkaicoDarkaico

1,1368 silver badges11 bronze badges

I think your problem is at the «Resource parser» because you are no defining $id parameter at the request so, please, try this:

//1. Require Slim
require('Slim/Slim.php');

//2. Instantiate Slim
$app = new Slim();

//3. Define routes
$app->get('/books/:id/', function ($id) {
    echo json_encode( getBook($id) );
});

// some stuff
$app->run();

Please, tell us if it’s ok

answered May 30, 2013 at 16:38

Ger Soto's user avatar

Ger SotoGer Soto

3282 silver badges12 bronze badges

For me the problem was that I’d forgotten to provide a .htaccess file in my document root.

.htaccess

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

answered Nov 14, 2017 at 11:49

Magnus's user avatar

MagnusMagnus

16.3k17 gold badges97 silver badges179 bronze badges

If you’re using Slim on Ubuntu 16.04 and you’re getting the 404 error. Try this test from Tod Birdsall above:

If this works

http://localhost/index.php/hello/bob

and this doesnt work

http://localhost/hello/bob

and your .htaccess file in the same directory as your index.php and is configured as follows:

    <IfModule mod_rewrite.c>
       RewriteEngine On
       RewriteCond %{REQUEST_FILENAME} !-d
       RewriteCond %{REQUEST_FILENAME} !-f
       RewriteRule ^ index.php [QSA,L]
    </IfModule>

And you still get the 404 error on Apache.

Try turning on Apache mod_rewrite as follows and restart Apache:

$sudo a2enmod rewrite

$sudo service apache2 restart

The Slim API should work correctly now as the provided .htaccess file will only work if mod_rewrite is enabled and that not the default on a clean install.

Here is how to turn off mod_rewirte if you need to.

$sudo a2dismod rewrite

$sudo service apache2 restart

answered Dec 28, 2017 at 7:13

Cosworth66's user avatar

Cosworth66Cosworth66

5595 silver badges13 bronze badges

Additionally mod_rewrite.so module must be loaded in httpd.conf or you will receive error 500.

LoadModule rewrite_module modules/mod_rewrite.so

answered Feb 15, 2017 at 2:03

user2454565's user avatar

For people who still search answers because previous doesn’t work, this works for me :

RewriteBase /<base_of_your_project>/

answered Mar 27, 2018 at 22:05

Jean R.'s user avatar

Jean R.Jean R.

4664 gold badges16 silver badges42 bronze badges

Решил опубликовать большой (!) лонгрид посвященный php-фреймворку Slim 4. Это микрофреймворк, а значит он содержит лишь самый минимум возможностей. С другой стороны Slim играет на поле full-stack фреймворков, таких как Symfony, Laravel и CodeIgniter.

Особенность Slim в том, что он по сути является лишь связующим звеном между сторонними библиотеками, которые созданы по стандартам PSR. Мы давно уже с этим сталкиваемся — сейчас доступно много хороших библиотек, которые можно использовать из коробки. Например для логов — Monolog, для контейнера — PHP-DI, для роутинга — FastRoute и т.д. Вот с помощью Slim можно всё это соединить воедино.

Но есть нюанс. Slim, хоть и небольшой фреймворк, но достаточно сложный. В основном мы привыкли работать в концепции MVC, где всё упирается в отдельные файлы контролёра, модели и шаблона, но вот Slim — это не MVC. Это ломает привычный подход и в процессе работы со Slim часто ловишь себя на мысли — зачем так усложнять, ведь можно сделать проще. :-)

Поэтому прежде чем приступать к кодированию, нужно разобраться с основными концепциями, лежащими в основе фреймворка.

Исторически сложилось так, что у нас сейчас доминируют три фреймворка.

CodeIgniter можно считать классическим MVC-фреймворком. Мы можем закрыть глаза на некоторые его «особенности» в понимании Model, поэтому в целом его работа будет строиться следующим образом:

  • Получить входящий URL.
  • Взять из конфигурации список маршрутов (роутинг).
  • Найти соответствующий текущему URL класс контролёра.
  • Передать управление этому контролёру.

Входящий URL представляет собой т.н. сегменты (разделитель «/»). Отсюда и строится приложение — каждому сегменту будет соответствовать «контролёр/метод». Да, CodeIgniter, конечно же позволяет делать и другие варианты роутинга, но в целом используется именно такой подход. Он простой и понятный.

В противоположность ему можно поставить фреймворк Symfony. Это фреймворк оперирует понятиями http-«запрос-ответ» request/response. То есть на определённый http-запрос формируется http-ответ. И хотя по сути ответ формирует php-класс, который мы можем назвать контролёром, но на самом деле здесь больше подходит название Action (действие). Получается, что http-запрос вызывает какое-то действие, которое в свою очередь формирует http-ответ.

И уже не важно, будет ли этот Action выполнен как MVC или в любой другой структуре. Такой подход намного гибче «классического» MVC, поскольку очень много задач можно решать без Модели и Представления (view). Даже его автор определяет Symfony как HTTP-фреймворк. По современным представлениям это ближе к модели ADR (Action Domain Responder).

И казалось бы, давайте все перейдём на Symfony, если он такой классный. Но здесь «засада» — он слишком сложный. Например фреймворк очень активно использует аннотации. Например вместо файла с маршрутами роутинга — они прописываются в виде php-комментариев к нужному php-классу. Тоже самое для работы с базой данных. Для вывода испольузется тяжелый и неуклюжий шаблонизатор Twig. Кроме того для работы с Symfony используется консоль, с помощью которой генерируются php-файлы или выполняются какие-то действия. Получается, что фреймворк работает как «черный ящик» — всё слишком автоматизировано и понять что происходит достаточно проблематично.

Именно поэтому Symfony, хоть и имеет красивую теорию, годится только для больших команд и сложных проектов. Однако особо стоит отметить, что Symfony состоит из множества готовых и самостоятельных компонентов. Я думаю, что именно эти компоненты дали популярность фреймворку.

Фреймворк Laravel занимает некое промежуточное положение. Изначальный форк CodeIgniter, постепенно начал мигрировать в сторону HTTP-фреймворка (естественно под влиянием Symfony). Laravel начинает работать «как Symfony» — роутинг строится по принципу http-«запрос-ответ». В отличие от Symfony, здесь уже используется явное указание роутов: маршрут — callback-функция. Это намного удобней, чем искать аннотации.

После этого подключает контролёр (по сути Action) и дальше вывод строится по концепции MVC. При этом в качестве представления (View) используется шаблонизатор Blade либо «голый» php-файл. То есть Laravel придерживается достаточно жесткой структуры каталогов и файлов (как и CodeIgniter), но это упрощает создание приложения — достаточно знать где и что нужно менять и хранить.

Именно из-за этого Laravel более привлекателен для php-разработчиков — в нём разобраться намного проще.

С другой стороны Laravel — это full-stack фреймворк, а значит содержит много файлов. Начальное приложение занимает 36Мб на диске (из них FakerPHP — 10Мб). Таким образом использование Laravel оправдано только для больших и сложных сайтов. Дело не спасает и их «дочерний» фреймворк Lumen — он базируется абсолютно на тех же самых библиотеках, что и Laravel.

Кстати похожая ситуация была и с Silex — «дочерний» фрейморк Symfony. Несколько лет назад его закрыли.

Что получается в итоге? Когда-то давно фреймворки действительно были небольшими, но сейчас сильно «разжирели» (кроме CodeIgniter) и использовать их для небольшого сайта слишком затратно.

Вот здесь в игру и вступает Slim. Это HTTP-фреймворк, где роутинг — является основой приложения. Сам роутинг описывается простым «маршрут — callback-функция». В качестве роутинга используется библиотека FastRoute, что покрывает большинство потребностей.

Современное приложение использует контейнер зависимостей. Есть «родной» сервис-контейнер Slim, но он слишком уж примитивный, поэтому можно использовать PHP-DI.

Slim оперирует понятием Middleware — это т.н. промежуточный слой. Например мы хотим добавить к какому-то маршруту дополнительные данные или провести проверку. Вот именно в Middleware такие вещи и происходят.

Работа со Slim происходит примерно так:

  1. Создаём экземпляр приложения — по сути это объект роутинга.
  2. Создаём DI-контейнер, который будет хранилищем зависимостей.
  3. Если нужно формируем Middleware.
  4. Определяем маршруты — задаются правила роутинга.
  5. Запускаем приложение.

Slim после инициализации, создаст объекты Request и Response и передаст их в Action, который определен в роутинге. Если у этого маршрута определён Middleware, то он также будет выполнен через его обработчик (handle). На выходе будет сформирован (на основе созданного Response) http-ответ, который и будет отправлен браузеру.

Slim вообще никак не регламентирует где хранить конфигурацию, контролёры, логи и т.д., поэтому структура приложения может быть произвольной.

Но Slim заставляет достаточно жестко придерживаться стандартов PSR: основной это PSR-7 который указывает как именно работать с http-сообщениями. На практике такой подход выражается в строгой типизации параметров Action — на входе нужно принять запрос с типом ServerRequestInterface, ответ с типом ResponseInterface и на выходе отдать объект ResponseInterface. Именно это позволяет строить произвольные цепочки вызовов.

Поскольку сам по себе Slim небольшой, то для полноценного приложения нужно будет использовать Composer. Приятным бонусом будет его поддержка PSR-4 (автозагрузка классов). То есть в теории мы можем вообще всё сделать в одном index.php, а библиотеки будут в каталоге vendor. На практике, конечно же нам всё равно придётся писать свой код, поэтому есть смысл определиться с каталогами будущего приложения. Но это мы сделаем позже, вначале просто поставим Slim и запустим его на локальном сервере. Для этого нам нужен Composer.

Я буду устанавливать на свой локальный сервер http://demo/slim1 (потом slim2). Вы можете сделать аналогично, или используйте другой хост.

1. Переходим в каталог сайта и вызываем консоль. Запускаем команду, которая установит Slim

composer require slim/slim:"4.*"

Если Composer спросит «No composer.json in current directory, do you want to use the one at D:domainsdemo? [Y,n]?» выберите «n».

После установки должен появиться каталог vendor и файлы composer.json и composer.lock.

2. Для работы Slim нужна какая-то PSR-7 библиотека. Будем использовать родную от Slim. Выполним:

composer require slim/psr7

3. Теперь здесь же создадим файл .htaccess

Options +SymLinksIfOwnerMatch
Options -Indexes
DirectoryIndex index.php
AddDefaultCharset UTF-8
 
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
</IfModule>

Это подключит ЧПУ.

4. Теперь сделаем index.php

<?php
 
use PsrHttpMessageServerRequestInterface;
use PsrHttpMessageResponseInterface;
use SlimFactoryAppFactory;
 
// подключаем Composer
require 'vendor/autoload.php';
 
// создаём объект приложения
$app = AppFactory::create();
 
// указываем базовый путь для роутинга — поскольку это подкаталог
$app->setBasePath('/slim1');
 
// http://demo/slim1/
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Home page');
    return $response;
});
 
// http://demo/slim1/hello
$app->get('/hello', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Hello world!');
    return $response;
});
 
// запускаем приложение
$app->run();
 
# end of file

5. Наберите в браузере http://demo/slim1/ и http://demo/slim1/hello — увидим соответствующее сообщение.

Обратите внимание на строчку с setBasePath(). Она нужна для случаев, если сайт располагается не в корне домена. Если у вас другой вариант, то измените эту строчку.

Давайте рассмотрим этот код.

Секция «use» показывает какие классы будут использоваться. Дальше мы подключаем автозагрузку файлов, который создал Composer.

Следующая строчка — это создание экземпляра приложения. Под капотом Slim использует FastRoute, но нигде этого указывать не нужно — вместо него используем оболочку AppFactory, которая хранит объекты роутинга, контейнера и т.д.

Маршруты указываются в виде http-методов GET, POST и т.п. Поэтому $app->get() — это GET-запрос. В нём нужно указать шаблон URL и callback-функцию (Action). Эта функция принимает три параметра. Первый — это входящий запрос ServerRequestInterface, второй — это текущий ответ с типом ResponseInterface, а третий — дополнительные аргументы, которые могут быть в шаблоне URL.

Внутри action-функции будут доступны два объекта $request и $response. В нашем примере $response формируется как тело ответа, куда мы отправляем простой текст. И в завершении функция возвращает объект $response для того, чтобы обеспечить дальнейшее выполнение цепочки запросов.

Как только роуты будут определены, можно запустить приложение с помощью метода run().

Основы Middleware

Middleware переводят по разному. Это и посредники, и промежуточное ПО. С помощью Middleware можно внедрится в http-ответ. Причем можно это сделать на глобальном уровне, или локально — для какого-то одного маршрута.

Использовать ли Middleware зависит только от разработчика. Можно вполне спокойно обойтись и без них, а можно всё приложение сделать на Middleware. В Slim http-ответ формируется в Action, но вначале он проходит через слой Middleware, которые могут его изменить. В этом и есть их основное предназначение.

Конкретно в Slim 4 роутинг — это тоже Middleware, только на самом глубоком уровне. Поэтому вначале «срабатывает» слой Middleware, а потом слой роутинга.

Давайте для примера сделаем один Middleware. Это обычная функция, которая должна поддерживать интерфейс PSR-15.

Первый аргумент — это http-запрос с типом ServerRequestInterface. Второй аргумент — это handler-обработчик RequestHandlerInterface. На выходе функции должен быть ответ с типом ResponseInterface.

Я приведу весь код index.php, чтобы вы не запутались:

<?php
 
use PsrHttpMessageServerRequestInterface;
use PsrHttpMessageResponseInterface;
use SlimFactoryAppFactory;
use PsrHttpServerRequestHandlerInterface; // обработчик Middleware
 
// подключаем Composer
require 'vendor/autoload.php';
 
// создаём объект приложения
$app = AppFactory::create();
 
// указываем базовый путь для роутинга — поскольку это подкаталог
$app->setBasePath('/slim1');
 
// создадим свой Middleware
$myMiddleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write(' myMiddleware ');
    return $response;
};
 
// добавим его в приложение
$app->add($myMiddleware);
 
 
// http://demo/slim1/
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Home page');
    return $response;
});
 
// http://demo/slim1/hello
$app->get('/hello', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Hello world!');
    return $response;
});
 
// запускаем приложение
$app->run();

# end of file

Теперь, если обновить страницы в браузере, мы увидим приписку « myMiddleware ». Здесь мы добавили глобальный Middleware с помощью $app->add().

Попробуем добавить этот же Middleware только к одному маршруту.

Закомментируйте строчку с $app->add() и добавьте его к маршруту:

// http://demo/slim1/
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Home page');
    return $response;
})->add($myMiddleware);

Теперь приписка « myMiddleware » будет срабатывать только для главной страницы сайта.

Дальше рассмотрим ещё одну возможность Slim — это отлов 404-страницы.

Для начала наберите в браузере несуществующий адрес, например http://demo/slim1/news. В браузере появится php-ошибка «Fatal error: Uncaught SlimExceptionHttpNotFoundException: Not found …». Чтобы от неё избавиться нужно добавить обработчик ErrorMiddleware, который будет перехватывать эту ошибку и выводить что-то более приемлемое.

$app->addErrorMiddleware(false, false, false);

Метод addErrorMiddleware() принимает три обязательных параметра. Первый из них отвечает за вывод подробной информации об ошибке. Для этого его нужно указать как true. Естественно на рабочем сайте этого не стоит делать.

Обработчик ErrorMiddleware можно заменить на другой вариант. Он пригодится для формирования своей 404-страницы. Такой код мы рассмотрим позже, когда будем строить своё приложение на основе классов.

Что будет если добавить несколько Middleware?

Сделайте копии $myMiddleware как $myMiddleware1 и $myMiddleware2 и добавьте к приложению:

$app->add($myMiddleware1);
$app->add($myMiddleware2);

После обновления страницы получится «Home page myMiddleware1 myMiddleware2». А теперь поменяйте порядок добавления:

$app->add($myMiddleware2);
$app->add($myMiddleware1);

Получится «Home page myMiddleware2 myMiddleware1». Этот пример показывает важность порядка добавления Middleware.

Теперь давайте вернёмся к роутингу. Помимо фиксированного URL можно указывать определённый паттерн. Например если мы хотим перехватить все неизвестные адреса, то можем использовать именованный параметр для главной:

// http://demo/slim1/
$app->get('/', ...
 
// http://demo/slim1/hello
$app->get('/hello', ...
 
// всё остальное
$app->get('/{slug}', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('This page is ' . $args['slug']);
    return $response;
}); 

В последнем роуте используется шаблон с подстановкой {slug}, который автоматически будет хранится в параметре $args. Это обычный массив, где будет ключ «slug». Мы его и выводим в браузере, если набрать любой несуществующий адрес. Такой приём позволяет перехватить 404-страницу без использования дополнительных Middleware.

При создании роутов нужно учитывать их порядок. Может случиться так, что маршруты будут перекрывать друг-друга, а значит Slim не сможет получить единственный обработчик и появится сообщение об ошибке «Slim Application Error». Для примера переместите последний роут в начало:

// всё остальное
$app->get('/{slug}', ...
 
// http://demo/slim1/
$app->get('/', ...
 
// http://demo/slim1/hello
$app->get('/hello', ...

Здесь мы получим ошибку, поскольку первый роут перекрывает остальные. Нужно за этим следить.

DI-контейнер

Ещё одной архитектурной концепцией Slim является работа с Dependency Container. Такой контейнер должен реализовывать PSR-11 и часто используется PHP-DI. Контейнер нужен для внедрения зависимостей. Я об этом уже рассказывал раньше. В простом виде это что-то вроде сервисов, которые будут доступны в разных частях приложения.

Контейнер привязывается к основному объекту приложения $app, поэтому, если в каком-то другом классе нужно использовать контейнер, то его можно получить из $app.

Для начала нам потребуется установить PHP-DI с помощью Composer. Наберите в консоли:

composer require php-di/php-di --with-all-dependencies

Ключ «—with-all-dependencies» нужен для того, что Slim и PHP-DI используют одни и те же PSR-интерфейсы, но в PHP-DI указана зависимость от более низкой версии.

Теперь подключаем его в index.php. Я опять привожу полный код, чтобы никто не запутался:

<?php
 
use PsrHttpMessageServerRequestInterface;
use PsrHttpMessageResponseInterface;
use SlimFactoryAppFactory;
use DIContainer; // это и есть PHP-DI
 
// подключаем Composer
require 'vendor/autoload.php';
 
// Создание контейнера PHP-DI
$container = new Container();
 
// Создаём объект приложения Slim и передаём в него контейнер
AppFactory::setContainer($container);
$app = AppFactory::create();
 
// указываем базовый путь для роутинга — поскольку это подкаталог
$app->setBasePath('/slim1');
 
$app->addErrorMiddleware(false, false, false);
 
// http://demo/slim1/
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Home page');
    return $response;
});
 
// http://demo/slim1/hello
$app->get('/hello', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Hello world!');
    return $response;
});
 
// запускаем приложение
$app->run();

Важно здесь то, что контейнер нужно добавлять перед созданием приложения. Перед тем, как создавать $app нужно передать ему контейнер через setContainer(). Почему так? Обратите внимание, что AppFactory — это статические методы. Когда мы вызываем AppFactory::create(), то по сути происходит инстанцирование объекта класса SlimApp:

    public static function create(
        ?ResponseFactoryInterface $responseFactory = null,
        ?ContainerInterface $container = null,
        ?CallableResolverInterface $callableResolver = null,
        ?RouteCollectorInterface $routeCollector = null,
        ?RouteResolverInterface $routeResolver = null,
        ?MiddlewareDispatcherInterface $middlewareDispatcher = null
    ): App {
        static::$responseFactory = $responseFactory ?? static::$responseFactory;
        return new App(
            self::determineResponseFactory(),
            $container ?? static::$container,
            $callableResolver ?? static::$callableResolver,
            $routeCollector ?? static::$routeCollector,
            $routeResolver ?? static::$routeResolver,
            $middlewareDispatcher ?? static::$middlewareDispatcher
        );
    } 

Поскольку AppFactory::create() мы вызываем без параметров, то будут использованы переменные из текущего static-класса. В нашем случае static::$container.

После этого контейнер можно получить из приложения:

$container = $app->getContainer();

Сейчас у нас нет зависимостей для контейнера, поэтому давайте добавим библиотеку для логирования Monolog в виде сервиса. Для этого установим её с помощью Composer:

composer require monolog/monolog

Теперь добавим в начало файла:

use MonologLogger;
use MonologHandlerStreamHandler;

В после создания $app добавим сервис logger

// Создадим объект для логирования через контейнер
$container->set('logger', function () {
    $log = new Logger('general');
    $log->pushHandler(new StreamHandler(__DIR__ . '/var/log/app.log', Logger::INFO));
 
    return $log;
});

Чтобы логи сохранялись создадим каталог «var/log» — в нём Monolog будет хранить файлы логов.

Добавим для примера логирование главной страницы:

// http://demo/slim1/
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) use ($app) {
    
    $container = $app->getContainer();
 
    // если установлен лог, то пишем в него сообщение об ошибке
    if ($container->has('logger'))
        $container->get('logger')->info('Home');
    
    $response->getBody()->write('Home page');
    
    return $response;
});

Чтобы получить доступ к $app внутри функции, мы используем use. На практике, скорее всего так делать не придётся, поскольку при работе с php-классами для роутинга, Slim автоматически будет передавать контейнер в конструктор. Это мы рассмотрим позже.

Сейчас главное понять общий принцип работы контейнера. Вначале нужно убедиться, что сервис logger существует, а потом его использовать как это определено в его классе Monolog.

Если вы посмотрите на файл var/log/app.log, то увидите, что в нём стали появляться новые строчки.

Использование контейнера, а точнее его сервисов, делает приложение немного проще. После мы сможем выделить создание сервисов отдельным файлом — так будет удобней ими управлять.

Стоит отметить ещё один подход к проектированию приложения. Мы создали контейнер и потом, если нужно, получаем его из объекта $app. Но можно поступить наоборот: создать контейнер, потом создать в нем сервис app и потом уже получить его из контейнера для работы. То есть главным становится уже контейнер.

Приведу полный рабочий код, чтобы было понятно:

<?php
 
use PsrHttpMessageServerRequestInterface;
use PsrHttpMessageResponseInterface;
use SlimFactoryAppFactory;
use DIContainer;
use MonologLogger;
use MonologHandlerStreamHandler;
 
// подключаем Composer
require 'vendor/autoload.php';
 
// Создание контейнера
$container = new Container();
 
// создаем сервис app — основной объект приложения
$container->set('app', function () {
    return AppFactory::create();
});
 
// получаем из контейнера и работаем как обычно
$app = $container->get('app');
 
// указываем базовый путь для роутинга — поскольку это подкаталог
$app->setBasePath('/slim1');
 
// отлавливаем ошибки
$app->addErrorMiddleware(false, false, false);
  
// Создадим объект для логирования через контейнер
$container->set('logger', function () {
    $log = new Logger('general');
    $log->pushHandler(new StreamHandler(__DIR__ . '/var/log/app.log', Logger::INFO));
 
    return $log;
});
 
// http://demo/slim1/
// обратите внимание, что здесь уже применяется use $container
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) use ($container) {
    
    // если установлен лог, то пишем в него сообщение об ошибке
    if ($container->has('logger'))
        $container->get('logger')->info('Home');
    
    // если нужно, то можно получить $app из контейнера
    // $app = $container->get('app');
    // var_dump($app->getContainer()); // null
    
    $response->getBody()->write('Home page');
    
    return $response;
});
 
// http://demo/slim1/hello
$app->get('/hello', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Hello world!');
    return $response;
});
 
// запускаем приложение
$app->run();
 
# end of file

Поскольку $app у нас хранится как сервис app, то мы можем получить его в любом месте. Но при этом $app уже не управляет контейнером. Если выполнить:

$app = $container->get('app');
var_dump($app->getContainer());

То получим null.

И хотя такой подход часто встречается, на мой взгляд всё-таки удобней пользоваться $app, в который внедрён контейнер. Тем более, что в Slim контейнер может автоматически передаваться в конструктор Action, а обработчик Middleware также может получить нужный $app. Поэтому потребность прятать приложение в контейнер крайне мала.

И еще раз возвращаясь к логированию. Мы добавили лог к главной странице, явно прописав его в самом роуте. Как вы понимаете, для этого несколько удобней использовать Middleware.

// не забудьте добавить
// use PsrHttpServerRequestHandlerInterface; 
 
...
// логирование всех запросов
$logMiddleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($app) {
    
    $container = $app->getContainer();
  
    // если установлен лог, то пишем в него текущий url-путь
    if ($container->has('logger'))
        $container->get('logger')->info($request->getUri()->getPath());
    
    $response = $handler->handle($request);
    
    return $response;
};
 
// добавим его в приложение
$app->add($logMiddleware);
...

Теперь вместо того, чтобы менять роуты, достаточно использовать $logMiddleware.

Структура приложения

Теперь мы уже знакомы с основами Slim, поэтому можно расширить наше приложение. Пока у нас всё в одном файле index.php, что не совсем правильно. Нам нужно придумать структуру каталогов, где мы будем хранить наши файлы. Тем более, что нам предстоит ещё разобраться как перейти на ОПП вместо функций роутинга и Middleware.

Если рассматривать типовые варианты именования каталогов, то обычно выделяют следующие.

  • Каталог vendor — это каталог для Composer. Мы его не меняем и всегда используем его автозагрузку файлов (PSR-4).
  • Каталог var — это каталог для изменяемых файлов. Это логи, кэш, может быть файлы SQLite.
  • Каталог app — каталог пользовательского приложения. Именно здесь размещаются файлы конфигурации и прочих модулей.
  • Каталог src — это каталог, где содержится «ядро» создаваемого приложения/фреймворка/CMS. Например если вы делаете публичный проект, то этот каталог может обновляться «накатом» — здесь не должно быть пользовательских файлов. Часто вместо src используют system/library/core и т.п.
  • Каталог resources — предназначен для прочих файлов проекта.

Отдельно стоит отметить каталог public (иногда web). Обычно он указывает на web-каталог сервера public_html. То есть в этом каталоге размещаются только те файлы, доступ к которым возможен через браузер. Все остальные файлы размещаются на более высоком уровне сервера и недоступны извне. Такая схема хороша, но только если у вас не виртуальный хостинг.

Поскольку в 99% случаев сайты используют именно вирутальный хостинг, то каталог public не используется — всё располагается в public_html вашего сервера. Для закрытия доступа используется .htaccess с директивой Deny from all, поэтому особых проблем с безопасностью не будет. Ну и плюс не стоит использовать открытые файлы, вроде .env для конфигураций и хранения секретных данных. Вместо этого нужно использовать обычные php-файлы с массивами. Тогда даже прямой вызов файла никаких проблем не вызывает.

Поэтому мы будем строить так, чтобы приложение можно было использовать на виртуальном хостинге, но при желании, можно будет его перенести на любой уровень выше. Здесь вопрос чисто технический — достаточно определить базовый константы.

Что у итоге получается? Пусть у нас будет такая структура:

app/
    Config/
    Modules/
    Middleware/
    bootstrap.php
  
src/
    Core/
        functions.php
    Debug/
        Pr.php
 
var/
    cache/
    log/
    sqlite/
    
.htaccess
index.php
composer.json

Файл index.php — фронт-контролёр. В его задачу входит создание базовых констант (это зависит от расположения файла) и передача управления в app/bootstrap.php.

В файле bootstrap.php формируется и запускается приложение Slim.

Дополнительные зависимости будут храниться в каталоге Config.

Каталог Modules будет хранить модули приложения. Мы говорили о концепции MVC и ADR, но вопрос лишь в том, как именно должны разделяться файлы одного модуля. Обычно принято хранить контролёры отдельно от моделей и представлений. Лично мне такой подход не нравится: я предпочитаю файлы модуля держать в одном месте. А какая там будет использоваться концепция — уже значения не имеет. Ну и кроме того, мы всего лишь знакомимся с Slim, поэтому большой разницы в этом разрезе не будет.

Каталог Middleware будет хранить классы Middleware.

Каталог src для хранения дополнительного функционала. Мы используем Composer, но часть кода придётся написать вручную. Поэтому здесь будут храниться функции и какие-то дополнительные классы.

Давайте начнём с чистого листа. Пусть у нас будет проект http://demo/slim2.

Вы можете сразу обратиться к итоговому коду, который я опубликовал на GitHub (github.com/maxsite/Slim-Skeleton). По ходу статьи я буду показывать откуда и что получилось.

Файл composer.json будет таким:

{
    "require": {
        "slim/slim": "4.*",
        "slim/psr7": "^1.5",
        "php-di/php-di": "^6.3",
        "monolog/monolog": "^2.3"
    },
    "autoload": {
        "psr-4": { 
			"App\": "app/",
			"Lib\": "src/"
		}
    }
} 

Здесь мы подключаем зависимости, а также определяем автозагрузку PSR-4. То есть namespace App будет ссылаться на каталог app, а Lib на src. Если вы захотите поменять расположение каталогов, то достаточно это будет сделать в этом файле и потом обновиться через Composer.

Теперь запускаем:

composer update

Появится каталог vendor. Зависимости установлены.

Файл index.php сделаем таким:

<?php
 
define('BASE_DIR', dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR);
define('SLIM_APP_BASEPATH', '/' . basename(__DIR__));
 
require BASE_DIR . 'app/bootstrap.php';
 
# end of file

Здесь мы определяем каталог BASE_DIR. Это нужно для того, чтобы зафиксировать корневой каталог приложения и все остальные файлы можно подключать относительно него. В теории можно ещё переопределить и app, var, но это уже экономия на спичках.

Константа SLIM_APP_BASEPATH нам нужна для того, чтобы использовать в setBasePath(). Раньше мы её определяли вручную, теперь используем на основе текущего каталога. Если у вас какое-то особое расположение сайта, то можно указать в этом файле, а не трогать bootstrap.php.

Теперь давайте разбираться с bootstrap.php. Мы уж знаем, что перед запуском приложения Slim необходимо выполнить несколько задач. Мы их выделим в отдельные файлы и сохраним в каталоге Config.

Первое что нужно выделить отдельно — это создание зависимостей для контейнера. Пусть это будет app/Config/container.php, где мы разместим код создания логирования.

<?php
 
use MonologLogger;
use MonologHandlerStreamHandler;
 
// Создадим объект для логирования через контейнер
// https://github.com/Seldaek/monolog
$container->set('logger', function () {
    $log = new Logger('general');
    $log->pushHandler(new StreamHandler(BASE_DIR . 'var/log/app.log', Logger::INFO));
 
    return $log;
});
 
# end of file

После контейнера мы можем выделить какие-то настройки в app/Config/settings.php. Пока мы оставим файл пустым, но в теории в нем можно будет хранить настройки всего приложения. А поскольку их можно будет держать в контейнере, то подключаем после container.php.

Потом у нас пойдут Middleware в файле app/Config/middleware.php. Пока он пусть будет пустым.

Также у нас будет ещё один особый Middleware для отлова ошибок (мы используем его для метода addErrorMiddleware). Пусть это будет файл app/Config/errorMiddleware.php.

<?php
 
$app->addErrorMiddleware(false, false, false);
 
# end of file

После этого нам потребуется файл для задания маршрутов. Выделим для этого файл app/Config/routes.php и разместим там наши адреса:

<?php
 
use PsrHttpMessageServerRequestInterface;
use PsrHttpMessageResponseInterface;
 
// http://demo/slim2/
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Home page');
    return $response;
});
 
// http://demo/slim2/hello
$app->get('/hello', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $response->getBody()->write('Hello world!');
    return $response;
});
 
# end of file

Но это ещё не всё. В самом начале нам нужно подключить src/Core/functions.php — это какие-то функции, которые могут использоваться на уровне всего приложения. Как правило всегда есть небольшие хелперы, которые использует разработчик. Например я использую функцию pr(), которая есть в MaxSite CMS и Albireo для отладки кода (это обертка для print_r).

Вот что у нас получилось в итоге:

<?php
 
use SlimFactoryAppFactory;
use DIContainer;
 
require BASE_DIR . 'vendor/autoload.php';
require BASE_DIR . 'src/Core/functions.php';
 
$container = new Container();
 
AppFactory::setContainer($container);
$app = AppFactory::create();
 
if (SLIM_APP_BASEPATH) $app->setBasePath(SLIM_APP_BASEPATH);
 
require BASE_DIR . 'app/Config/container.php';
require BASE_DIR . 'app/Config/settings.php';
require BASE_DIR . 'app/Config/middleware.php';
require BASE_DIR . 'app/Config/errorMiddleware.php';
require BASE_DIR . 'app/Config/routes.php';
 
$app->run();

Попробуйте посмотреть сайт в браузере — всё должно работать. Теперь работать с приложением будет удобней.

Модули для роутинга. Отлов 404-ошибки

Мы пока работали с callback-функциями, а по хорошему нужно всё делать через php-классы. Для начала разберёмся с тем, как работать роутингом. Например мы хотим оформить главную страницу в виде обычного html-файла.

Для этого нам нужно привязать в роутинге адрес к определенному классу. Откроем файл routes.php и вместо существующего кода для главной пропишем новый класс.

$app->get('/', 'AppModulesHomeHome:home');

Эту строчка указывает на то, чтобы Slim передал управление классу AppModulesHomeHome с методом home(). Поскольку у нас PSR-4, то это будет соответствовать файлу app/Modules/Home/Home.php

<?php
 
/**
 * Вывод главной страницы
 */
 
namespace AppModulesHome;
 
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrContainerContainerInterface;
 
class Home
{
    private $container;
 
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
 
    public function home(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        // простой шаблонизатор
        $data = ['name' => 'Вася'];
        $body = getTmpl(__DIR__ . '/home.template.php', $data);
 
        // формируем ответ
        $response->getBody()->write($body);
 
        return $response;
    }
}
 
# end of file

Рассмотрим этот код подробнее. Вначале указывается namespace, который совпадает с каталогом расположения файла. так работает PSR-4. Дальше секция use в которой перечисляются используемые типы данных.

Сам класс состоит из конструктора, который принимает контейнер приложения. Я об этом уже упоминал, поэтому если нужно, контейнер будет сразу доступен в нашем классе.

Метод home() по сути и есть наша исходная callback-функция. Она имеет те же самые входные и выходные параметры. Но вместо простого текста, мы передаём в тело http-ответа содержимое файла шаблона home.template.php:

<!DOCTYPE html>
<html>
<h1>Hello, <?= $name ?>!</h1>
<h2>This is Slim4</h2>
</html>

Для шаблонизатора используется core-функция getTmpl(), которая принимает имя файла и массив данных. Массив будет автоматом развёрнут в обычные php-переменные: в нашем примере в шаблоне появится переменная $name.

После обновления главная страница выведет:

Hello, Вася!
This is Slim4

Подобный подход часто используется в MVC. Здесь $data — это то, что будет получено из Модели, а $body сформирован из View. Если вам нравится такой вариант, то вы легко сможете добавить нужные классы.

Очень часто класс контролёра MVC содержит сразу несколько методов. Например контролёр работы с формой может содержать кучу методов. Таким образом в роутинге нужно будет прописывать что-то вроде такого:

$app->get('/form', 'AppModulesFormController:show');
$app->post('/form', 'AppModulesFormController:create');

То есть здесь указывается один и тот же контролёр, но меняются его методы.

В качестве альтернативы можно использовать модель Action Domain Responder (ADR), где в основе лежит Action (действие). Action также представляет собой отдельный класс, но только с методом __invoke(). Это магический php-метод, который позволяет использовать класс как функцию. Таким образом меняется и задание роутинга, и сам класс.

<?php
 
/**
 * Вывод главной страницы
 */
 
namespace AppModulesHome;
 
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrContainerContainerInterface;
 
class Home
{
    private $container;
 
    // constructor receives container instance
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
 
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        // простой шаблонизатор
        $data = ['name' => 'Вася'];
        $body = getTmpl(__DIR__ . '/home.template.php', $data);
 
        // формируем ответ
        $response->getBody()->write($body);
 
        return $response;
    }
}

Роутинг же задается так:

$app->get('/', AppModulesHomeHome::class);

Результат будет точно таким же, но с __invoke() можно не думать над именем метода.

Что касается ADR, то в ней принимается то, что Action должен описывать только одно единственное действие. В случае с «классическим» MVC, контролёр обычно содержит сразу много действий. Если следовать ADR, то каждый Action будет отдельным файлом. С одной стороны это увеличивает их количество, а с другой с ними легче работать. Кроме того, такие одиночные файлы (Action) могут использоваться повторно, поскольку не содержат зависимостей от других Action.

Какой вариант выбрать зависит только от вас. Slim не накладывает ограничений на структуру приложений.

Теперь давайте я покажу как можно перехватить 404-ошибку и вместо сообщения Slim выводить свой вариант, основанный на html-шаблоне.

Для начала откроем файл errorMiddleware.php и сделаем его таким:

<?php
 
use PsrHttpMessageServerRequestInterface;
 
$customErrorHandler = function (
    ServerRequestInterface $request,
    Throwable $exception
) use ($app) {
    return AppModulesPage404Page404::response($app, $exception, $request);
};
  
if ($container->has('logger'))
    $errorMiddleware = $app->addErrorMiddleware(true, true, true, $container->get('logger'));
else
    $errorMiddleware = $app->addErrorMiddleware(true, true, true);
 
$errorMiddleware->setDefaultErrorHandler($customErrorHandler);
 
# end of file

Здесь мы регистрируем для приложения свой обработчик ошибочных Middleware. Сам по себе обработчик будет возвращать наш класс AppModulesPage404Page404. Метод response() мы делаем статичным, чтобы упростить его вызов (но при желании можно сделать обычным классом). Также мы сразу передаём в приложение сервис logger, проверив что он есть.

Теперь делаем файл app/Modules/Page404/Page404.php:

<?php
 
/**
 * Модуль, который отвечает за вывод 404-ошибки
 */
 
namespace AppModulesPage404;
 
use PsrHttpMessageServerRequestInterface;
use FigHttpMessageStatusCodeInterface;
 
class Page404
{
    public static function response($app, Throwable $exception, ServerRequestInterface $request)
    {
        $container = $app->getContainer();
 
        // если установлен лог, то пишем в него сообщение об ошибке
        if ($container->has('logger'))
            $container->get('logger')->error('404', [$exception->getMessage()]);
 
        // простой шаблонизатор
        $body = getTmpl(__DIR__ . '/404.template.php');
 
        // формируем ответ
        // указываем http-код ошибки 404
        $response = $app->getResponseFactory()->createResponse(StatusCodeInterface::STATUS_NOT_FOUND);
 
        $response->getBody()->write($body);
 
        return $response;
    }
}
 
# end of file

Ну и сам html-шаблон в файле 404.template.php:

<!DOCTYPE html>
<html>
<h1>404</h1>
<h2>Not Found</h2>
</html>

В принципе этот код уже нам знаком, обращу только момент на параметр метода createResponse(), через который мы формируем http-ответ. Здесь указывается статус STATUS_NOT_FOUND, что соответствует коду 404. Если этого не сделать, то Slim будет отдавать обычный код 200, что для ненайденной страницы неверно.

Метод AppModulesPage404Page404::response() может быть совершенно произвольным. Главное, чтобы он возвращал response-объект.

Использование своего обработчика $errorMiddleware один из способов отлова 404-страниц. Другой способ я уже показывал раньше — это указать последним роутер:

$app->get('/{slug}', AppModulesсвой-404-класс::class);

А потом создать этот класс по аналогии с Home.

Какой вариант использовать — зависит от разработчика.

Добавляем Middleware как PHP-классы

Как работать с Middleware мы уже знаем, поэтому рассмотрим вопрос как их переделать из функций в PHP-классы. У нас есть каталог app/Middleware, где будут храниться классы, а подключать их к приложению нужно будет в app/Config/middleware.php В этом случае они будут работать для всех адресов.

Если же нужно прицепить Middleware к определённому маршруту, то делается это уже в файле роутинга app/Config/routes.php

Для начала создадим файл app/Middleware/AfterMiddleware.php:

<?php
 
/**
 * Пример Middleware
 */
 
namespace AppMiddleware;
 
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerRequestHandlerInterface;
use SlimPsr7Response;
 
class AfterMiddleware
{
    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        $response = $handler->handle($request);
        $response->getBody()->write('AFTER');
  
        return $response;
    }
}
 
# end of file

Это пример из справки Slim. Здесь мы также используем __invoke(), чтобы упростить вызов класса. Теперь добавим в файл app/Config/middleware.php

$app->add(AppMiddlewareAfterMiddleware::class);

Теперь на каждой странице будет текстовая приписка «AFTER». Если же нужно добавить Middleware для конкретного маршрута, то указываем это в его роутинге:

$app->get('/', AppModulesHomeHome::class)->add(AppMiddlewareAfterMiddleware::class);

По такому принципу строятся все Middleware. Но мы рассмотрим ещё один пример из справки Slim — класс BeforeMiddleware:

<?php
 
/**
 * Пример Middleware
 */
 
namespace AppMiddleware;
  
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerRequestHandlerInterface;
use SlimPsr7Response;
use PsrContainerContainerInterface;
 
class BeforeMiddleware
{
    private $container;
    
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
 
    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        $response = $handler->handle($request);
        $existingContent = (string) $response->getBody();
    
        $response = new Response();
        $response->getBody()->write('BEFORE' . $existingContent);
    
        return $response;
    }
}
 
# end of file

Здесь я добавил конструктор класса, через который можно получить контейнер приложения. Например если нужен сервис logger, то мы берём его из контейнера.

Теперь обратите внимание, как формируется ответ. Вначале получается текущее тело запроса, а потом создаётся новый объект, в который отправляется изменённый текст. Суть в том, что ответ $response можно получить как из handler-обработчика, так и создать самостоятельно новый.

Настройки Settings

Наше приложение уже почти готово, осталось добавить только settings-настройки. Где они могут пригодится? Например можно хранить управление выводом ошибок для addErrorMiddleware(). Скажем для отладки было бы полезным получить полный и красивый отчёт вывода. Логично эти опции вынести в отдельный файл.

Есть несколько подходов к организации настроек сайта, но наиболее привычным способом будет использование в конфигурационном файле обычного php-массива, который возвращается по return. Например файл app/Config/app.php:

return [
    'ErrorMiddleware' => [
        'customErrorHandler' => false, // свой обработчик errorMiddleware
        'displayErrorDetails' => true, // выводить детали ошибки
        'logErrors' => false, // писать в лог
        'logErrorDetails' => false, // детали в лог
    ],
];

Но нюанс в том, что эти данные нам могут понадобиться в разных частях программы, поэтому правильным было бы перенести их в общий контейнер. Второй момент — может получиться так, что нужно будет работать с разными конфигурационными файлами, работающими по этому же принципу (возврат по return). Например, если нужно будет добавить конфигурационный файл для баз данных db.php. Поэтому нужен механизм, позволяющий работать с разными файлами.

Для этого мы будем использовать простой класс LibSettingsSettings, который будет храниться в контейнере, но перед этим он считает все указанные конфигурационные файлы из каталога app/Config.

Таким образом файл app/Config/settings.php получится таким:

<?php
 
// alias => файл конфигурации
$files = [
    'app' => 'app.php',
    'db' => 'db.php',
];
 
// сохраняем в контейнере
$container->set('config', function () use ($files) {
    return new LibSettingsSettings($files);
});
 
# end of file

В массиве $files мы указываем имя файла и его псевдоним/ключ. Дальше сохраняем в контейнере класс Settings. Он в свою очередь сам считает все указанные файлы.

<?php
 
namespace LibSettings;
 
class Settings
{
    private $settings = [];
 
    public function __construct(array $files)
    {
        $pathConfig = BASE_DIR . 'app/Config/';
 
        foreach ($files as $alias => $file) {
            if (file_exists($pathConfig . $file)) {
                $this->settings[$alias] = require $pathConfig . $file;
            }
        }
    }
 
    public function getAlias(string $alias, $default = [])
    {
        return $this->settings[$alias] ?? $default;
    }
}
 
# end of file

Для получения массива из файла конфигурации, вначале нужно получить экземпляр объекта Settings из контейнера, а потом выполнить метод getAlias().

$confApp = $container->get('config')->getAlias('app');
 
pr($confApp);
 
Array
(
    [ErrorMiddleware] => Array
        (
            [customErrorHandler] => 
            [displayErrorDetails] => 1
            [logErrors] => 
            [logErrorDetails] => 
        )
)

Для примера используем эту конфигурацию в errorMiddleware.php. Я добавил немного логики, чтобы более гибко управлять 404-ошибками.

<?php
 
use PsrHttpMessageServerRequestInterface;
 
// получаем данные из контейнера
$confApp = $container->get('config')->getAlias('app');
 
// пошли опции
$customErrorHandler = $confApp['ErrorMiddleware']['customErrorHandler'] ?? true;
$displayErrorDetails = $confApp['ErrorMiddleware']['displayErrorDetails'] ?? false;
$logErrors = $confApp['ErrorMiddleware']['logErrors'] ?? false;
$logErrorDetails = $confApp['ErrorMiddleware']['logErrorDetails'] ?? false;
 
if ($customErrorHandler) {
    // Define Custom Error Handler
    $customErrorHandler = function (
        ServerRequestInterface $request,
        Throwable $exception
    ) use ($app) {
        return AppModulesPage404Page404::response($app, $exception, $request);
    };
 
    // Add Error Middleware
    if ($container->has('logger'))
        $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails, $container->get('logger'));
    else
        $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails);
 
    $errorMiddleware->setDefaultErrorHandler($customErrorHandler);
} else {
    // Slim вариант вывода ошибок
    if ($container->has('logger'))
        $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails, $container->get('logger'));
    else
        $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails);
}
 
# end of file

Дальше просто. Если нужно добавить новый конфигурационный файл, то достаточно его указать в app/Config/settings.php. После он автоматом станет доступным через контейнер.

Работа с модулями приложения

В целом каркас приложения уже готов, но мы добавим в него ещё несколько штрихов, чтобы сделать работу с модулями более удобной. Лично я привык именно к модульной структуре, когда все файлы модуля расположены в одном месте. С практической точки зрения это намного удобней, чем раскидывать файлы по разным частям приложения.

Работать с такими модулями удобно ещё и потому что они по сути автономны. Если модуль не нужен, то достаточно его просто удалить. Но вот чтобы подключить модуль нам нужно предусмотреть механизм, который будет автоматически подключать файлы модуля к основному приложению. Например модуль имеет свой роутинг. Если он фиксированный, то было бы здорово сделать так, чтобы он автоматом подключался к основному routes.php.

C другой стороны у модуля могут быть и другие задачи, например работа с контейнером или регистрация своих php-классов. То есть мы не знаем наверняка что именно будет делать модуль, но он должен получить доступ к $app до его запуска.

Поэтому поступим хитро. Подключим в bootstrap.php файл app/Config/initModules.php

...
require BASE_DIR . 'app/Config/errorMiddleware.php';
require BASE_DIR . 'app/Config/initModules.php';
require BASE_DIR . 'app/Config/routes.php';
...

Сам же initModules.php будет автоматом искать файлы init.php в каждом модуле и подключать как обычно.

<?php
 
$allFiles = glob(BASE_DIR . 'app/Modules/*/init.php');
 
foreach ($allFiles as $file) {
    require $file;
}
 
# end of file

Таким образом, у разработчика модуля будет возможность с помощью файла init.php добавить свои роуты.

Для примера сделаем модуль Form, где рассмотрим работу с обычной формой. Создадим каталог app/Modules/Form и в нём несколько файлов.

Роутинг пропишем в init.php

// http://demo/slim2/form
$app->get('/form', AppModulesFormFormShow::class);
$app->post('/form', AppModulesFormFormPost::class);

Форма будет иметь два состояния: отображение самой формы и приём данных. Отображение формы будет формироваться в FormShow.php. Приём будет идти в FormPost.php с одноименными классами. Я буду следовать не MVC, а ADR, поэтому в классах буду использовать метод __invoke().

В качестве файла с html-кодом формы будем использовать show.template.php:

<!DOCTYPE html>
<html>
<h1>Sample form</h1>

<?= $message ?>

<div>
    <form method="POST">
        <div><label>Name: <input type="text" name="name"></label></div>
        <div><label>Email: <input type="text" name="email"></label></div>
        <div><button type="submit">Send</button></div>
    </form>
</div>

</html>

Поскольку это демо-пример, то разметка примитивная.

Теперь сделаем FormShow.php, который будет просто выводить эту форму:

<?php
 
/**
 * Вывод формы
 */
 
namespace AppModulesForm;
 
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
 
class FormShow
{
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        $body = getTmpl(__DIR__ . '/show.template.php', ['message' => '']);
        $response->getBody()->write($body);
 
        return $response;
    }
}
 
# end of file

Переменная $message будет содержать результат отправки формы. При начальном отображении там пусто.

Блок отображения также сделаем в виде шаблона message.template.php.

<div>
    <div><b>Name:</b> <?= htmlspecialchars($name) ?></div>
    <div><b>Email:</b> <?= htmlspecialchars($email) ?></div>
</div>

В нём будут выводиться данные формы.

Теперь класс обработчика POST-запроса. Файл FormPost.php

<?php
 
/**
 * Получение post от формы
 */
 
namespace AppModulesForm;
 
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
 
class FormPost
{   
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        // получить данные из формы
        $parsedBody = $request->getParsedBody();
  
        $message = getTmpl(__DIR__ . '/message.template.php', [
            'name' => $parsedBody['name'],
            'email' => $parsedBody['email'],        
        ]);
  
        $body = getTmpl(__DIR__ . '/show.template.php', ['message' => $message]);
  
        $response->getBody()->write($body);
  
        return $response;
    }
}
 
# end of file

Данные от формы получаем в запросе через метод getParsedBody(). На выходе обычный массив, как если бы мы работало через $_POST. Дальше формируем блок сообщения $message с помощью шаблона message.template.php. В него мы и передаём данные формы. А уже после ещё раз выводим форму, где и выводится блок message.

Логика примера достаточно простая, я специально не стал её усложнять, чтобы можно было легко разобраться. На практике же нужно ещё добавить валидацию входящих данных, потом какие-то действие, поскольку форма нужна для каких-то задач (например отправка на email).

Несколько слов о шаблонах.

Сейчас это очень простые файлы, которые принимают готовые php-переменные. По этому принципу работают практически все существующие шаблонизаторы. Разница только в «синтаксическом сахаре» и дополнительных функциях. Поэтому не важно какой именно будет шаблонизатор использоваться в вашем проекте, суть будет одна — указать имя шаблона и массив данных.

Но есть ещё один момент, на который нужно обратить внимание. Представьте себе, что вы создаёте «движок», а не готовый сайт. Поэтому html-разметка будет отличаться от вашего исходного варианта. Почти все современные php-фреймворки рассчитаны только на одну разметку, поэтому у конечного пользователя нет другой возможности, кроме как менять шаблон в самом модуле или каком-то выделенном каталоге (например templates). То есть вопрос, что фреймворк будет поддерживать шаблоны сайта, как это сделано в любой CMS, обычно разработчиками не ставится.

Сложность здесь в том, что файл шаблона (например show.template.php) имеет довольно жесткую привязку заданному каталогу. Таким образом, единственный правильный способ — это сделать так, чтобы модуль брал сам на себя проверку пользовательского файла шаблона. Примерно так:

    ...
    
    $fileTmpl = '/show.template.php';
    
    if (file_exists(BASE_DIR . 'resources/views/Modules/Form' . $fileTmpl))
        $file = BASE_DIR . 'resources/views/Modules/Form' . $fileTmpl;
    else
        $file = __DIR__ .  $fileTmpl;
 
    $body = getTmpl($file, ['message' => '']);
    ...

Либо можно воспользоваться небольшой функцией-хелпером findFileTmpl(), которая возвращает имя файла либо в пользовательском каталоге (resources/views/Modules/Form), либо в каталоге модуля (app/Modules/Form).

    ...
    
    $file = findFileViews('Modules/Form', 'show.template.php'); // каталог модуля, файл
    $body = getTmpl($file, ['message' => '']);
    ...

То есть модуль позволяет разместить файл шаблона в любом другом предопределенном каталоге. Если «движок» предполагает разные шаблоны сайта (как в CMS), то выбранный каталог может указываться в общей конфигурации. Таким образом, если у пользователя возникнет задача изменить вёрстку формы, то ему не нужно будет переписывать файлы модуля.

Ну и плюс такого подхода в том, что мы можем размещать файлы абсолютно произвольно, что редко встречается в других php-фрейморках.

В любом случае вопрос о шаблонизации сайта несколько выходит за рамки статьи, и этот вопрос отдан на откуп разработчику.

Итого

Slim 4 хорош для проектов, где использование больших фреймворков не оправдано. Главная «фишка» Slim — это его поддержка стандартов PSR, что позволяет без особых сложностей подключать и использовать сторониe PSR-библиотеки к своему приложению.

Итоговый код вы можете скачать с моего репозитория на GitHub: github.com/maxsite/Slim-Skeleton.

In Slim 4 Skeleton, the 404 response handled in AppApplicationHandlersHttpErrorHandler::respond() which check against SlimExceptionHttpNotFoundException. We can create a templated 404 page for it with utilize SlimCallableResolver via callableResolver property which can resolve the callable handler. I assume that we are using Twig template engine, and already has setup of Twig service like in my previous post.

We can create a not found handler like the following in src/Application/Handlers/NotFoundHandler.php

<?php
// src/Application/Handlers/NotFoundHandler.php
declare(strict_types=1);

namespace AppApplicationHandlers;

use PsrHttpMessageResponseInterface;
use SlimViewsTwig;

use function compact;

class NotFoundHandler
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }

    public function __invoke(
        ResponseInterface $response,
        string            $message
    ): ResponseInterface {
        return $this->view->render($response, '404.html.twig', compact('message'));
    }
}

We can create a view based on it like the following at templates/404.html.twig:

{# templates/404.html.twig #}

{% extends "layout.html.twig" %}
{% block title '404 - '~parent() %}

{% block body %}

{{ message }}

{% endblock %}

Now, in AppApplicationHandlersHttpErrorHandler::respond(), we can check when $exception instanceof HttpNotFoundException to make a self called resolved NotFoundHandler.

// src/Application/Handlers/HttpErrorHandler.php
// ...
    protected function respond(): Response
    {
        // ...
        if ($exception instanceof HttpNotFoundException) {
            $response = $this->responseFactory->createResponse($statusCode);
            return ($this->callableResolver->resolve(NotFoundHandler::class)(
                $response,
                $exception->getMessage()
            ));
        }
        // ...
    }
// ...

So, when the SlimExceptionHttpNotFoundException thrown, it will shown the 404 page with brought the message passed into it like the following:

Bonus

We can create a handling against Request as well, eg: show 404 templated page only when request doesn’t has Accept: application/json or X-Requested-With:XmlHttpRequest header, so, we can modify like the following:

// src/Application/Handlers/HttpErrorHandler.php
// ...
    protected function respond(): Response
    {
        // ...
        if ($exception instanceof HttpNotFoundException) {            
            $isAppJsonAccept  = $this->request->getHeaderLine('Accept')           === 'application/json';
            $isXmlHttpRequest = $this->request->getHeaderLine('X-Requested-With') === 'XmlHttpRequest';

            if (! $isAppJsonAccept && ! $isXmlHttpRequest) {
                $response = $this->responseFactory->createResponse($statusCode);
                return ($this->callableResolver->resolve(NotFoundHandler::class)(
                    $response,
                    $exception->getMessage()
                ));
            }

            // already return early, no need else
            $error->setType(ActionError::RESOURCE_NOT_FOUND);
        }
        // ...
    }
// ...

So, for example, when called via ajax, it will show like the following:

Документация

docs/v4/

Slim 4 Изменения

  • Slim-4-Roadmap
  • Slim/issues/1686
  • slim-4.0.0 alpha-release

Установка

  • installation

Заготовки

  • Slim-Skeleton
  • odan/slim4-skeleton
  • adriansuter/Slim4-Skeleton
  • akrabat/slim4-starter

Ошибка 404

Вначале у многих людей возникает проблема с сообщением об ошибке: Ошибка 404 (не найдено)

Сначала убедитесь, что вы добавили RoutingMiddleware:

$app = AppFactory::create();

// Add Routing Middleware
$app->addRoutingMiddleware();

// ...
$app->run();

Далее, убедитесь, что установлен правильный базовый путь (base path):

$app->setBasePath('/my-base-path');

Запуск Slim 4 из подпапки

Данная библиотека может быть использована для определения и установки корректного базового пути:

  • selective-php/basepath

Получение текущего маршрута

$route = SlimRoutingRouteContext::fromRequest($request)->getRoute();

Получение аргументов текущего маршрута

$routeArguments = SlimRoutingRouteContext::fromRequest($request)
    ->getRoute()
    ->getArguments();

Доступ к парсеру маршрутов (Route Parser)

$routeParser = SlimRoutingRouteContext::fromRequest($request)->getRouteParser();

Получение базового пути (base path)

$basePath = SlimRoutingRouteContext::fromRequest($request)->getBasePath(),

Получение ответа (response body)

$body = (string)$request->getBody();

Если ответ пустой, это может быть ошибка или проблема с фрагментированными запросами (EN)

Получение входных данных

Чтобы получить отправленные данные JSON / XML, вам необходимо добавить BodyParsingMiddleware:

$app = AppFactory::create();

$app->addBodyParsingMiddleware(); // <--- here

// ...
$app->run();

Важно: BodyParsingMiddleware будет анализировать данные только в том случае, если заголовок запроса Content-Type содержит поддерживаемое значение. Поддерживаются следующие значения:

  • application/json
  • application/x-www-form-urlencoded
  • application/xml
  • text/xml

BodyParsingMiddleware также поддерживает PUT запросы.

Больше информации: slim-4-application

CSRF защита

Можно воспользоваться пакетом Slim-CSRF.

SameSite cookies

С SameSite Cookies (доступны с PHP 7.3) вам может больше не понадобиться защита CSRF:

  • CSRF умер
  • SameSite Cookie Middlware

Внедрение зависимости

Основное правило:

Все ваше приложение не должно знать о контейнере.

Создание контейнера — это антипаттерн. Вместо этого явно объявите все зависимости классов в своем конструкторе.

Почему создание контейнера (в большинстве случаях) это антипаттерн?

В Slim 3 Service Locator (антипаттерн) был «стилем» по умолчанию для внедрения всего контейнера и извлечения из него зависимостей.

  • Service Locator прятал реальные зависимости от вашего класса.
  • Service Locator нарушает принцип инверсии управления (IoC) в SOLID.

Вопрос: Как лучше сделать?

Ответ: Использовать композицию вместо наследования и конструктор внедрения зависимостей. Внедрение зависимостей — процесс предоставления внешней зависимости программному компоненту.

Начиная с Slim 4, вы можете использовать современный контейнер внедрения зависимостей (Dependency Injection Container — DIC), такой как PHP-DI и league / container. Это значит, что теперь вы можете явно объявить все зависимости в своем конструкторе и позволить DIC внедрить эти зависимости за вас.

Чтобы быть более ясным: «Композиция» не имеет ничего общего с функцией «Autowire» DIC. Вы можете использовать композицию с чистыми классами и без контейнера или чего-либо еще. Функция autowire просто использует классы PHP Reflection для автоматического разрешения и вставки зависимостей.

В настоящее время я использую Slim Framework как самый простой инструмент для разработки php web api.
Используя эти две статьи:

  • Coenraets
  • CodingThis

Я следую некоторым шагам оттуда. Загрузка Slim Framework, установка правильного каталога и файлов. Настройка инструкций инициализации, таких как:

//1. Require Slim
require('Slim/Slim.php');

//2. Instantiate Slim
$app = new Slim();

//3. Define routes
$app->get('/books', function ($id) {
    //Show book with id = $id
});

И затем я соответствующим образом изменяю остальные.

Например, мой контрольный список, который уже был выполнен:

  • LoadModule rewrite_module modules/mod_rewrite.so → enabled
  • Slim.htaccess:

RewriteEngine On RewriteCond% {REQUEST_FILENAME}! -f RewriteRule ^ (. *) $bootstrap.php [QSA, L]

  • httpd.conf: (общая ссылка).

Но после того, как я запустил это утверждение,

$app->run();

И я запустил его в своем браузере. Затем я получил 404 Ошибка, тестируя его на моем Localhost. Какое решение для исправления этого?

FYI, вот мой самый простой PHP файл, который я сейчас использую. (общая ссылка)

17 март 2012, в 07:49

Поделиться

Источник

7 ответов

Проблема решена!

Мой apache на самом деле нормальный, и файл .htaccess, предоставленный ранее, также нормальный.

Ключ — это URL, который я использовал.
Раньше я использовал недопустимый URL-адрес, поэтому он возвратил ошибку страницы 404.
Я просто понял это, когда я пытался получить доступ к новому URL-адресу GET через браузер с этим:

http://localhost/dev/index.php/getUsers/user1

и теперь это работает!

Я просто понял это, как только нашел эти утверждения;

Если Slim не находит маршруты с URI, которые соответствуют запросу HTTP URI, Slim автоматически вернет ответ 404 Not Found.

Если Slim находит маршруты с URI, которые соответствуют URI запроса HTTP, но не метод HTTP-запроса, Slim автоматически вернет метод 405 Не разрешенный ответ с заголовком Allow: чей значение перечисляет HTTP методы, приемлемые для запрошенного ресурса.

gumuruh
19 март 2012, в 10:31

Поделиться

Я нашел этот пост при поиске в Google «slimframework 404». Сообщение привело меня к решению моей проблемы.

Проблема

Я установил сайт с платформой Slim с помощью Composer и создаю index.php со следующей примерной формой кода slimframework.com:

<?php
$app = new SlimSlim();
$app->get('/hello/:name', function ($name) {
    echo "Hello, $name";
});
$app->run();

Затем я пытаюсь получить доступ к странице с помощью http://localhost/hello/bob. Я возвращаю страницу 404 Страница не найдена.

Мне удалось получить доступ к странице с помощью http://localhost/index.php/hello/bob.

Решение

Я нашел решение, посмотрев /vendor/slim/.htaccess (включая w/Composer Slim install) и URL Rewriting в рамочной документации Slim.

Я добавил копию файла /vendor/slim/.htaccess в ту же папку, что и файл index.php. Содержимое файла:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L] 

Теперь я могу получить доступ к странице с помощью http://localhost/hello/bob.

Tod Birdsall
04 июнь 2015, в 02:42

Поделиться

Вы правы, чтобы включить index.php в свой файл, но это происходит потому, что вы не работаете должным образом с вашим mod-rewrite
Проверьте следующую ссылку:

https://github.com/codeguy/Slim

В части «Начало работы» вы можете увидеть, как настроить сервер (в случае, если вы используете apache/lighttpd/ngynx)

Darkaico
05 июнь 2012, в 19:53

Поделиться

Для меня проблема заключалась в том, что я забыл предоставить файл .htaccess в моем корневом каталоге документа.

.htaccess

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

BadCash
14 нояб. 2017, в 13:03

Поделиться

Если вы используете Slim на Ubuntu 16.04, и вы получаете ошибку 404. Попробуйте этот тест от Tod Birdsall выше:

Если это работает

http://localhost/index.php/hello/bob

и это не работает

http://localhost/hello/bob

и ваш .htaccess файл в том же каталоге, что и index.php, и настроен следующим образом:

    <IfModule mod_rewrite.c>
       RewriteEngine On
       RewriteCond %{REQUEST_FILENAME} !-d
       RewriteCond %{REQUEST_FILENAME} !-f
       RewriteRule ^ index.php [QSA,L]
    </IfModule>

И вы все еще получаете ошибку 404 на Apache.

Попробуйте включить Apache mod_rewrite следующим образом и перезапустите Apache:

$sudo a2enmod rewrite

$sudo service apache2 restart

Теперь Slim API должен работать корректно, так как предоставленный .htaccess файл будет работать только в том случае, если mod_rewrite включен и не установлен по умолчанию для чистой установки.

Вот как отключить mod_rewirte, если вам нужно.

$sudo a2dismod rewrite

$sudo service apache2 restart

Cosworth66
28 дек. 2017, в 09:08

Поделиться

Дополнительно модуль mod_rewrite.so должен быть загружен в httpd.conf или вы получите сообщение об ошибке 500.

LoadModule rewrite_module modules/mod_rewrite.so

user2454565
15 фев. 2017, в 02:21

Поделиться

Я думаю, что ваша проблема связана с «парсером ресурсов», потому что вы не определяете параметр $id при запросе, поэтому попробуйте следующее:

//1. Require Slim
require('Slim/Slim.php');

//2. Instantiate Slim
$app = new Slim();

//3. Define routes
$app->get('/books/:id/', function ($id) {
    echo json_encode( getBook($id) );
});

// some stuff
$app->run();

Пожалуйста, скажите нам, нормально ли это

Ger Soto
30 май 2013, в 18:31

Поделиться

Ещё вопросы

  • 1Как узнать пользовательский агент браузера в Android
  • 1Проблемы PrintDocument с 64-разрядной версией Windows Server 2008 R2 Standard
  • 0значение формы исчезает при нажатии
  • 0проверить пустые или не значения в столбцах в R
  • 0Как вызвать функцию JavaScript из HTML?
  • 1Как запустить файл MATLAB (файл .m) из Java?
  • 0Можно ли переслать объявление класса enum для использования в производном классе?
  • 0Spring Security + угловое приложение REST-аутентификация на основе токенов = 403 запрещено на POST
  • 1SessionFactory дает мне java.lang.NullPointerException
  • 0Magento — добавить товар в корзину с пользовательским текстом
  • 1JUnit Как обработать ожидаемый сбой? [Дубликат]
  • 0Как установить минимальную высоту синкфузионной сетки, используя Angular JS?
  • 0Прохождение программ на C ++, таких как Python и R
  • 1Как взять значение двух полей EditText и выполнить простую математику
  • 1Индексирование вложенного списка списком
  • 1Maven: Как я могу иметь модуль с одинаковым именем в двух разных модулях?
  • 0ob_get_contents () — получить данные из открытого тега тела, чтобы закрыть тег тела
  • 0Получить все значения из одного столбца в MySQL PDO
  • 0Подсветка ввода с высотой значения
  • 0jquery SuperSize нужно перезагрузить страницу после воспроизведения слайдов
  • 0Как сделать ссылку на вкладки javascript
  • 0Перезапись URL отключить открытие файлов
  • 1Почему есть PatternSyntax Excpetion для следующей программы?
  • 0Наличие разных действий для каждого переключателя в форме
  • 0Это всегда дает неопределенную ошибку ссылки
  • 1Отслеживание таблиц Google в Google Analytics
  • 0Sql Delete запускает несколько строк
  • 0Сокрытие формулировки корзины BigCommerce с помощью javascript или jquery?
  • 0Пользовательский поиск Google — изменение URL без обновления страницы
  • 0Настройте mysimpleads с помощью CakePHP
  • 0Почему я получаю сообщение об ошибке, используя std :: locale :: locale (const std :: string &)?
  • 1Разделение строки Юникода в общем списке
  • 1Проверка NetworkAvailability возвращает false, но телефон подключен
  • 0AngularJS связь между видом и контроллером
  • 0Сбой jQuery Rotater, когда я скрываю и показываю новый контент
  • 0Изменение скорости вращения с SURF и SIFT
  • 1Невозможно запустить очень простой COM-клиент
  • 1как получить доступ к карте пользовательских объектов в drools
  • 1Преобразование XML-документа в IEnumerable
  • 0В полях формы есть место
  • 0Как правильно расположить DIVS на странице, чтобы
  • 0PHP абсолютный файл
  • 1Настройки чтения с телефона на Android
  • 1получение columnCount = 1 в метаданных jdbc
  • 1Невозможно получить доступ к члену родительского класса в базовом классе в Python
  • 0Html5 полноэкранный браузер Toggle Button
  • 1Объедините два флага намерений
  • 0Как разобрать xml с помощью <! [CDATA [?
  • 0Разрешить пользователю добавлять поля ввода формы: почему append () работает только один раз?
  • 0получить идентификатор элемента click в данный момент и получить идентификатор детей

Сообщество Overcoder

В эти дни я использую Тонкий каркас как мой самый простой инструмент для разработки php web api. Используя эти две статьи:

  • Коэнраец
  • Кодирование

Я следую некоторым шагам оттуда. Скачиваем Slim Framework, помещаем правильный каталог и файлы. Регулировка заявлений инициации, таких как;

//1. Require Slim
require('Slim/Slim.php');

//2. Instantiate Slim
$app = new Slim();

//3. Define routes
$app->get('/books', function ($id) {
    //Show book with id = $id
});

А затем я соответствующим образом модифицирую остальное.

Например, мой контрольный список, который уже выполнен:

  • LoadModule rewrite_module modules / mod_rewrite.so -> включен
  • Тонкий .htaccess:

RewriteEngine на RewriteCond% {REQUEST_FILENAME}! -F RewriteRule ^ (. *) $ Bootstrap.php [QSA, L]

  • httpd.conf: (Общая ссылка).

Но после того, как я запустил этот оператор;

$app->run();

И я запускаю его в своем браузере …. тогда я получил 404 Ошибка пока тестировал его на моем Localhost. Как можно это исправить?

К вашему сведению, вот мой самый простой файл PHP, который я сейчас использую. (общая ссылка)

8 ответы

Проблема решена!

Мой apache на самом деле нормальный, и файл .htaccess, предоставленный ранее, тоже нормальный.

Подсказка — это URL, который я использовал. Раньше я использовал недопустимый URL-адрес, поэтому он возвращал ошибку страницы 404. Я только что понял это, когда попытался получить доступ к новому URL-адресу GET через браузер с этим;

http://localhost/dev/index.php/getUsers/user1

и теперь это работает!

Я просто понял это, когда нашел эти утверждения;

Если Slim не находит маршруты с URI, которые соответствуют URI HTTP-запроса, Slim автоматически вернет ответ 404 Not Found.

Если Slim находит маршруты с URI, которые соответствуют URI HTTP-запроса, но не соответствуют методу HTTP-запроса, Slim автоматически вернет ответ 405 Method Not Allowed с заголовком Allow :, значение которого перечисляет методы HTTP, приемлемые для запрошенного ресурса.

ответ дан 19 мар ’12, в 09:03

Я нашел этот пост при поиске в Google «slimframework 404». Сообщение привело меня к решению моей проблемы.

Проблема

Я создал сайт на платформе Slim, используя Композитор и создайте index.php со следующим примером формы кода slimframework.com:

<?php
$app = new SlimSlim();
$app->get('/hello/:name', function ($name) {
    echo "Hello, $name";
});
$app->run();

Затем я пытаюсь получить доступ к странице, используя http://localhost/hello/bob. Я возвращаю страницу 404 Page Not Found.

Мне удалось получить доступ к странице, используя http://localhost/index.php/hello/bob.

Решение

Я нашел решение, посмотрев на /vendor/slim/.htaccess (в комплекте с установкой Composer Slim) и Перезапись URL раздел документации по фреймворку Slim.

Я добавил копию /vendor/slim/.htaccess файл в ту же папку, что и мой файл index.php. Содержимое файла:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L] 

Теперь я могу получить доступ к странице, используя http://localhost/hello/bob.

Создан 17 июн.

Вы правы, собираясь включить index.php в свой файл, но это произошло из-за того, что вы не работаете должным образом с вашим мод-перезаписью. Пожалуйста, проверьте следующую ссылку:

https://github.com/codeguy/Slim

В части «Начало работы» вы могли увидеть, как настроить свой сервер (в случае, если вы использовали apache / lighttpd / ngynx)

Создан 05 июн.

Я думаю, ваша проблема связана с «анализатором ресурсов», потому что вы не определяете параметр $ id в запросе, поэтому попробуйте следующее:

//1. Require Slim
require('Slim/Slim.php');

//2. Instantiate Slim
$app = new Slim();

//3. Define routes
$app->get('/books/:id/', function ($id) {
    echo json_encode( getBook($id) );
});

// some stuff
$app->run();

Пожалуйста, скажите нам, нормально ли

ответ дан 30 мая ’13, 17:05

Для меня проблема заключалась в том, что я забыл предоставить .htaccess файл в корне моего документа.

.htaccess

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

Создан 14 ноя.

Дополнительно mod_rewrite.so модуль должен быть загружен в httpd.conf или вы получите ошибку 500.

LoadModule rewrite_module modules/mod_rewrite.so

Создан 15 фев.

Если вы используете Slim в Ubuntu 16.04 и получаете ошибку 404. Попробуйте этот тест от Тода Бердсолла выше:

Если это работает

http://localhost/index.php/hello/bob

и это не работает

http://localhost/hello/bob

и ваш файл .htaccess в том же каталоге, что и ваш index.php, и настроен следующим образом:

    <IfModule mod_rewrite.c>
       RewriteEngine On
       RewriteCond %{REQUEST_FILENAME} !-d
       RewriteCond %{REQUEST_FILENAME} !-f
       RewriteRule ^ index.php [QSA,L]
    </IfModule>

И вы по-прежнему получаете ошибку 404 на Apache.

Попробуйте включить Apache mod_rewrite следующим образом и перезапустите Apache:

$ sudo a2enmod переписать

$ sudo service apache2 перезапуск

Теперь Slim API должен работать правильно, поскольку предоставленный файл .htaccess будет работать, только если включен mod_rewrite, а не по умолчанию при чистой установке.

Вот как отключить mod_rewirte, если вам нужно.

$ sudo a2dismod переписать

$ sudo service apache2 перезапуск

ответ дан 28 дек ’17, 07:12

Для людей, которые все еще ищут ответы, потому что предыдущее не работает, это работает для меня:

RewriteBase /<base_of_your_project>/

ответ дан 27 мар ’18, в 23:03

Не тот ответ, который вы ищете? Просмотрите другие вопросы с метками

php
api
frameworks
slim

or задайте свой вопрос.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Slim error slimmer
  • Smpp error bind failed
  • Slim application error что делать
  • Smplayer код ошибки 1
  • Smoke atomizer short как исправить

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии