Saving application/json with SLIM 3 and Eloquent - php

So, when getting an Object from the database i do something like this in the Object Controller:
public function show(Request $request, Response $response, array $args)
{
$id = (int)$args['oid'];
$object = $this->object->getObjectById($id);
$data = $this->fractal->createData(new Item($object, new ObjectTransformer()))->toArray();
return $response->withJson($data);
}
I'm using fractal to transform the database structure to the needed JSON format and Eloquent for the Database access.
Then i'm getting something like this:
{
"propertyId": 12345,
"created": "2017-12-29T19:25:23+01:00",
"modified": "2018-06-07T17:28:04+02:00",
"published": true,
"market": [
"buy"
],
"text": {
"title": "Object Title"
}
}
I'm sending the exact same JSON string (without the ID) via Postman as application/json.
When trying to save a new object i'm doing this in the Controller:
public function store(Request $request, Response $response, array $args)
{
$object = new Object($request->getParsedBody());
//dd($request->getParsedBody());
$object->save(); // nothing happens, just an empty entry
}
The Object Class
class Object extends \Comp\Models\Mapper
{
protected $database;
protected $hidden = array('created_by', 'checked_out', 'checked_out_time', 'modified_by', 'access', 'params', 'oid', 'video');
protected $casts = [
'published' => 'boolean',
'featured' => 'boolean',
'rating' => 'float',
];
protected $dates = ['created','modified'];
public $timestamps = false;
/*protected $fillable = [
'shop_id','vendor_id','name','address','pincode','phone','shop_type'
];*/
public function __construct( $database)
{
$this->setTable('objects');
$this->database = $database;
}
}
The "Mapper" Class
<?php
namespace Comp\Models;
use Interop\Container\ContainerInterface;
use Illuminate\Database\Eloquent\Model;
abstract class Mapper extends Model {
protected $db;
public function __construct(ContainerInterface $container) {
}
public function removeEmptyElements(array $array)
{
}
}
I don't know if and how to do a transformation again, to map the structure for the Eloquent ORM.
Update 1:
I just spotted this:
https://github.com/tuupola/slim-todo-backend/blob/master/app.php
So do i need something like Spot and Fractal together? I wanted to have a central Transformer and not multiple places for output and input. And also i use Eloquent and cant use another ORM...
Update 2:
Added Object Model and Mapper Class
Any ideas are very appreciated.

We can insert it very easily.I did this .I use slim 3.0 frame work and laravel Eloquent
Here i explain detailed structure of my project
First you need to install Eloquent data base driver and create models
<?php
require 'vendor/autoload.php';
include 'dbboot.php';
use User\Models\User;
$app = new \Slim\App([
'settings' => [
'displayErrorDetails' => true,
'debug' => true,
'whoops.editor' => 'sublime',
]
]);
$app->get('/user', function ($request, $response, $args) {
$_user = new User();
$users = $_user->all();
$payload = [];
$payload['user']=$users;
return $response->withStatus(200)->withJson($payload);
});
$app->post('/user', function($request,$response,$args) {
$_user = new User();
$user = $request->getParsedBodyParam('userinfo', '');
$user_id=$_user :: insertGetId($user);
$payload = [];
$payload['user_id']=$user_id;
$payload['message']="Insertion success";
return $response->withStatus(200)->withJson($payload);
});
// Run app
$app->run();
For source code please clone my Github repository:
https://github.com/sherinmac/slim3-with-eloquent.git

Related

CREATE operation with Eloquent ORM shows 500 internal server error

I am trying to make a POST request to add a showroom in the Laravel application. When I try to do it with Showroom model using Eloquent ORM , it shows 500 internal server error. But if I do it with DB query, then it successfully CREATE the showroom. I commented out the db query lines and apply dd debugging and found out table for Showroom Model is null.
This is my controller code -
public function store(ShowroomRequest $request)
{
$showroom = new Showroom([
"name" => $request->get('name'),
"address" => $request->get('address'),
"description" => $request->get('description'),
]);
dd($showroom);
$ret = $showroom->save();
// $name = $request->input('name');
// $address = $request->input('address');
// $description = $request->input('description');
// DB::table('showroom')->insert(
// ['name' => $name, 'address' => $address, 'description' => $description]
// );
return redirect()->route('back.showroom.index')->withSuccess(__('Showroom Added Successfully.'));
}
And this is my model -
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Showroom extends Model
{
protected $fillable = ['name', 'description', 'address'];
protected static $ordersModel = 'App\Models\Order';
protected static $reviewsModel = 'App\Models\Review';
public function Orders()
{
return $this->hasMany(static::$ordersModel, 'showroom_id');
}
public function Reviews()
{
return $this->hasMany(static::$reviewsModel, 'showroom_id');
}
}
Finally this is my db structure -
Can anyone help me to find out what went wrong here? Thanks in advance.
in controller can you assign static values instead of request->get, and see if it saves.
please let me what happens afterwards.
also assign name of table in model like this,
protected $table = 'tablename';

Create Model with HasMany relation using Repository Pattern

I'm trying to implement the repository pattern and save a relationship using the create method as shown below.
abstract class EloquentRepository implements Repository {
public function create($data)
{
return $this->model->create($data);
}
}
Within my controller I have injected the repository:
public function __construct(SubscriberRepository $subscriberRepository,
SubscribableRepository $subscribableRepository)
{
$this->subscriberRepository = $subscriberRepository;
$this->subscribableRepository = $subscribableRepository;
}
My store method looks like:
public function store(CreateSubscriberRequest $request): JsonResponse
{
$subscribable = $this->subscribableRepository->findByIdentifier($request->input('type'))
->firstOrFail();
$attributes = [
'name' => $request->input('name'),
'email' => $request->input('email')
];
$subscriber = $this->subscriberRepository->create($attributes);
}
Subscriber Model
public function subscribable()
{
return $this->belongsTo(Subscribable::class, 'subscribable_id');
}
Subscribable Model
public function subscribers()
{
return $this->hasMany(Subscriber::class);
}
My issue General error: 1364 Field 'subscribable_id' doesn't have a default value is because the subscribable_id is a foreign key and not set in the create method.
How do I relate the subscribable model, setting the subscribable_id? I don't think setting the subscribable_id in the fillable property is the way to go with this.
Many thanks in advance.
Laravel gives functionality to save relations using the related model instances.
So You can save relation by calling create method on relation like this:
public function store(CreateSubscriberRequest $request): JsonResponse
{
$subscribable = $this->subscribableRepository->findByIdentifier($request->input('type'))
->firstOrFail();
$attributes = [
'name' => $request->input('name'),
'email' => $request->input('email')
];
$subscribable->subscribers()->create($attributes);
}
See laravel doc on relationship

Adding filter to eloquent resource to attach relationship conditionally

Laravel 5.8
PHP 7.4
I want to load the relationships conditionally like
http://127.0.0.1:8000/api/posts
and
http://127.0.0.1:8000/api/posts/1 are my end points now, I want to load comments like
http://127.0.0.1:8000/api/posts/?include=comments and
http://127.0.0.1:8000/api/posts/1/?include=comments
If the query parameter is there, only then it should load comments with posts or it should load only posts/post
I am doing this by referring a blog post
now, RequestQueryFilter
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
class RequestQueryFilter
{
public function attach($resource, Request $request = null)
{
$request = $request ?? request();
return tap($resource, function($resource) use($request) {
$this->getRequestIncludes($request)->each(function($include) use($resource) {
$resource->load($include);
});
});
}
protected function getRequestIncludes(Request $request)
{
// return collect(data_get($request->input(), 'include', [])); //single relationship
return collect(array_map('trim', explode(',', data_get($request->input(), 'include', [])))); //multiple relationships
}
}
and in helper
<?php
if ( ! function_exists('filter') ) {
function filter($attach)
{
return app('filter')->attach($attach);
}
}
?>
in PostController
public funciton show(Request $request, Post $post) {
return new PostResource(filter($post));
}
but when I am trying to retrieve
http://127.0.0.1:8000/api/posts/1/?include=comments getting no comments, with no error in log
A work around will be PostResource
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'name' => $this->title,
'body' => $this->content,
];
$filter = $request->query->get('include', '');
if($filter){
$data[$filter] = $this->resource->$filter;
}
return $data;
}
I want to load the relationships conditionally like
Lazy Eager Loading using the load() call
The Lazy Eager Loading accomplishes the same end results as with() in Laravel, however, not automatically. For example:
?include=comments
// Get all posts.
$posts = Post::without('comments')->all();
if (request('include') == 'comments')) {
$posts->load('comments');
}
return PostResource::collection($posts);
Alternativelly, you could require the include query string to be an array:
?include[]=comments&include[]=tags
// Validate the names against a set of allowed names beforehand, so there's no error.
$posts = Post::without(request('includes'))->all();
foreach (request('includes') as $include) {
$posts->load($include);
}
return PostResource::collection($posts);
The call without() is only required in case you defined your model to automatically eager load the relationships you want to conditionally load.
With all data filtered in Controller, just make sure to display only loaded relations in your PostResource
public function toArray($request) {
$data = [...];
foreach ($this->relations as $name => $relation)
{
$data[$name] = $relation;
}
return $data;
}
I would create a custom resource for the posts with
php artisan make_resource
command.
E.g. PostResource.
The toArray function of the resource must return the data.
PostResource.php
public function toArray($request){
$data =['title' => $this->resource->title,
'body' => $this->resource->body,
'images' => new ImageCollection($this->whenLoaded('images')),
];
$filter = $request->query->get('filter', '');
if($filter){
$data['comments'] => new CommentCollection($this->resource->comments);
}
return $data;
}
Also, for collections, you need to create a ResourceCollection.
PostResourceCollection.php
class PostResourceCollection extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
In your controller:
PostsController.php
//show one post
public function show(Post $post, Request $request)
{
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResource($post));
}
//list of posts
public function index(Request $request)
{
$posts = Post::all();
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResourceCollection($posts));
}
Partial Solution
It will need a small change in resource class
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'comments' => new CommentCollection($this->whenLoaded('comments')),
'images' => new ImageCollection($this->whenLoaded('images')),
];
return $data;
}
and it will load comments and images if loaded and that depends on the include query parameter, if that is not included, it will not load the relationship.
However,
In post collection
return [
'data' => $this->collection->transform(function($post){
return [
'id' => $post->id,
'title' => $post->title,
'body' => $post->body,
'comments' => new CommentCollection($post->whenLoaded('comments')),
'images' => new ImageCollection($post->whenLoaded('images')),
];
}),
];
will results in
"Call to undefined method App\Models\Customer::whenLoaded()",, if anyone suggests a complete solution, it will be a great help, if I will able to do, it I will update here.

Fractal parseIncludes not working

I'm using the Laravel framework for my web app, eloquent models for data and Fractal to transform some data.
I want to use the parseIncludes functionality of fractal but I can't seem to get it working despite following the docs.
Here's my code:
StudentTransformer.php
class StudentTransformer extends Fractal\TransformerAbstract
{
protected $availableIncludes = [
'course'
];
public function transform(Student $student)
{
return [
'name' => $student->name,
// other attributes
];
}
public function includeCourse(Student $student)
{
$course = $student->course;
return $this->item($course, new CourseTransformer);
}
}
CourseTransformer.php
class CourseTransformer extends Fractal\TransformerAbstract
{
public function transform(Course $course)
{
return [
'name' => $course->name
// other attributes
];
}
}
In one of my controllers:
$student = App\Models\Student::first();
$fractal = new \League\Fractal\Manager();
$fractal->parseIncludes('/student?include=course');
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
$response = new \League\Fractal\Resource\Item($student, new \App\Transformers\Models\StudentTransformer);
return response()->json($fractal->createData($response)->toArray());
Also, when I remove the availableIncludes from the StudentTransformer and use defaultIncludes instead, like so:
protected $defaultIncludes = [
'course'
];
It works just fine?! No idea why! Any help would be appreciated.
Fixed it. For the benefit of others, the issue was here:
$fractal->parseIncludes('/student?include=course');
It should just be:
$fractal->parseIncludes('course');

How to handle multidimensional output with (nested) lists using the Zend\Db\TableGateway with a mapper in Zend Framework 2?

I'm developing a RESTful ZF2 based application and using a TableGateway implementation (subclass of the Zend\Db\TableGateway) in combination with a simple mapper for the model, similar to the Album example of the ZF2 manual.
Table class
<?php
namespace Courses\Model;
use ...
class CourseTable {
protected $tableGateway;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
public function findOnceByID($id) {
$select = new Select();
$where = new Where();
$select->columns(array(
'id',
'title',
'details',
));
$select->from($this->tableGateway->getTable());
$select->where($where, Predicate::OP_AND);
$resultSet = $this->tableGateway->selectWith($select);
return $resultSet;
}
}
Mapper class
<?php
namespace Courses\Model;
use ...
class CourseDetails implements ArraySerializableInterface {
public $id;
public $title;
public $details;
public function exchangeArray(array $data) {
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
$this->details = (isset($data['details'])) ? $data['details'] : null;
}
public function getArrayCopy() {
return get_object_vars($this);
}
}
Controller
<?php
namespace Courses\Controller;
use ...
class CoursesController extends RestfulController // extends AbstractRestfulController
{
protected $acceptCriteria = array(
'Zend\View\Model\JsonModel' => array(
'application/json',
),
'Zend\View\Model\FeedModel' => array(
'application/rss+xml',
),
);
private $courseTable;
public function get($id)
{
$course = $this->getCourseTable()->findOnceByID($id)->current();
$viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
$viewModel->setVariables(array('data' => array(
'id' => $courseDetails->id,
'title' => $courseDetails->title,
'details' => $courseDetails->details
)));
return $viewModel;
}
...
}
It's working for a shallow output like this:
{
"data":{
"id":"123",
"title":"test title",
"details":"test details"
}
}
But now I need a multidimensional output with nested lists like this:
{
"data":{
"id":"123",
"title":"test title",
"details":"test details",
"events":{
"count":"3",
"events_list":[ <- main list
{
"id":"987",
"date":"2013-07-20",
"place":"Berlin",
"trainers":{
"count":"1",
"trainers_teamid":"14",
"trainers_teamname":"Trainers Team Foo",
"trainers_list":[ <- nested list
{
"id":"135",
"name":"Tom"
}
]
}
},
{
"id":"876",
"date":"2013-07-21",
"place":"New York",
"trainers":{
"count":"3",
"trainers_teamid":"25",
"trainers_teamname":"Trainers Team Bar",
"trainers_list":[ <- nested list
{
"id":"357",
"name":"Susan"
},
{
"id":"468",
"name":"Brian"
},
{
"id":"579",
"name":"Barbara"
}
]
}
},
{
"id":"756",
"date":"2013-07-29",
"place":"Madrid",
"trainers":{
"count":"1",
"trainers_teamid":"36",
"trainers_teamname":"Trainers Team Baz",
"trainers_list":[ <- nested list
{
"id":"135",
"name":"Sandra"
}
]
]
}
]
}
}
}
How / where should I assemble the data to this structure? Directly in the mapper, so that it contains the whole data? Or should I handle this with multiple database requests anb assemple the structure in the controller?
What you are trying to accomplish has nothing to do with the TableGateway-Pattern. The TableGateway-Pattern is there to gain access to the Data of one specified Table. This is one of the reasons why in ZF2 you no longer have the option to findDependantRowsets(). It's simply not the TableGateways Job to do so.
To achieve what you are looking for you have pretty much two options:
1. Joined Query
You could write a big query that joins all respective tables and then you'd manually map the output into your desired JSON Format.
2. Multiple Queries
A little less performant approach (looking at the SQL side of things) but "easier" to "map" into your JSON Format.
To give some insight, Doctrine would go with the multiple query approach by default. This is mostly (i guess!) done to provide features that would work on every data backend possible rather than just a couple of SQL Versions...
Service Class
Since you're wondering about the assembling of the json / array, i would set it up like this
'service_manager' => array(
'factories' => array(
'MyEntityService' => 'Mynamespace\Service\Factory\MyEntityServiceFactory'
)
)
// MyEntityServiceFactory.php
// assuming you only need one dependency! more lines for more dependencies ;)
class MyEntityServiceFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
return new MyEntityService($serviceLocator->get('YourTableGateway'));
}
}
// Your SERVICE Class
class MyEntityService {
// do constructor and stuff to handle dependency
public function someBigQueryAsArray() {
// Query your Gateway here and create the ARRAY that you want to return,
// basically this array should match your json output, but have it as array
// to be used for other stuff, too
}
}
// lastly your controller
public function someAction() {
$service = $this->getServiceLocator()->get('MyEntityService');
$data = $service->someBigQueryAsArray();
// do that viewmodel selector stuff
// ASSUMING $data is a array of more than one baseObject
// i did this in my app to produce the desired valid json output, there may be better ways...
if ($viewModel instanceof JsonModel) {
foreach($data as $key => $value) {
$viewModel->setVariable($key, \Zend\Json\Json::encode($value));
}
return $viewModel;
}
// Handle other ViewModels ....
}

Categories