I copied the example that the Lighthouse website links to (https://webonyx.github.io/graphql-php/type-system/scalar-types/) for a custom Email type. (see code below)
Unfortunately, if I now go to the graphql-playground, I get a 500 error stating:
"No matching subclass of GraphQL\Type\Definition\ScalarType of found for the scalar Email"
How can I solve this error?
<?php
// file: /graphql/EmailType.php
namespace MyApp;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
class EmailType extends ScalarType
{
// Note: name can be omitted. In this case it will be inferred from class name
// (suffix "Type" will be dropped)
public $name = 'Email';
/**
* Serializes an internal value to include in a response.
*
* #param string $value
* #return string
*/
public function serialize($value)
{
// Assuming internal representation of email is always correct:
return $value;
}
/**
* Parses an externally provided value (query variable) to use as an input
*
* #param mixed $value
* #return mixed
*/
public function parseValue($value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new Error("Cannot represent following value as email: " . Utils::printSafeJson($value));
}
return $value;
}
/**
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
*
* E.g.
* {
* user(email: "user#example.com")
* }
*
* #param \GraphQL\Language\AST\Node $valueNode
* #param array|null $variables
* #return string
* #throws Error
*/
public function parseLiteral($valueNode, array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
if (!$valueNode instanceof StringValueNode) {
throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, [$valueNode]);
}
if (!filter_var($valueNode->value, FILTER_VALIDATE_EMAIL)) {
throw new Error("Not a valid email", [$valueNode]);
}
return $valueNode->value;
}
}
(Edit: Look at the comment from Enzo Notario, below, for a real solution)
I fixed this problem in the following way:
Inside 'composer.json' I added the folder 'graphql' (in which the EmailType class file is) to the list of folders in 'autoload':
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeds",
"database/factories",
"graphql"
]
},
Then I used the following commands (I am quite sure the last line is not needed):
php artisan clear-compiled
composer dumpautoload
composer update
After this al was working as expected.
I am trying to upgrade my Laravel 5.5 to 5.6. I have followed the instructions from the laravel website, yet I got this error:
Your requirements could not be resolved to an installable set of packages.
Problem 1
- The requested package laravel/framework 5.6.* is satisfiable by laravel/framework[5.6.x-dev] but these conflict with your requirements or minimum-stability.
So, I changed my composer.json file and added 2 lines: **"minimum-stability": "dev", "prefer-stable": true,** based on the first answer on this laracast discussion.
Everything seemed to be working just fine until I got another error:
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 6 updates, 0 removals
- Updating sebastian/diff (2.0.1 => 3.0.0): Downloading (100%)
- Updating phpunit/phpunit-mock-objects (5.0.6 => 6.0.0): Downloading (100%) - Updating phpunit/php-timer (1.0.9 => 2.0.0): Downloading (100%)
- Updating phpunit/php-token-stream (2.0.2 => 3.0.0): Downloading (100%) - Updating phpunit/php-code-coverage (5.3.0 => 6.0.1): Downloading (100%) - Updating phpunit/phpunit (6.5.6 => 7.0.0): Downloading (100%)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> #php artisan package:discover
In trustedproxy.php line 66:
Undefined class constant 'HEADER_CLIENT_IP'
Script #php artisan package:discover handling the post-autoload-dump event returned with error code 1
The problem on line 66 is Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',.
I have searched other posts on stackoverflow but still no luck. The last thing I tried was composer selfupdate and composer global update which was mentioned on the post composer dump-autoload not recognized command
Also, I cannot remove "minimum-stability": "dev","prefer-stable": true because if I do, then I will get the following error:
- Installation request for laravel/framework 5.6.* -> satisfiable by laravel/framework[5.6.x-dev].
- Removal request for laravel/framework == 5.6.9999999.9999999-dev
I have attached my composer.json file and trustedproxy.php respectively. Any help will be highly appreciated.
{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"minimum-stability": "dev",
"prefer-stable": true,
"keywords": ["framework", "laravel"],
"license": "MIT",
"type": "project",
"require": {
"php": ">=7.1.3",
"fideloper/proxy": "~3.3",
"laravel/framework": "5.6.*",
"laravel/tinker": "~1.0"
},
"require-dev": {
"filp/whoops": "~2.0",
"fzaninotto/faker": "~1.4",
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~7.0",
"symfony/thanks": "^1.0"
},
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"dont-discover": [
]
}
},
"scripts": {
"post-root-package-install": [
"#php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"#php artisan key:generate"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"#php artisan package:discover"
]
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
}
}
trustedproxy.php (error in line 66--> Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',)
<?php
return [
/*
* Set trusted proxy IP addresses.
*
* Both IPv4 and IPv6 addresses are
* supported, along with CIDR notation.
*
* The "*" character is syntactic sugar
* within TrustedProxy to trust any proxy
* that connects directly to your server,
* a requirement when you cannot know the address
* of your proxy (e.g. if using Rackspace balancers).
*
* The "**" character is syntactic sugar within
* TrustedProxy to trust not just any proxy that
* connects directly to your server, but also
* proxies that connect to those proxies, and all
* the way back until you reach the original source
* IP. It will mean that $request->getClientIp()
* always gets the originating client IP, no matter
* how many proxies that client's request has
* subsequently passed through.
*/
'proxies' => [
'192.168.1.10',
],
/*
* Or, to trust all proxies that connect
* directly to your server, uncomment this:
*/
# 'proxies' => '*',
/*
* Or, to trust ALL proxies, including those that
* are in a chain of forwarding, uncomment this:
*/
# 'proxies' => '**',
/*
* Default Header Names
*
* Change these if the proxy does
* not send the default header names.
*
* Note that headers such as X-Forwarded-For
* are transformed to HTTP_X_FORWARDED_FOR format.
*
* The following are Symfony defaults, found in
* \Symfony\Component\HttpFoundation\Request::$trustedHeaders
*
* You may optionally set headers to 'null' here if you'd like
* for them to be considered untrusted instead. Ex:
*
* Illuminate\Http\Request::HEADER_CLIENT_HOST => null,
*
* WARNING: If you're using AWS Elastic Load Balancing or Heroku,
* the FORWARDED and X_FORWARDED_HOST headers should be set to null
* as they are currently unsupported there.
*/
'headers' => [
(defined('Illuminate\Http\Request::HEADER_FORWARDED') ? Illuminate\Http\Request::HEADER_FORWARDED : 'forwarded') => 'FORWARDED',
Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
]
];
Illuminate\Http\Request file:
<?php
namespace Illuminate\Http;
use Closure;
use ArrayAccess;
use RuntimeException;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Support\Arrayable;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
use Concerns\InteractsWithContentTypes,
Concerns\InteractsWithFlashData,
Concerns\InteractsWithInput,
Macroable;
/**
* The decoded JSON content for the request.
*
* #var \Symfony\Component\HttpFoundation\ParameterBag|null
*/
protected $json;
/**
* All of the converted files for the request.
*
* #var array
*/
protected $convertedFiles;
/**
* The user resolver callback.
*
* #var \Closure
*/
protected $userResolver;
/**
* The route resolver callback.
*
* #var \Closure
*/
protected $routeResolver;
/**
* Create a new Illuminate HTTP request from server variables.
*
* #return static
*/
public static function capture()
{
static::enableHttpMethodParameterOverride();
return static::createFromBase(SymfonyRequest::createFromGlobals());
}
/**
* Return the Request instance.
*
* #return $this
*/
public function instance()
{
return $this;
}
/**
* Get the request method.
*
* #return string
*/
public function method()
{
return $this->getMethod();
}
/**
* Get the root URL for the application.
*
* #return string
*/
public function root()
{
return rtrim($this->getSchemeAndHttpHost().$this->getBaseUrl(), '/');
}
/**
* Get the URL (no query string) for the request.
*
* #return string
*/
public function url()
{
return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
}
/**
* Get the full URL for the request.
*
* #return string
*/
public function fullUrl()
{
$query = $this->getQueryString();
$question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?';
return $query ? $this->url().$question.$query : $this->url();
}
/**
* Get the full URL for the request with the added query string parameters.
*
* #param array $query
* #return string
*/
public function fullUrlWithQuery(array $query)
{
$question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?';
return count($this->query()) > 0
? $this->url().$question.http_build_query(array_merge($this->query(), $query))
: $this->fullUrl().$question.http_build_query($query);
}
/**
* Get the current path info for the request.
*
* #return string
*/
public function path()
{
$pattern = trim($this->getPathInfo(), '/');
return $pattern == '' ? '/' : $pattern;
}
/**
* Get the current decoded path info for the request.
*
* #return string
*/
public function decodedPath()
{
return rawurldecode($this->path());
}
/**
* Get a segment from the URI (1 based index).
*
* #param int $index
* #param string|null $default
* #return string|null
*/
public function segment($index, $default = null)
{
return Arr::get($this->segments(), $index - 1, $default);
}
/**
* Get all of the segments for the request path.
*
* #return array
*/
public function segments()
{
$segments = explode('/', $this->decodedPath());
return array_values(array_filter($segments, function ($value) {
return $value !== '';
}));
}
/**
* Determine if the current request URI matches a pattern.
*
* #param dynamic $patterns
* #return bool
*/
public function is(...$patterns)
{
foreach ($patterns as $pattern) {
if (Str::is($pattern, $this->decodedPath())) {
return true;
}
}
return false;
}
/**
* Determine if the route name matches a given pattern.
*
* #param dynamic $patterns
* #return bool
*/
public function routeIs(...$patterns)
{
return $this->route() && $this->route()->named(...$patterns);
}
/**
* Determine if the current request URL and query string matches a pattern.
*
* #param dynamic $patterns
* #return bool
*/
public function fullUrlIs(...$patterns)
{
$url = $this->fullUrl();
foreach ($patterns as $pattern) {
if (Str::is($pattern, $url)) {
return true;
}
}
return false;
}
/**
* Determine if the request is the result of an AJAX call.
*
* #return bool
*/
public function ajax()
{
return $this->isXmlHttpRequest();
}
/**
* Determine if the request is the result of an PJAX call.
*
* #return bool
*/
public function pjax()
{
return $this->headers->get('X-PJAX') == true;
}
/**
* Determine if the request is over HTTPS.
*
* #return bool
*/
public function secure()
{
return $this->isSecure();
}
/**
* Get the client IP address.
*
* #return string
*/
public function ip()
{
return $this->getClientIp();
}
/**
* Get the client IP addresses.
*
* #return array
*/
public function ips()
{
return $this->getClientIps();
}
/**
* Get the client user agent.
*
* #return string
*/
public function userAgent()
{
return $this->headers->get('User-Agent');
}
/**
* Merge new input into the current request's input array.
*
* #param array $input
* #return \Illuminate\Http\Request
*/
public function merge(array $input)
{
$this->getInputSource()->add($input);
return $this;
}
/**
* Replace the input for the current request.
*
* #param array $input
* #return \Illuminate\Http\Request
*/
public function replace(array $input)
{
$this->getInputSource()->replace($input);
return $this;
}
/**
* Get the JSON payload for the request.
*
* #param string $key
* #param mixed $default
* #return \Symfony\Component\HttpFoundation\ParameterBag|mixed
*/
public function json($key = null, $default = null)
{
if (! isset($this->json)) {
$this->json = new ParameterBag((array) json_decode($this->getContent(), true));
}
if (is_null($key)) {
return $this->json;
}
return data_get($this->json->all(), $key, $default);
}
/**
* Get the input source for the request.
*
* #return \Symfony\Component\HttpFoundation\ParameterBag
*/
protected function getInputSource()
{
if ($this->isJson()) {
return $this->json();
}
return $this->getRealMethod() == 'GET' ? $this->query : $this->request;
}
/**
* Create an Illuminate request from a Symfony instance.
*
* #param \Symfony\Component\HttpFoundation\Request $request
* #return \Illuminate\Http\Request
*/
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}
$content = $request->content;
$request = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$request->content = $content;
$request->request = $request->getInputSource();
return $request;
}
/**
* {#inheritdoc}
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
{
return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server);
}
/**
* Filter the given array of files, removing any empty values.
*
* #param mixed $files
* #return mixed
*/
protected function filterFiles($files)
{
if (! $files) {
return;
}
foreach ($files as $key => $file) {
if (is_array($file)) {
$files[$key] = $this->filterFiles($files[$key]);
}
if (empty($files[$key])) {
unset($files[$key]);
}
}
return $files;
}
/**
* Get the session associated with the request.
*
* #return \Illuminate\Session\Store
*
* #throws \RuntimeException
*/
public function session()
{
if (! $this->hasSession()) {
throw new RuntimeException('Session store not set on request.');
}
return $this->session;
}
/**
* Get the session associated with the request.
*
* #return \Illuminate\Session\Store|null
*/
public function getSession()
{
return $this->session;
}
/**
* Set the session instance on the request.
*
* #param \Illuminate\Contracts\Session\Session $session
* #return void
*/
public function setLaravelSession($session)
{
$this->session = $session;
}
/**
* Get the user making the request.
*
* #param string|null $guard
* #return mixed
*/
public function user($guard = null)
{
return call_user_func($this->getUserResolver(), $guard);
}
/**
* Get the route handling the request.
*
* #param string|null $param
*
* #return \Illuminate\Routing\Route|object|string
*/
public function route($param = null)
{
$route = call_user_func($this->getRouteResolver());
if (is_null($route) || is_null($param)) {
return $route;
}
return $route->parameter($param);
}
/**
* Get a unique fingerprint for the request / route / IP address.
*
* #return string
*
* #throws \RuntimeException
*/
public function fingerprint()
{
if (! $route = $this->route()) {
throw new RuntimeException('Unable to generate fingerprint. Route unavailable.');
}
return sha1(implode('|', array_merge(
$route->methods(), [$route->getDomain(), $route->uri(), $this->ip()]
)));
}
/**
* Set the JSON payload for the request.
*
* #param \Symfony\Component\HttpFoundation\ParameterBag $json
* #return $this
*/
public function setJson($json)
{
$this->json = $json;
return $this;
}
/**
* Get the user resolver callback.
*
* #return \Closure
*/
public function getUserResolver()
{
return $this->userResolver ?: function () {
//
};
}
/**
* Set the user resolver callback.
*
* #param \Closure $callback
* #return $this
*/
public function setUserResolver(Closure $callback)
{
$this->userResolver = $callback;
return $this;
}
/**
* Get the route resolver callback.
*
* #return \Closure
*/
public function getRouteResolver()
{
return $this->routeResolver ?: function () {
//
};
}
/**
* Set the route resolver callback.
*
* #param \Closure $callback
* #return $this
*/
public function setRouteResolver(Closure $callback)
{
$this->routeResolver = $callback;
return $this;
}
/**
* Get all of the input and files for the request.
*
* #return array
*/
public function toArray()
{
return $this->all();
}
/**
* Determine if the given offset exists.
*
* #param string $offset
* #return bool
*/
public function offsetExists($offset)
{
return array_key_exists(
$offset, $this->all() + $this->route()->parameters()
);
}
/**
* Get the value at the given offset.
*
* #param string $offset
* #return mixed
*/
public function offsetGet($offset)
{
return $this->__get($offset);
}
/**
* Set the value at the given offset.
*
* #param string $offset
* #param mixed $value
* #return void
*/
public function offsetSet($offset, $value)
{
$this->getInputSource()->set($offset, $value);
}
/**
* Remove the value at the given offset.
*
* #param string $offset
* #return void
*/
public function offsetUnset($offset)
{
$this->getInputSource()->remove($offset);
}
/**
* Check if an input element is set on the request.
*
* #param string $key
* #return bool
*/
public function __isset($key)
{
return ! is_null($this->__get($key));
}
/**
* Get an input element from the request.
*
* #param string $key
* #return mixed
*/
public function __get($key)
{
if (array_key_exists($key, $this->all())) {
return data_get($this->all(), $key);
}
return $this->route($key);
}
}
Thanks
I did this and it works perfectly.
1. composer.json:
From:
"require": {
"php": ">=7.0.0",
"fideloper/proxy": "~3.3",
"laravel/framework": "5.5.*",
"laravel/tinker": "~1.0"
},
To:
"require": {
"php": ">=7.1.3",
"fideloper/proxy": "~4.0",
"laravel/framework": "5.6.*",
"laravel/tinker": "~1.0"
},
2. Replace app\Http\Middleware\TrustedProxies.php file with contents below:
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* #var array
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* #var string
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}
3. composer update
Laravel's Request object extends Symfony's Request object. Laravel 5.5 depends on Symfony 3, which has that constant. Laravel 5.6 depends on Symfony 4, which does not have that constant.
Based on your trusted proxies configuration, it looks like you're using the trusted proxies package "outside" of Laravel. Laravel brought the trusted proxies package inside the framework in 5.5, and created a dedicated \App\Http\Middleware\TrustProxies middleware for you to use.
I would suggest moving to use the middleware and configuring it as described in the Laravel documentation. This will help prevent this type of compatibility issue in the future.
To make the switch:
In app/Http/Kernel.php, if \Fideloper\Proxy\TrustProxies::class is in your $middleware array, remove it. If \App\Http\Middleware\TrustProxies::class is not in your $middleware array, add it.
Open your app/Http/Middleware/TrustProxies.php file and update it with your proxies.
Delete your config/trustedproxy.php file.
Remove Fideloper\Proxy\TrustedProxyServiceProvider::class from your providers array in config/app.php.
Update your composer.json file to use "fideloper/proxy": "~4.0". Run composer update fideloper/proxy to update the package.
I have updated from 5.5 to 5.6
composer.json
"minimum-stability":"dev",
"prefer-stable": true,
This will install latest Laravel packages, then there will be issue with TrustedProxies.
Install the latest proxy version for Laravel 5.6.
Please use tag 4.0+ for Laravel 5.6:
composer require fideloper/proxy:~4.0
More details
First install Laravel 5.6 i faced this error as well.
Just follow the few steps below will fix it:
Make sure your file composer.json requirement has:
"require": {
"php": "^7.1.3",
"fideloper/proxy": "^4.0",
},
Then try composer update to make sure your composer is up-to-date
Finally run: composer require fideloper/proxy:~4.0
Done!
To anyone who tried upgrading directly from laravel 5.5 to 5.7, and got this problem too, remove the trustedproxy.php file from app->config->trustedproxy.php.
Hopes that helps someone.
Just need to Change fideloper/proxy in composer.json file:-
Your composer.json file now:-
"fideloper/proxy": "~3.3",
Change it to ^4.0 somthing like this:-
"fideloper/proxy": "^4.0",
After that you need to run update composer that's it.
composer update
Your problem comes from your use of the library TrustedProxy.
It uses Symfony's HEADER_CLIENT_IP constant which was deprecated with Symfony 3.3 and completely removed in Symfony 4.0.
Since Laravel 5.6 has updated to use Symfony 4 components, this will no longer work.
The way to solve it is to do what patricus suggested and use Laravel's TrustProxies middleware.
I did the following things and got my project to run on Laravel 5.6-dev:
Followed what patricus
suggested
Changed fideloper/proxy to "~4.0", and added "minimum-stability":
"dev", "prefer-stable": true at the end of my composer.json file.
Replace app\Http\Middleware\TrustedProxies.php by:
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
protected $proxies;
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}
Replace config\trustedproxy.php by:
<?php
return [
'proxies' => null,
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL,
];
None of the suggestions here worked for me for some reason. I am using quickadmin panel and various dependencies which might have something to do with it.
What finally worked was removing laravel/dusk, then updating to "fideloper/proxy": "~4.0", on it's own. Then updating laravel/framework to 5.6, then reinstalling dusk.
I did not need:
"minimum-stability":"dev",
"prefer-stable": true,
Maybe that was fixed with recent updates.
Faced the same issue and got a number of guidelines to resolve this. Unfortunately none of those worked or me. Actually there is no additional step or tasks needed to be addressed to fix this issue.
Just follow the official upgrade guide from https://laravel.com/docs/5.6/upgrade and along with that remove the trustedproxy config file located at config/trustedproxy.php
Had the same issue in Laravel 5.7. You may add TELESCOPE_ENABLED=false in your .env or .env.dusk.local :Source
In Drupal there is a simple url rewrite system that stores path aliases and the real route in the database.
For example:
/category/hello => node/5
I would like to imitate this system in Laravel.
I know how to create the database structure. What I would like suggestions for is actually overriding and remapping the incoming request.
I've taken a glance at the router. No events are really sticking out. What I would like to avoid is adding every permutation as a static route. I would like to for this to be completely dynamic.
I was reading middleware with a redirect would work but don't know if that is the best route to go. Keep in mind that the aliases could be anything. There isn't any set pattern.
The actual business case for this is the application has a hierarchy of categories like for a catalog on an ecommerce site. For every path a dynamic page will need to exist and possibly also allow pass-thrus to other pages.
Ex.
/sports/football/nfl => \App\Http\Controllers\Category::lp(2)
Even something like:
/sports/football/nfl/:game/lines => \App\Http\Controllers\Lines::lp(:game)
However, I don't want to have every permutation in the db. Just the base one and allow everything after /sports/football/nfl/* pass thru to a completely different location.
If I do recall in Symfony this could be done with a custom route matcher. However, I don't see anything like that in Laravel. Unless I'm just missing something. It looks like you either add a static route or nothing all but I haven't taken the deep dive into that code yet so could be wrong.
I was able to implement a dynamic routing system by creating my own custom route and adding to the route collection manually.
Custom Route
use Illuminate\Routing\Route as BaseRoute;
use Modules\Catalog\Routing\Matching\CategoryValidator;
use Illuminate\Routing\Matching\MethodValidator;
use Illuminate\Routing\Matching\SchemeValidator;
use Illuminate\Routing\Matching\HostValidator;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Routing\ControllerDispatcher;
/**
* Special dynamic touting for catalog categories.
*/
class CategoryRoute extends BaseRoute {
protected $validatorOverrides;
/**
* #param CategoryRepository
*/
protected $categoryRepository;
/**
* Create a new Route instance.
*
* #param CategoryRepository $categoryRepository
* The category repository.
*/
public function __construct(CategoryRepository $categoryRepository)
{
$this->categoryRepository = $categoryRepository;
$action = [
'uses'=> function() use ($categoryRepository) {
$path = app('request')->path();
$category = $categoryRepository->findOneByHierarchicalPath($path);
$controller = app()->make('Modules\Catalog\Http\Controllers\Frontend\CategoryController');
return $controller->callAction('getIndex', ['categoryId'=>$category->getId()]);
}
];
$action['uses']->bindTo($this);
parent::__construct(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'],'_catalog_category',$action);
}
/**
* Determine if the route matches given request.
*
* #param \Illuminate\Http\Request $request
* #param bool $includingMethod
* #return bool
*/
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
$validators = $this->getValidatorOverrides();
foreach ($validators as $validator) {
/*if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}*/
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
/**
* Get the route validators for the instance.
*
* #return array
*/
public function getValidatorOverrides()
{
if (isset($this->validatorOverrides)) {
return $this->validatorOverrides;
}
$this->validatorOverrides = [
new MethodValidator, new SchemeValidator,
new HostValidator, /*new UriValidator,*/
new CategoryValidator($this->categoryRepository)
];
return $this->validatorOverrides;
}
}
Custom Route Validator
<?php
namespace Modules\Catalog\Routing\Matching;
use Illuminate\Routing\Matching\ValidatorInterface;
use Illuminate\Routing\Route;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
class CategoryValidator implements ValidatorInterface
{
protected $categoryRepository;
public function __construct(CategoryRepository $categoryRepository) {
$this->categoryRepository = $categoryRepository;
}
/**
* Validate a given rule against a route and request.
*
* #param \Illuminate\Routing\Route $route
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function matches(Route $route, Request $request)
{
$path = $request->path() == '/' ? '/' : '/'.$request->path();
$category = $this->categoryRepository->findOneByHierarchicalPath($path);
return $category?true:false;
}
}
To satisfy the requirements of the category repository dependency I had to also create a subscriber that adds the route after all the providers had been booted. Simply placing it in the routes.php file would not work because there was no guarantee that all the dependencies for IoC would be configured when that file gets loaded.
Bootstrap Subscriber
use Modules\Catalog\Routing\CategoryRoute;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Support\Facades\Route as RouteFacade;
class BootstrapSubscriber {
public function subscribe($events) {
$events->listen(
'bootstrapped: Illuminate\Foundation\Bootstrap\BootProviders',
'Modules\Catalog\Subscribers\BootstrapSubscriber#onBootstrappedBootProviders'
);
}
public function onBootstrappedBootProviders($event) {
$categoryRepository = app(CategoryRepository::class);
RouteFacade::getRoutes()->add(new CategoryRoute($categoryRepository));
}
}
I will probably expand on this but that is the basic way to do it.
Hi everyone I'm creating a Laravel package and I'm trying to implement the tests.
My composer.json has this structure:
"require-dev": {
"graham-campbell/testbench": "^3.1",
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^4.8|^5.0"
},
I'm using this package for the test creation. I've looked at other packages of Graham Campbell in order to understand a little better how he creates the tests and I'm trying to "adapt" his classes for my goals.
The problem is that I receive this error when running phpunit:
1) IlGala\Tests\LaravelWizzy\WizzyTest::testGetPrefix
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_0_Illuminate_Contracts_Config_Repository::get("wizzy.prefix", ""). Either the method was unexpected or its arguments matched no expected argument list for this method
/home/ilgala/NetBeansProjects/laravelWizzy/packages/ilgala/laravel-wizzy/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:93
/home/ilgala/NetBeansProjects/laravelWizzy/packages/ilgala/laravel-wizzy/src/Wizzy.php:68
/home/ilgala/NetBeansProjects/laravelWizzy/packages/ilgala/laravel-wizzy/tests/WizzyTest.php:71
I'm trying to test the Wizzy class which is registered as singleton in the WizzyServiceProvider:
$this->app->singleton('wizzy', function (Container $app) {
$config = $app['config'];
return new Wizzy($config);
});
This is my test class:
protected $defaults = [
[...]
];
/**
*
*/
public function testGetPrefix()
{
$wizzy = $this->getWizzy();
$this->assertSame('install', $wizzy->getPrefix());
}
protected function getWizzy()
{
$repository = Mockery::mock(Repository::class);
$wizzy = new Wizzy($repository);
$wizzy->getConfig()->shouldReceive('get')->once()
->with('wizzy.prefix')->andReturn($this->defaults['prefix']);
return $wizzy;
}
An finally this is the Wizzy class:
/**
* Config repository.
*
* #var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* Creates new instance.
*/
public function __construct(Repository $config)
{
$this->config = $config;
}
/**
* Get the config instance.
*
* #return \Illuminate\Contracts\Config\Repository
*/
public function getConfig()
{
return $this->config;
}
/**
* Get the configuration name.
*
* #return string
*/
protected function getConfigName()
{
return 'wizzy';
}
/**
* Get wizzy route group prefix from the config file.
*
* #return string wizzy.prefix
*/
public function getPrefix()
{
return $this->config->get($this->getConfigName() . '.prefix', '');
}
Can anyone help me understand what am I doing wrong?
I've found the solution...
$wizzy->getConfig()->shouldReceive('get')->once()
->with('wizzy.prefix')->andReturn($this->defaults['prefix']);
The problem was here, since (translating this method with natural language) the mocked object should receive a call to the get method with wizzy.prefix string, but actually it was receiving wizzy.prefix and '', so I've changed the code in this way:
$wizzy->getConfig()->shouldReceive('get')->once()
->with('wizzy.prefix', '')->andReturn($this->defaults['prefix']);
I'm using FormRequest to validate from which is sent in an API call from my smartphone app. So, I want FormRequest alway return json when validation fail.
I saw the following source code of Laravel framework, the default behaviour of FormRequest is return json if reqeust is Ajax or wantJson.
//Illuminate\Foundation\Http\FormRequest class
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->ajax() || $this->wantsJson()) {
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
I knew that I can add Accept= application/json in request header. FormRequest will return json. But I want to provide an easier way to request my API by support json in default without setting any header. So, I tried to find some options to force FormRequest response json in Illuminate\Foundation\Http\FormRequest class. But I didn't find any options which are supported in default.
Solution 1 : Overwrite Request Abstract Class
I tried to overwrite my application request abstract class like followings:
<?php
namespace Laravel5Cg\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\JsonResponse;
abstract class Request extends FormRequest
{
/**
* Force response json type when validation fails
* #var bool
*/
protected $forceJsonResponse = false;
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->forceJsonResponse || $this->ajax() || $this->wantsJson()) {
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
}
I added protected $forceJsonResponse = false; to setting if we need to force response json or not. And, in each FormRequest which is extends from Request abstract class. I set that option.
Eg: I made an StoreBlogPostRequest and set $forceJsoResponse=true for this FormRequest and make it response json.
<?php
namespace Laravel5Cg\Http\Requests;
use Laravel5Cg\Http\Requests\Request;
class StoreBlogPostRequest extends Request
{
/**
* Force response json type when validation fails
* #var bool
*/
protected $forceJsonResponse = true;
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
}
Solution 2: Add an Middleware and force change request header
I build a middleware like followings:
namespace Laravel5Cg\Http\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\HeaderBag;
class AddJsonAcceptHeader
{
/**
* Add Json HTTP_ACCEPT header for an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->server->set('HTTP_ACCEPT', 'application/json');
$request->headers = new HeaderBag($request->server->getHeaders());
return $next($request);
}
}
It 's work. But I wonder is this solutions good? And are there any Laravel Way to help me in this situation ?
It boggles my mind why this is so hard to do in Laravel. In the end, based on your idea to override the Request class, I came up with this.
app/Http/Requests/ApiRequest.php
<?php
namespace App\Http\Requests;
class ApiRequest extends Request
{
public function wantsJson()
{
return true;
}
}
Then, in every controller just pass \App\Http\Requests\ApiRequest
public function index(ApiRequest $request)
I know this post is kind of old but I just made a Middleware that replaces the "Accept" header of the request with "application/json". This makes the wantsJson() function return true when used. (This was tested in Laravel 5.2 but I think it works the same in 5.1)
Here's how you implement that :
Create the file app/Http/Middleware/Jsonify.php
namespace App\Http\Middleware;
use Closure;
class Jsonify
{
/**
* Change the Request headers to accept "application/json" first
* in order to make the wantsJson() function return true
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
*
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
Add the middleware to your $routeMiddleware table of your app/Http/Kernel.php file
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'jsonify' => \App\Http\Middleware\Jsonify::class
];
Finally use it in your routes.php as you would with any middleware. In my case it looks like this :
Route::group(['prefix' => 'api/v1', 'middleware' => ['jsonify']], function() {
// Routes
});
Based on ZeroOne's response, if you're using Form Request validation you can override the failedValidation method to always return json in case of validation failure.
The good thing about this solution, is that you're not overriding all the responses to return json, but just the validation failures. So for all the other Php exceptions you'll still see the friendly Laravel error page.
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Symfony\Component\HttpFoundation\Response;
class InventoryRequest extends FormRequest
{
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response($validator->errors(), Response::HTTP_UNPROCESSABLE_ENTITY));
}
}
if your request has either X-Request-With: XMLHttpRequest header or accept content type as application/json FormRequest will automatically return a json response containing the errors with a status of 422.
i just override the failedValidation function
protected function failedValidation(Validator $validator)
{
if ($this->wantsJson()) {
throw new HttpResponseException(
Response::error(__('api.validation_error'),
$validator->errors(),
470,
[],
new ValidationException)
);
}
parent::failedValidation($validator);
}
So my custom output sample like below:
{
"error": true,
"message": "Validation Error",
"reference": [
"The device id field is required.",
"The os version field is required.",
"The apps version field is required."
],
}
BTW Response::error dont exist in laravel. Im using macroable to create new method
Response::macro('error', function ($msg = 'Something went wrong', $reference = null, $code = 400, array $headers = [], $exception = null) {
return response()->json(//custom here);
});
I came to this solution (Laravel 9):
throw new ValidationException(
$validator,
new JsonResponse([
'errors' => $validator->errors()->messages(),
], 422),
);