Creating Rest API without views using cakePHP 3.5 - cakephp

I'm trying to create Rest API without view and planning to use these api's in angular 2 application. does have any idea about it?

Cake makes this incredibly easy. A few things I have learned building without views.
Set the _serialize variable
$data = ['cheeses' => ['gouda', 'pepper jack', 'cheddar']];
$this->set('responseData', $data);
$this->set('_serialize', 'responseData');
Throw bad request exceptions and other network related exceptions
Cake will render nice json views for you.
Set your accept header when issuing and ajax request to be application/json
You can use cake prefixes for api versions
Look at Stateless Authentication for your api

In your AppController.php, with these parameters, all of your controllers will be render in json
public function beforeRender(Event $event)
{
$this->RequestHandler->renderAs($this, 'json');
$this->response->type('application/json');
$this->set('_serialize', true);
}

CakePHP will render json easily.
In your Controller,look like something.
protected $responseBody = [];
public function beforeRender(Event $event){
foreach($this->responseBody as $responseKey=>$response){
$this->set($responseKey, $response);
}
$this->set('_serialize', array_keys($this->responseBody));
}
public function initialize()
{
parent::initialize();
$this->RequestHandler->renderAs($this, 'json');
}
public function index(){
$this->request->allowMethod(['get']); // Method like post,get..
$this->responseBody["statusCode"] = 200;
$this->responseBody["statusDescription"] = ''; //You send any text in json.
$this->responseBody["data"] = []; // All data that you can send.
}
For further informations , You can see CakePHP Cookbook REST API to click here

Related

How to get this code work to store in Laravel. No errors and no storage

I can't store name and IP address to DB. I created a table 'info' with appropriate fields by running php artisan migrate.
A schema
Schema::create('info', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('ip');
$table->timestamp('created_at')->nullable();
});
A model for Info
class Info extends Model
{
protected $fillable = ['ip', 'name'];
}
Maybe the problem is in my HomeController where I get those variables?
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Info;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Request;
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request) {
Info::create(['info' => $request->input('info')]);
}
public function index()
{
if (Auth::check())
{
$name = Auth::user()->name;
$ip = Request::ip();
\App\Events\eventIp::dispatch($ip,$name);
return view('home');
}
}
}
My routes in web.php
Route::post('/home','HomeController#store');
Route::get('/home', 'HomeController#index')->name('home');
});
But it doesn't work. Gives no errors and no records in DB.
Something make me think that it have to do with my index function. I got info in function index and maybe function store doesn't have a clue what I mean.
A controller action is basically a method that usually gets executed when you open an url (as you connect them to routes).
In your example you have connected two routes to their respective actions:
Route::post('/home','HomeController#store');
Route::get('/home', 'HomeController#index')->name('home');
Now, when you log in succesfully, imagine that you end up in the page with url http://localhost:8000/home in your web browser.
The key difference is the method which you use to call your route (you can get an overview of the differences here), in your case you are using GET method.
The resulting action executed it the one associated to /home route with the GET method, that is the HomeController#index action (or method).
The store method, although is in the same HomeController class, doesn't get triggered unless you execute the /home route, but with the POST method.
You can confirm that if you put a debug message in each of the methods like this:
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request)
{
echo 'I will not be executed';
}
public function index()
{
echo 'I have been executed';
}
}
If you want to simply save a info record when you visit the /home route with the GET method, you can put the save in the index method itself and get rid of the store method:
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
// You can omit Auth::check() because you are using the auth middleware
// that exactly does this job.
Info::create([
'name' => Auth::user()->name,
'ip' => Request::ip(),
]);
return view('home');
}
}
Keep in mind that doing in this way you will get a new database record for each page view you make to that route (if you keep refreshing the page, you should see new records being added to database).
Update
When you use Eloquent Models, laravel will look for a table named after the pluralized model name (Info model will try to use infos table).
However you created a table named info. To solve that you can either rename the table and rerun the migration with php artisan migrate:refresh (it will delete all the existing data in the database you are using for your laravel app)
Or specify the table name to use for that laravel model:
class Info extends Model
{
protected $table = 'info';
protected $fillable = ['ip', 'name'];
}
How are you calling the functions? There is a couple of things wrong with your code, but you're saying there are no errors at all.
Firstly, your Info::create call does not need the ['info' => $request->input('info')] info. This is because your Info model has no database property called info, but normally you would get an obvious error with the approach, which is why I expect you are also calling the store method incorrectly.
Call the create method like so:
$infoModel = Info::create(['name' => $request->input('name'), 'ip' => $request->input['ip']]);
or, if you can guarantee your $request only contains the needed fields (properly validated), you can just do
$infoModel = Info::create($request->all());
Add a little more info to the question on how you are calling store and we can probably solve the rest of your problem.
Within your store function inside HomeController , use
Info::create([
'name' => Auth::user()->name,
'ip' => Request::ip(),
]);
and make sure Info model is imported.
Also make sure your route has the call to store function while POSTing data .

Error: Call to a member function newEntity() on boolean

I am new on cakephp. I have done all the required steps but still having trouble in saving data in database with cakephp
code of adduser function from Articlecontroller.php:
public function adduser()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
// Hardcoding the user_id is temporary, and will be removed later
// when we build authentication out.
$user->user_id = 1;
if ($this->Users->save($user)) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $user);
}
code of UserTable model:
<?php
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
}
Data Base Table onto my locahost:
I think you forgot to load User model in controller. It should be fixed adding this line in function adduser() before first line. It should look like this.
public function adduser()
{
$this->loadModel('Users');
$user = $this->Users->newEntity();
...
Cakephp documentation.
https://book.cakephp.org/3.0/en/controllers.html#loading-additional-models
Well, you need to have a CakePHP application that is using the plugin. You need to add $this->loadComponent('Auth'); to your AppControllers initialize() method and configure it properly.
I highly recommend you to do the complete blog tutorial of the official CakePHP documentation or you won't have much fun with any plugin or anything else in the framework. It covers setting up Auth as well.
As other have started, when not in the controller related to the table you're accessing, you need to load the model and its related entities.
The current way of retrieving a table is through the table registry, as shown below:
use Cake\ORM\TableRegistry;
// Now $articles is an instance of our ArticlesTable class. This is how CakePHP 4 prefers it.
$articles = TableRegistry::getTableLocator()->get('Articles');
// Prior to 3.6.0
$articles = TableRegistry::get('Articles');
So for your public method, it should look like this:
public function adduser()
{
$usersTable = TableRegistry::getTableLocator()->get('Users');
$user = $usersTable->newEntity();
if ($this->request->is('post')) {
...code for handling post...
}
}
The CakePHP 3.x (and beyond) documentation regarding ORM outlines this well. While $this->loadModel('Articles') work, a quick search through the docs shows it is not frequently referred to neither in ORM section nor in examples.

CakePHP 3.4.2 Testing POST's response always returning NULL

i'm currently testing an app that simply searches a record by the given id.
It works fine but the testing refuses to return the response in the code. Strangely it is ONLY shown in the CLI.
I'm using phpunit provided by cakephp:
"phpunit/phpunit": "^5.7|^6.0"
Here is the conflicting code:
$this->post('/comunas/findByBarrio',[
'barrio_id'=>1
]);
var_dump($this->_response->body());die(); //This is just a test which always returns NULL... while the CLI shows the actual response, which is a JSON.
Also the same problem occurrs while doing GET or POST to any other action.
But here is the targeted controller's code:
public function findByBarrio()
{
$this->autoRender = false;
if ($this->request->is('POST'))
{
$data = $this->request->getData();
if (!empty($data['barrio_id']))
{
$this->loadModel('Comuna');
$barrio_id = $data['barrio_id'];
$comuna = $this->Comuna->find('list',['conditions' => ['barrio_id'=>$barrio_id]])
->hydrate(false)
->toArray();
if ($comuna)
{
echo json_encode($comuna);
}
else
{
throw new NotFoundException('0');
//echo 0; //Comuna no encontrada para el barrio recibido
}
}
else
{
echo -1;
}
}
}
Thank you in advance!
UPDATE 1: I've only managed to get the output by using "ob_start()" and "ob_get_clean()" around the "$this->post" method. I wish there were a cleaner way though...
UPDATE 2: Now it's working! Just by using the PSR-7 compliant interface. Thank you!
Here is the corrected controller:
public function findByBarrio()
{
$this->autoRender = false;
$this->response = $this->response->withType('json'); //CORRECTION
if ($this->request->is('POST'))
{
$data = $this->request->getData();
if (!empty($data['barrio_id']))
{
$this->loadModel('Comuna');
$barrio_id = $data['barrio_id'];
$comuna = $this->Comuna->find('list',['conditions' => ['barrio_id'=>$barrio_id]])
->hydrate(false)
->toArray();
if ($comuna)
{
$json = json_encode($comuna);
$this->response->getBody()->write($json); //CORRECTION
}
else
{
//Comuna no encontrada para el barrio recibido
$this->response->getBody()->write(0); //CORRECTION
}
}
else
{
//No se recibió el barrio
$this->response->getBody()->write(-1); //CORRECTION
}
}
return $this->response; //CORRECTION
}
Controller actions are not supposed to echo data, even though it might work in some, maybe even most situations. The correct way of outputting data that doesn't stem from a rendered view template, is to configure and return the response object, or to use serialized views.
The test environment relies on doing this properly, as it doesn't buffer possible output, but will use the actual value returned from the controller action.
The following is basically a copy from https://stackoverflow.com/a/42379581/1392379
Quote from the docs:
Controller actions generally use Controller::set() to create a context that View uses to render the view layer. Because of the conventions that CakePHP uses, you don’t need to create and render the view manually. Instead, once a controller action has completed, CakePHP will handle rendering and delivering the View.
If for some reason you’d like to skip the default behavior, you can return a Cake\Network\Response object from the action with the fully created response.
* As of 3.4 that would be \Cake\Http\Response
Cookbook > Controllers > Controller Actions
Configure the response
Using the PSR-7 compliant interface
$content = json_encode($comuna);
$this->response->getBody()->write($content);
$this->response = $this->response->withType('json');
// ...
return $this->response;
The PSR-7 compliant interface uses immutable methods, hence the utilization of the return value of withType(). Unlike setting headers and stuff, altering the body by writing to an existing stream doesn't change the state of the response object.
CakePHP 3.4.3 will add an immutable withStringBody method that can be used alternatively to writing to an existing stream.
$this->response = $this->response->withStringBody($content);
Using the deprecated interface
$content = json_encode($comuna);
$this->response->body($content);
$this->response->type('json');
// ...
return $this->response;
Use a serialized view
$content = json_encode($comuna);
$this->set('content', $content);
$this->set('_serialize', 'content');
This requires to also use the request handler component, and to enable extensing parsing and using correponsing URLs with .json appended, or to send a proper request with a application/json accept header.
See also
Cookbook > Controllers > Controller Actions
Cookbook > Views > JSON and XML views
PHP FIG Standards > PSR-7 HTTP message interfaces

cakephp mapResource to action

I have setup cakephp to accept json but when I add the
Router::mapResources('members');
it breaks my search plugin. Can I just target a particular action in the controller members instead of all the actions.
cakephp 2.1
Controller: members
action: api
Seems like that's not possible, but you can use it like this (in MembersController.php):
public function beforeFilter() {
$action = reset(explode(".", $this->request->params['action']));
if($action == 'api') {
$this->viewClass = 'Json'; // or whatever you want
$this->setAction($action);
}
}

Cakephp - Pagination with JSON API

I am developing (with a partner) a CakePHP application which will use Backbone.js.
So, basically, my cake application largely behaves like a JSON API. After the controller action loads a certain view (no data is rendered yet), Backbone makes an AJAX call to another controller action to fetch the data. The controller return JSON.
(The routes.php file has the mapResources and parseExtensions line in place)
Controller code
public function index(){
$company_id = $this->Session->read('Company.id');
$channel_ids = $this->Order->Channel->find('all', array('conditions' => array('Channel.company_id' => $company_id), 'fields' => array('Channel.id')));
$channel_ids = Set::extract('/Channel/id', $channel_ids);
$data = $this->paginate('Order', array('Order.fulfillment_status_id' => 1, 'Order.channel_id' => $channel_ids));
$this->set('orders', $data);
//In case of a JSON request
if($this->RequestHandler->ext == 'json') {
$this->autoRender = false;
echo json_encode($data);
}
}
This worked fine, but we hit a wall when we tried to implement pagination with this.
How do we generate the Pagination links? (If we do it via Backbone, it does not know the total records in the database and hence does not know how many links to generate)
How should the "page number" be burnt into the AJAX call? How should it be deciphered in the controller?
Really stuck here, we will really appreciate your help.
In Your Controller
$this->layout = false;
$this->RequestHandler->respondAs('json');
$recipes = $this->paginate('Recipe');
$this->set(compact('recipes'));
In your View
echo json_encode($recipes);
echo json_encode($this->Paginator->params());

Resources