I am trying to do something that seems to go out of the box with how laravel-nova works ...
I have a Batch model/ressource that is used by super admins. Those batch reeports belongs to sevral merchants. We decided to add a layer of connection to are portal and allow merchants to log in and see there data. So obviously, when the merchant visites the batch repport page, he needs to see only data related to it's own account.
So what we did was add the merchant id inside the batch page like this:
nova/resources/batch?mid=0123456789
The problem we then found out is that the get param is not send to the page it self but in a subpage called filter ... so we hacked it and found a way to retreive it like this:
preg_match('/mid\=([0-9]{10})/', $_SERVER['HTTP_REFERER'], $matches);
Now that we have the mid, all we need to do is add a where() to the model but it's not working.
Obviously, this appoach is not the right way ... so my question is not how to make this code work ... but how to approche this to make it so that merchants can only see his own stuff when visiting a controller.
All i really need to is add some sort of a where('external_mid', '=' $mid) and everything is good.
The full code looks like this right now:
<?php
namespace App\Nova;
use App\Nova\Resource;
use Laravel\Nova\Fields\ID;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\Currency;
use Laravel\Nova\Fields\BelongsTo;
use App\Nova\Filters\StatementDate;
use Laravel\Nova\Http\Requests\NovaRequest;
class Batch extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
//
public static function query(){
preg_match('/mid\=([0-9]{10})/', $_SERVER['HTTP_REFERER'], $matches);
if (isset($matches['1'])&&$matches['1']!=''){
$model = \App\Batch::where('external_mid', '=', $matches['1']);
}else{
$model = \App\Batch::class;
}
return $model;
}
public static $model = $this->query();
/**
* The single value that should be used to represent the resource when being displayed.
*
* #var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* #var array
*/
public static $search = [
'id','customer_name', 'external_mid', 'merchant_id', 'batch_reference', 'customer_batch_reference',
'batch_amt', 'settlement_date', 'fund_amt', 'payment_reference', 'payment_date'
];
/**
* Indicates if the resource should be globally searchable.
*
* #var bool
*/
public static $globallySearchable = false;
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->hideFromIndex(),
Text::make('Customer','customer_name'),
Text::make('MID','external_mid'),
Text::make('Batch Ref #','batch_reference'),
Text::make('Batch ID','customer_batch_reference'),
Text::make('Batch Date','settlement_date')->sortable(),
Currency::make('Batch Amount','batch_amt'),
Text::make('Funding Reference','payment_reference')->hideFromIndex(),
Text::make('Funding Date','payment_date')->hideFromIndex(),
Currency::make('Funding Amount','fund_amt')->hideFromIndex(),
// **Relationships**
HasMany::make('Transactions'),
BelongsTo::make('Merchant')->hideFromIndex(),
// ***
];
}
/**
* Get the cards available for the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function cards(Request $request)
{
return [];
}
/**
* Get the filters available for the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function filters(Request $request)
{
return [
];
}
/**
* Get the lenses available for the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function lenses(Request $request)
{
return [];
}
/**
* Get the actions available for the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function actions(Request $request)
{
return [];
}
}
In Laravel Nova you can modify the result query of any Resource by adding the index Query method. This method allows you to use Eloquent to modify the results with any condition you define.
I understand you just need to maintain the $model property with the model with the default definition and modify the results in the indexQuery method:
...
public static $model = \App\Batch::class;
public static function indexQuery(NovaRequest $request, $query)
{
// Using the same logic of the example above. I recommend to use the $request variable to access data instead of the $_SERVER global variable.
preg_match('/mid\=([0-9]{10})/', $_SERVER['HTTP_REFERER'], $matches);
if (isset($matches['1'])&&$matches['1']!=''){
return $query->where('external_mid', '=', $matches['1']);
}else{
return $query;
}
}
...
About the use of the PHP Global Variable, I recommend you to use the laravel default request() to look into your URL. You can use something like this $request->mid to read the value from the mid value in the URL.
Related
I have PostController, here is its method store().
public function store(Request $request)
{
$this->handleUploadedImage(
$request->file('upload'),
$request->input('CKEditorFuncNum')
);
$post = Post::create([
'content' => request('content'),
'is_published' => request('is_published'),
'slug' => Carbon::now()->format('Y-m-d-His'),
'title' => $this->firstSentence(request('content')),
'template' => $this->randomTemplate(request('template')),
]);
$post->tag(explode(',', $request->tags));
return redirect()->route('posts');
}
Method handleUploadedImage() now stored in PostController itself. But I'm going to use it in other controllers. Where should I move it? Not in Request class, because it's not about validation. Not in Models/Post, because it's not only for Post model. And it's not so global function for Service Provider class.
Methods firstSentence() and randomTemplate() are stored in that controller too. They will only be used in it. Maybe I should move them in Models/Post? In that way, how exactly call them in method store() (more specifically, in method create())?
I read the theory, and I understand (hopefully) the Concept of Thin Controllers and Fat Models, but I need some practical concrete advice with this example. Could you please suggest, where to move and how to call these methods?
First, a note: I don't work with Laravel, so I'll show you a general solution, pertinent to all frameworks.
Indeed, the controllers should always be kept thin. But this should be appliable to the model layer as well. Both goals are achievable by moving application-specific logic into application services (so, not into the model layer, making the models fat!). They are the components of the so-called service layer. Read this as well.
In your case, it seems that you can elegantly push the handling logic for uploaded images into a service, like App\Service\Http\Upload\ImageHandler, for example, containing a handle method. The names of the class and method could be chosen better, though, dependent on the exact class responsibility.
The logic for creating and storing a post would go into another application service: App\Service\Post, for example. In principle, this service would perform the following tasks:
Create an entity - Domain\Model\Post\Post, for example - and set its properties (title, content, is_published, template, etc) based on the user input. This could be done in a method App\Service\Post::createPost, for example.
Store the entity in the database, as a database record. This could be done in a method App\Service\Post::storePost, for example.
Other tasks...
In regard of the first task, two methods of the App\Service\Post service could be useful:
generatePostTitle, encapsulating the logic of extracting the first sentence from the user-provided "content", in order to set the title of the post entity from it;
generatePostTemplate, containing the logic described by you in the comment in regard of randomTemplate().
In regard of the second task, personally, I would store the entity in the database by using a specific data mapper - to directly communicate with the database API - and a specific repository on top of it - as abstraction of a collection of post objects.
Service:
<?php
namespace App\Service;
use Carbon;
use Domain\Model\Post\Post;
use Domain\Model\Post\PostCollection;
/**
* Post service.
*/
class Post {
/**
* Post collection.
*
* #var PostCollection
*/
private $postCollection;
/**
* #param PostCollection $postCollection Post collection.
*/
public function __construct(PostCollection $postCollection) {
$this->postCollection = $postCollection;
}
/**
* Create a post.
*
* #param string $content Post content.
* #param string $template The template used for the post.
* #param bool $isPublished (optional) Indicate if the post is published.
* #return Post Post.
*/
public function createPost(
string $content,
string $template,
bool $isPublished
/* , ... */
) {
$title = $this->generatePostTitle($content);
$slug = $this->generatePostSlug();
$template = $this->generatePostTemplate($template);
$post = new Post();
$post
->setTitle($title)
->setContent($content)
->setIsPublished($isPublished ? 1 : 0)
->setSlug($slug)
->setTemplate($template)
;
return $post;
}
/**
* Store a post.
*
* #param Post $post Post.
* #return Post Post.
*/
public function storePost(Post $post) {
return $this->postCollection->storePost($post);
}
/**
* Generate the title of a post by extracting
* a certain part from the given post content.
*
* #return string Generated post title.
*/
private function generatePostTitle(string $content) {
return substr($content, 0, 300) . '...';
}
/**
* Generate the slug of a post.
*
* #return string Generated slug.
*/
private function generatePostSlug() {
return Carbon::now()->format('Y-m-d-His');
}
/**
* Generate the template assignable to
* a post based on the given template.
*
* #return string Generated post template.
*/
private function generatePostTemplate(string $template) {
return 'the-generated-template';
}
}
Repository interface:
<?php
namespace Domain\Model\Post;
use Domain\Model\Post\Post;
/**
* Post collection interface.
*/
interface PostCollection {
/**
* Store a post.
*
* #param Post $post Post.
* #return Post Post.
*/
public function storePost(Post $post);
/**
* Find a post by id.
*
* #param int $id Post id.
* #return Post|null Post.
*/
public function findPostById(int $id);
/**
* Find all posts.
*
* #return Post[] Post list.
*/
public function findAllPosts();
/**
* Check if the given post exists.
*
* #param Post $post Post.
* #return bool True if post exists, false otherwise.
*/
public function postExists(Post $post);
}
Repository implementation:
<?php
namespace Domain\Infrastructure\Repository\Post;
use Domain\Model\Post\Post;
use Domain\Infrastructure\Mapper\Post\PostMapper;
use Domain\Model\Post\PostCollection as PostCollectionInterface;
/**
* Post collection.
*/
class PostCollection implements PostCollectionInterface {
/**
* Posts list.
*
* #var Post[]
*/
private $posts;
/**
* Post mapper.
*
* #var PostMapper
*/
private $postMapper;
/**
* #param PostMapper $postMapper Post mapper.
*/
public function __construct(PostMapper $postMapper) {
$this->postMapper = $postMapper;
}
/**
* Store a post.
*
* #param Post $post Post.
* #return Post Post.
*/
public function storePost(Post $post) {
$savedPost = $this->postMapper->savePost($post);
$this->posts[$savedPost->getId()] = $savedPost;
return $savedPost;
}
/**
* Find a post by id.
*
* #param int $id Post id.
* #return Post|null Post.
*/
public function findPostById(int $id) {
//...
}
/**
* Find all posts.
*
* #return Post[] Post list.
*/
public function findAllPosts() {
//...
}
/**
* Check if the given post exists.
*
* #param Post $post Post.
* #return bool True if post exists, false otherwise.
*/
public function postExists(Post $post) {
//...
}
}
Data mapper interface:
<?php
namespace Domain\Infrastructure\Mapper\Post;
use Domain\Model\Post\Post;
/**
* Post mapper.
*/
interface PostMapper {
/**
* Save a post.
*
* #param Post $post Post.
* #return Post Post entity with id automatically assigned upon persisting.
*/
public function savePost(Post $post);
/**
* Fetch a post by id.
*
* #param int $id Post id.
* #return Post|null Post.
*/
public function fetchPostById(int $id);
/**
* Fetch all posts.
*
* #return Post[] Post list.
*/
public function fetchAllPosts();
/**
* Check if a post exists.
*
* #param Post $post Post.
* #return bool True if the post exists, false otherwise.
*/
public function postExists(Post $post);
}
Data mapper PDO implementation:
<?php
namespace Domain\Infrastructure\Mapper\Post;
use PDO;
use Domain\Model\Post\Post;
use Domain\Infrastructure\Mapper\Post\PostMapper;
/**
* PDO post mapper.
*/
class PdoPostMapper implements PostMapper {
/**
* Database connection.
*
* #var PDO
*/
private $connection;
/**
* #param PDO $connection Database connection.
*/
public function __construct(PDO $connection) {
$this->connection = $connection;
}
/**
* Save a post.
*
* #param Post $post Post.
* #return Post Post entity with id automatically assigned upon persisting.
*/
public function savePost(Post $post) {
/*
* If $post->getId() is set, then call $this->updatePost()
* to update the existing post record in the database.
* Otherwise call $this->insertPost() to insert a new
* post record in the database.
*/
// ...
}
/**
* Fetch a post by id.
*
* #param int $id Post id.
* #return Post|null Post.
*/
public function fetchPostById(int $id) {
//...
}
/**
* Fetch all posts.
*
* #return Post[] Post list.
*/
public function fetchAllPosts() {
//...
}
/**
* Check if a post exists.
*
* #param Post $post Post.
* #return bool True if the post exists, false otherwise.
*/
public function postExists(Post $post) {
//...
}
/**
* Update an existing post.
*
* #param Post $post Post.
* #return Post Post entity with the same id upon updating.
*/
private function updatePost(Post $post) {
// Persist using SQL and PDO statements...
}
/**
* Insert a post.
*
* #param Post $post Post.
* #return Post Post entity with id automatically assigned upon persisting.
*/
private function insertPost(Post $post) {
// Persist using SQL and PDO statements...
}
}
In the end, your controller would look something like bellow. By reading its code, its role becomes obvious: just to push the user input to the service layer. The use of a service layer provides the big advantage of reusability.
<?php
namespace App\Controller;
use App\Service\Post;
use App\Service\Http\Upload\ImageHandler;
class PostController {
private $imageHandler;
private $postService;
public function __construct(ImageHandler $imageHandler, Post $postService) {
$this->imageHandler = $imageHandler;
$this->postService = $postService;
}
public function storePost(Request $request) {
$this->imageHandler->handle(
$request->file('upload'),
$request->input('CKEditorFuncNum')
);
$post = $this->postService->createPost(
request('content'),
request('template'),
request('is_published')
/* , ... */
);
return redirect()->route('posts');
}
}
PS: Keep in mind, that the model layer MUST NOT know anything about where its data is coming from, nor about how the data passed to it was created. So, the model layer must not know anything about a browser, or a request, or a controller, or a view, or a response, or etc. It just receives primitive values, objects, or DTOs ("data transfer objects") as arguments - see repository and data mapper above, for example.
PS 2: Note that a lot of frameworks are talking about repositories, but, in fact, they are talking about data mappers. My suggestion is to follow Fowler's conventions in your mind and your code. So, create data mappers in order to directly access a persistence space (database, filesystem, etc). If your project becomes more complex, or if you just want to have collection-like abstractions, then you can add a new layer of abstraction on top of the mappers: the repositories.
Resources
Keynote: Architecture the Lost Years by Robert C. Martin
Sandro Mancuso : Crafted Design
Clean, high quality code...
And a good 4-parts series of Gervasio on sitepoint.com:
Building a Domain Model - An Introduction to Persistence Agnosticism
Building a Domain Model – Integrating Data Mappers
Handling Collections of Aggregate Roots – the Repository Pattern
An Introduction to Services
What i usually do
public function store(ValidationRequest $request)
{
$result = $this->dispatchNow($request->validated())
return redirect()->route('posts');
}
So, i create a job for handling those registration steps, and i can reuse that in another parts of my system.
Your firstSentence i would move to an helper called strings app\helpers\strings (and don't forget to update that in composer.json and you could use just firstSentence($var) in any part of your system
The randomTemplate would fit nicelly in a trait, but i don't know what this methods does.
In my Laravel Nova project, I have a Page and a PageTranslation (model and resource). When adding a hasMany to my Resource fields, upon visiting the detail of the Page, I get a 404 error. This is my code
This is my Page Resource
<?php
namespace App\Pages\Resources;
use Illuminate\Http\Request;
use Laravel\Nova\Resource;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\HasMany;
class Page extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
public static $model = 'App\Pages\Models\Page';
/**
* The single value that should be used to represent the resource when being displayed.
*
* #var string
*/
public static $title = 'working_title';
/**
* #var string
*/
public static $group = 'Pages';
/**
* The columns that should be searched.
*
* #var array
*/
public static $search = [
'id', 'working_title'
];
/**
* Eager load translations
*/
public static $with = ['translations'];
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title', 'working_title')
->sortable()
->rules('required', 'max:256'),
HasMany::make('Translations', 'translations', \App\Pages\Resources\PageTranslation::class)
];
}
}
This is my PageTranslation Resource
<?php
namespace Codedor\Pages\Resources;
use Illuminate\Http\Request;
use Laravel\Nova\Resource;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
class PageTranslation extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
public static $model = 'Codedor\Pages\Models\PageTranslation';
/**
* Hide resource from Nova's standard menu.
* #var bool
*/
public static $displayInNavigation = false;
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Locale')
->sortable()
->rules('required', 'max:256')
];
}
}
I'm a little bit late, but if anyone comes across this issue while using Nova::resources instead of the resources path inside resources method in NovaServiceProvider, make sure you add the related resource to the list.
If you wish to hide a resource from the sidebar navirgation, just use public static $displayInNavigation = false; inside the resource file
It's not related to relationships at all. Make sure you've included the resources in your NovaServiceProvider
Also, to restrict from viewing in the sidebar based on user role, you can do something like:
public static function availableForNavigation(Request $request)
{
return $request->user()->isAdmin();
}
Currently I am working on a project where we are trying to create a RESTful API. This API uses some default classes, for example the ResourceController, for basic behaviour that can be overwritten when needed.
Lets say we have an API resource route:
Route::apiResource('posts', 'ResourceController');
This route will make use of the ResourceController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Repositories\ResourceRepository;
class ResourceController extends Controller
{
/**
* The resource class.
*
* #var string
*/
private $resourceClass = '\\App\\Http\\Resources\\ResourceResource';
/**
* The resource model class.
*
* #var string
*/
private $resourceModelClass;
/**
* The repository.
*
* #var \App\Repositories\ResourceRepository
*/
private $repository;
/**
* ResourceController constructor.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
public function __construct(Request $request)
{
$this->resourceModelClass = $this->getResourceModelClass($request);
$this->repository = new ResourceRepository($this->resourceModelClass);
$exploded = explode('\\', $this->resourceModelClass);
$resourceModelClassName = array_last($exploded);
if (!empty($resourceModelClassName)) {
$resourceClass = '\\App\\Http\\Resources\\' . $resourceModelClassName . 'Resource';
if (class_exists($resourceClass)) {
$this->resourceClass = $resourceClass;
}
}
}
...
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->validate($request, $this->getResourceModelRules());
$resource = $this->repository->create($request->all());
$resource = new $this->resourceClass($resource);
return response()->json($resource);
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$resource = $this->repository->show($id);
$resource = new $this->resourceClass($resource);
return response()->json($resource);
}
...
/**
* Get the model class of the specified resource.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
private function getResourceModelClass(Request $request)
{
if (is_null($request->route())) return '';
$uri = $request->route()->uri;
$exploded = explode('/', $uri);
$class = str_singular($exploded[1]);
return '\\App\\Models\\' . ucfirst($class);
}
/**
* Get the model rules of the specified resource.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
private function getResourceModelRules()
{
$rules = [];
if (method_exists($this->resourceModelClass, 'rules')) {
$rules = $this->resourceModelClass::rules();
}
return $rules;
}
}
As you can maybe tell we are not making use of model route binding and we make use of a repository to do our logic.
As you can also see we make use of some dirty logic, getResourceModelClass(), to determine the model class needed to perform logic on/with. This method is not really flexible and puts limits on the directory structure of the application (very nasty).
A solution could be adding some information about the model class when registrating the route. This could look like:
Route::apiResource('posts', 'ResourceController', [
'modelClass' => Post::class
]);
However it looks like this is not possible.
Does anybody have any suggestions on how to make this work or how to make our logic more clean and flexible. Flexibility and easy of use are important factors.
The nicest way would be to refactor the ResourceController into an abstract class and have a separate controller that extends it - for each resource.
I'm pretty sure that there is no way of passing some context information in routes file.
But you could bind different instances of repositories to your controller. This is generally a good practice, but relying on URL to resolve it is very hacky.
You'd have to put all the dependencies in the constructor:
public function __construct(string $modelPath, ResourceRepository $repo // ...)
{
$this->resourceModelClass = $this->modelPath;
$this->repository = $repo;
// ...
}
And do this in a service provider:
use App\Repositories\ResourceRepository;
use App\Http\Controllers\ResourceController;
// ... model imports
// ...
public function boot()
{
if (request()->path() === 'posts') {
$this->app->bind(ResourceRepository::class, function ($app) {
return new ResourceRepository(new Post);
});
$this->app->when(ResourceController::class)
->needs('$modelPath')
->give(Post::class);
} else if (request()->path() === 'somethingelse') {
// ...
}
}
This will give you more flexibility, but again, relying on pure URL paths is hacky.
I just showed an example for binding the model path and binding a Repo instance, but if you go down this road, you'll want to move all the instantiating out of the Controller constructor.
After a lot of searching and diving in the source code of Laravel I found out the getResourceAction method in the ResourceRegistrar handles the option passed to the route.
Further searching led me to this post where someone else already managed to extend this registrar en add some custom functionality.
My custom registrar looks like:
<?php
namespace App\Http\Routing;
use Illuminate\Routing\ResourceRegistrar as IlluResourceRegistrar;
class ResourceRegistrar extends IlluResourceRegistrar
{
/**
* Get the action array for a resource route.
*
* #param string $resource
* #param string $controller
* #param string $method
* #param array $options
* #return array
*/
protected function getResourceAction($resource, $controller, $method, $options)
{
$action = parent::getResourceAction($resource, $controller, $method, $options);
if (isset($options['model'])) {
$action['model'] = $options['model'];
}
return $action;
}
}
Do not forget to bind in the AppServiceProvider:
$registrar = new ResourceRegistrar($this->app['router']);
$this->app->bind('Illuminate\Routing\ResourceRegistrar', function () use ($registrar) {
return $registrar;
});
This custom registrar allows the following:
Route::apiResource('posts', 'ResourceController', [
'model' => Post::class
]);
And finally we are able to get our model class:
$resourceModelClass = $request->route()->getAction('model');
No hacky url parse logic anymore!
So I'm using the Laravel model event observerables to fire custom event logic, but they only accept the model as a single argument. What I'd like to do is call a custom event that I can also pass some extra arguments to that would in turn get passed to the Observer method. Something like this:
$this->fireModelEvent('applied', $user, $type);
And then in the Observer
/**
* Listen to the applied event.
*
* #param Item $item
* #param User $user
* #param string $type
* #return void
*/
public function applied(Item $item, $user, string $type) {
Event::fire(new Applied($video, $user, $type));
}
As you can see i'm interested in passing a user that performed this action, which is not the one that necessarily created the item. I don't think temporary model attributes are the answer because my additional event logic gets queued off as jobs to keep response time as low as possible. Anyone have any ideas on how I could extend Laravel to let me do this?
My theory would be to do a custom trait that overrides one or more functions in the base laravel model class that handles this logic. Thought I'd see if anyone else has needed to do this while I look into it.
Also here's the docs reference
I've accomplished this task by implementing some custom model functionality using a trait.
/**
* Stores event key data
*
* #var array
*/
public $eventData = [];
/**
* Fire the given event for the model.
*
* #param string $event
* #param bool $halt
* #param array $data
* #return mixed
*/
protected function fireModelEvent($event, $halt = true, array $data = []) {
$this->eventData[$event] = $data;
return parent::fireModelEvent($event, $halt);
}
/**
* Get the event data by event
*
* #param string $event
* #return array|NULL
*/
public function getEventData(string $event) {
if (array_key_exists($event, $this->eventData)) {
return $this->eventData[$event];
}
return NULL;
}
I get the error when trying to make a post call to /api/subject/search
I assume it's a simple syntax error I'm missing
I have my api routes defined below
Route::group(array('prefix' => 'api'), function()
{
Route::post('resource/search', 'ResourceController');
Route::resource('resource', 'ResourceController');
Route::post('subject/search', 'SubjectController');
Route::resource('subject', 'SubjectController');
Route::resource('user', 'UserController');
Route::controller('/session', 'SessionController');
Route::post('/login', array('as' => 'session', 'uses' => 'SessionController#Store'));
});
And my controller is mostly empty
class SubjectController extends \BaseController
{
public function search()
{
$subjects = [];
if((int)Input::get('grade_id') < 13 && (int)Input::get('grade_id') > 8)
$subjects = Subject::where('name', 'like', '%HS%')->get();
else
$subjects = Subject::where('name', 'not like', '%HS%')->get();
return Response::json([
'success' => true,
'subjects' => $subjects->toArray()
]);
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
//
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
//
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id)
{
//
}
}
You need to specify the method.
try
Route::post('subject/search', 'SubjectController#search');
See the named route example:
Laravel Docs
In your case I think search is not resolved by the controller to load the search() method. You are also sending a POST for search functionality and I guess it's better to do a GET request since POST and PUT are for storing data.
Conventions
When creating API's it's a good thing to stick to naming conventions and patterns.
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
Solution
Your route could be simpler like this: api.yourdomain.com/api/subject?search=term1,term2. Doing this with a GET query makes it going to the index() method. There you can check the GET params and do your search stuff and return.
Check this for the cleanest and truely RESTful way to make an API in Laravel:
How do I create a RESTful API in Laravel to use in my BackboneJS app
I got same error when accessing object at index of an empty array in view blade php file.