Im really new to Laravel. I have manage to set up a database via the migration functionality, and now i want to renturn a table from the database as json. What im working on is kind of a rest-api-thingy. Nothing too fancy.
In my router i have a route going to /api/cases wich inits the controller for the cases. From that controller i basically just want to return a table from my database as JSON.
Router:
Route::resource('/api/cases', 'CasesController');
Controller:
class CasesController extends \BaseController {
public function index()
{
//return db table as json here
}
}
Model:
class Case extends \Eloquent {
protected $fillable = [];
}
And my database looks like this:
I have only one table, named "cases". That one has attributes like "id", "name", "title".
How would i now return that rest-like as json?
You can simply call the toJSON() method:
Case::all()->toJson();
I assume you have your Case model tested and working properly. Once that's done, you can query for all the objects in this table, convert the result to an array, and encode it as JSON.
public function index()
{
return Response::json(Case::all()->toArray());
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
I often give talks about APIs and the dangers of trying to expose database schema directly. Unless you app is on an internal network, and only your app looks at this data, and your app will never going to change at all then interacting directly with the table is a very bad idea.
Here is my talk, which uses Laravel as an example a few times.
Related
So I need to translate the records from the database, Im searching a way to do this 'automatic' way but I cannot find any solution.
I'm using astrotomic/laravel-translatable and I created a new Provider for the 'Food' in en_US and in hr_HR.
So I seed the database with both en and hr, but they give random stuff where the translations are not correct.
For example.... Food name is 'pasta' in en(english), and 'pasta' in hr(croatian) is 'tjestenina'...
Can i seed the database with just 'pasta' and do some stuff to translate it automatically or I need to do this by hand.
Here's the code....
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Category;
class MealFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
# When installed via composer
require_once 'vendor/autoload.php';
$en_faker = \Faker\Factory::create('en_US');
$hr_faker = \Faker\Factory::create('hr_HR');
$title_en = $en_faker->unique()->food;
$title_hr = $hr_faker->unique()->food;
$desc_en = $en_faker->text;
$desc_hr = $hr_faker->text;
return [
'category_id' => rand(null, 5),
'en' => [
'title' => $title_en,
'description' => $desc_en
],
'hr' => [
'title' => $title_hr,
'description' => $desc_hr
],
'status' => 'created'
];
}
}
and this is the seeder...
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Meal;
use App\Models\Tag;
use App\Models\Ingredient;
class MealSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Meal::factory()->count(10)->create();
foreach (Meal::all() as $meal) {
$tags = Tag::inRandomOrder()->take(rand(1, 8))->pluck('id');
$ingredients = Ingredient::inRandomOrder()->take(rand(1, 6))->pluck('id');
$meal->tags()->attach($tags);
$meal->ingredients()->attach($ingredients);
}
}
}
If you don't want to store your translated record on database at all your data is dynamic You can use a third-party api for translation realtime and you may create a Controller for using that translation api and then show it to user.
If your data is static you can also use a translation api on your seeder, by reading English words statically from database and translate it to Croatian using api and store it on database
Otherwise you should you need to do it by hand.
In case you are not familiar with using a third-party this link might help you
Why don't you just initialize the database with the boostrap English data and write a snippet (probably using a translation library) to translate your English data to Croation (and store it in the correponding database field)?
BTW, it might be better practice to store these data in e.g., POT files (used in Wordpress etc.) for more flexible internationalization (i18n).
I'm using Laravel's API Resource functionality to format my responses nicely for the client, but the trouble I'm having is with the code below;
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection->transform(function ($item)
{
return [
'id' => $item->id,
'title' => Str::limit($item->title, 32),
'body' => Str::limit($item->body, 32),
'created_at' => $item->created_at->format('d M Y, H:i a'),
'user' => $item->user
];
}),
'links' => [
'current_page' => $this->currentPage(),
'total' => $this->total(),
'per_page' => $this->perPage(),
],
];
}
When using this code, I get an error; "Call to a member function format() on null" on the created_at attribute.
But I've already used dd($this->collection) to confirm that none of the attributes are in fact null and I'm not really sure what could be causing it. My migration contains $table->timestamps();, and inside my factory, I'm not overriding the timestamps at all, so I'm not really sure what the issue is.
Here is the test I'm running below to get this error as well;
factory(News::class, 10)->create();
$user = factory(User::class)->create();
$this->actingAs($user)
->get('/news')
->assertOk()
->assertPropCount('news.data', 10)
->assertPropValue('news.data', function ($news)
{
$this->assertEquals(
[
'id', 'title', 'body', 'created_at',
'user',
],
array_keys($news[0])
);
});
The extra functions such as assertPropCount and assertPropValue are sourced from the InertiaJS demo app as I'm using InertiaJS in my project.
Hopefully, someone is able to help as I've asked around a few other places and no one seems to know what the reason for this is, and based on my debugging there doesn't really seem to be much of a valid explanation as to WHY created_at is null.
As a note, if I turn $item->user to $item->user->toArray() in the code as well, this then also fails complaining that user is null when it isn't. It seems that trying to chain any method onto any attribute causes this null error and I'm not sure why.
First of all keep in mind that the transform function you are using alter the original $this->collection property, you better use map instead that serves the same purpose as transform without altering the original array.
This might be related to your problem because you are modifying the collection you are iterating on, and that can cause issues.
Furthermore, I would suggest you to keep on reading this answer and try out one of the two refactoring alternatives I explained below. That's because I think you are not using API resources correctly and using them properly could actually solve the issue.
Suggestion about your API resource structure
The correct way would be to have two separate files: a News resource and a NewsCollection resource.
This setup allows to define the rendering structure of a single News as well as a collection of News and reuse the former while rendering the latter.
To implement API resources correctly there are a couple of ways (based on what you are trying to achieve):
Note: in both methods, I take for granted that you already have an additional User resource that defines the structure to render a User model (the $this->user property of a News).
1) Create separate classes for single and collection resources
You have to create two files in your resources folder through these two artisan commands:
// Create the single News resource
php artisan make:resource News
// Create the NewsCollection resource
php artisan make:resource NewsCollection
Now you can customize the collection logic:
NewsCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class NewsCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
// Each $this->collection array item will be rendered automatically
// with the News resource definition, so you can leave data as it is
// and just customize the links section/add more data as you wish.
'data' => $this->collection,
'links' => [
'current_page' => $this->currentPage(),
'total' => $this->total(),
'per_page' => $this->perPage(),
],
];
}
}
as well as the single News resource logic:
News.php
<?php
namespace App\Http\Resources;
use App\Http\Resources\User as UserResource;
use Illuminate\Http\Resources\Json\JsonResource;
class News extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => Str::limit($this->title, 32),
'body' => Str::limit($this->body, 32),
'created_at' => $this->created_at->format('d M Y, H:i a'),
'user' => new UserResource($this->user)
];
}
}
To render a news collection, you only have to do:
use App\News;
use App\Http\Resources\NewsCollection;
// ...
return new NewsCollection(News::paginate());
Laravel will automatically reuse the News resource class to render each single element of the NewsCollection's $this->collection array when you are converting the NewsCollection instance for response.
2) Exploit the ::collection method of the single News resource
This method is applicable only if you need metadata about paginated responses (it seems that is what you are trying to achieve with your code).
You just need the single News api resource that you can generate with:
// Create the single News resource
php artisan make:resource News
Then customize the single resource according to your needs:
News.php
<?php
namespace App\Http\Resources;
use App\Http\Resources\User as UserResource;
use Illuminate\Http\Resources\Json\JsonResource;
class News extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => Str::limit($this->title, 32),
'body' => Str::limit($this->body, 32),
'created_at' => $this->created_at->format('d M Y, H:i a'),
'user' => new UserResource($this->user)
];
}
}
Then to render a paginated collection of news, just do:
use App\News;
use App\Http\Resources\News as NewsResource;
// ...
return NewsResource::collection(News::paginate());
The first method allow for a better overall control of the resulting output structure, but I would not structure the $this->collection inside the collection class.
The responsability to define how each collection element should be structured is of the News resource class.
The second method is quicker and works really nice with Laravel pagination allowing you to save quite some time to generate paginated responses with links (that seems what you want to achieve from your code).
Sorry for the long post, if you need further explaination just ask.
I have one quite simple question, Imagine I have Orders model and now I am writing something like that :
Order::where('status', 1)->with('orderer')->get();
Ok. It's simple and returns something like that:
{
id: 1,
price: 200,
status: 1,
income: 21,
orderer_id: 4,
orderer: {
//some orderer fields
}
}
now I don't want to get the whole object, I want to remove income, orderer_id and status properties from data. if I write something like that : get(["id", "price"]) I end up without orderer object (get(["id", "price", "orderer"]) doesn't work too), I couldn't make it work even using select(), so what is the solution? Also I don't want to hide it from everyone, for example admin should know income but user shouldn't, so $hidden field will not work.
You can add select() but make sure select does not take array but comma separated arguments :
$orders = Order::where('status', 1)->with('orderer');
if($user->role == 'admin'){
$orders->select('id','income','status','price');
}
else{
$orders->select('id','status','price');
}
$orders = $orders->get();
Above will first check the current logged in user's role and accordingly will select the columns required.
https://scotch.io/bar-talk/hiding-fields-when-querying-laravel-eloquent-models
In your Order Eloquent model:
protected $hidden = array('hide_this_field', 'and_that_field');
Edit: You want to filter based on role like Admin or User, next time please write that down in your question as well. Well a solution for that is to capture the DB query result, and walk that array, then unset properties of the model if the user is not an admin.
Edit2: I also see a discussion here which might help. Some user suggested using middle ware:
https://laracasts.com/discuss/channels/laravel/hide-eloquent-fields-based-on-user-role-or-any-model
If you are looking for a built in Laravel way to handle this, you could use API Resources: https://laravel.com/docs/5.7/eloquent-resources
php atrisan make:resource OrderResource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$current_role = $request->user()->role; //or however you determine admin etc
$out = [
'id' => $this->id,
'price' => $this->price,
'orderer'=> $this->orderer,
];
if($current_role == 'admin'){
$out['income'] = $this->income;
$out['status'] = $this->status;
}
return $out;
}
}
In your Controller action
return OrderResource::collection(Order::where('status', 1)->with('orderer')->get());
If you want something a little more robust, consider https://github.com/spatie/laravel-fractal
I created an API for my various SPA's using a Laravel resource controller that works like a charm. While BackboneJS has no problem with the default JSON response, EmberJS expects the result wrapped in a singular and plural named JSON object depending on if it's fetching a single model or a collection of models.
What I already know
Convert the JSON result to an array wrap it and convert it back to JSON.
Wrapping every result in an object.
Change the result comming from the database call to an array with setFetchMode(PDO::FETCH_ASSOC);.
Question
How to properly (using the cheapest process) create the wrapped JSON responses, so without converting the results back and forth between JSON, Arrays or Objects?
A snippit from the Laravel resource controller:
<?php
class ResourceController extends Controller {
...
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index() {
return Model::all();
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id) {
return Model::find($id);
}
...
}
This will wrap the original Eloquent database result for a Laravel application with a database configured as "fetch" => PDO::FETCH_CLASS. It creates a new object of Illuminate\Database\Eloquent\Collection and wraps the Eloquent result in a models property. This way it's formatted properly for using it with Ember data without any conversions.
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index() {
$index = new Illuminate\Database\Eloquent\Collection;
$index['models'] = Model::all();
return $index;
}
You can do that with following structure. Always, return your json response in wrapped format like(I assumed you have User model);
{
"user": [
{"name": "Hüseyin"},
{"surname": "BABAL"},
{"title": "Software Developer"}
]
}
This is suitable for ember.js. For backbone.js, you need to do some extra simple job like following;
var User = Backbone.Collection.extend({
model: User,
url: '/api/userInfo',
parse: function(response) {
return response.user;
});
}
});
By doing this, you will only one format rest service, and you just need to do some extra job for only backbone.js
Update:
Think about index action, you can do that like;
public function index() {
return Response::json(array("user" => Model::all()));
}
Result will be wraped with "user", you can use response as json on frontend. The result will be;
{
"user": [
{...},
{...}
]
}
I'm writing some code that allows users to read reports on a site, using AJAX calls to dynamically load only what is requested, instead of the entire 15+MB report.
I'm writing a Model to access all the report data from the database, and I don't want to use the Active Record pattern. I'm following the idea of "A Model HAS a table, instead of IS-A table", since this model will be accessing 5 different tables, and there are some complex MySQL JOIN's between these tables.
What is a good design pattern to follow in Zend Framework for this, examples?
UPDATED on 2012-12-05 # 12:14PM EST
I'm currently working for a Market Research Report company. Without using actual function names, or revealing any meaningful details of the code, here are the basics:
readreportAction() does:
get the report meta data
get the report "table of contents"
readsectionAction() does:
get the report text, only a part of it
get the embedded tabular data
get the figures / images
get the footnotes
format the report text
reportpdfAction() does the exact same thing as readreportAction() and readsectionAction(), except all at one time. I'm trying to conceptualize a way to NOT copy + paste this code / programming logic. A data mapper seems to solve this.
I would recommend the Data Mapper pattern.
Everything you said makes sense and this pattern fits. Your model should not know or care how it is persisted. Instead the mapper does what it suggests - maps your model to your database. One of the things I like about this approach is it encourages people to think about the model in terms of an object, not a relational database table, as often happens with active record patterns and table row gateways.
Your object, unless very simple, typically will not reflect the structure of a database table. This lets you write good objects and then worry about the persistence aspects afterward. Sometimes more manual in that your mapper will need to deal with the complex joins, probably requiring writing some code or SQL, but the end result is it does just what you want and nothing more. No magic or conventions required if you don't want to leverage them.
I've always though these articles do a good job of explaining some of the design patterns that can be used well in ZF: http://survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors#zfbook.implementing.the.domain.model.entries.and.authors.exploring.the.entry.data.mapper
UPDATE:
Well you mapper might extend from an interface similar to this:
<?php
interface Mapper_Interface
{
/**
* Sets the name of the entity object used by the mapper.
*/
public function setObjectClass($class);
/**
* Sets the name of the list class used by the mapper.
*/
public function setObjectListClass($listClass);
/**
* Get the name of the object class used by the mapper.
*
*/
public function getObjectClass();
/**
* Get the name of the object list class used by the mapper.
*
* #return string
*/
public function getObjectListClass();
/**
* Fetch one row.
*
* #param array $where Criteria for the selection.
* #param array [$order = array()] Optionally the order of results
* #return Object_Abstract
* #throws Mapper_Exception
*/
public function fetchRow($where, $order = array());
/**
* Fetch all records. If there is no underlying change in the persisted data this should
* return a consistant result.
*
* #param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
* #param string|array $order OPTIONAL An SQL ORDER clause.
* #param int $count OPTIONAL An SQL LIMIT count.
* #param int $offset OPTIONAL An SQL LIMIT offset.
* #return Object_List_Abstract
* #throws Mapper_Exception
*/
public function fetchAll($where = null, $order = null, $count = null, $offset = null);
/**
* Deletes one or more object.
*
* #param array|string $where Criteria for row deletion.
* #return integer $affectedRows
* #throws Mapper_Exception
*/
public function delete($where);
/**
* Saves a record. Either updates or inserts, as required.
*
* #param $object Object_Abstract
* #return integer $lastInsertId
* #throws Mapper_Exception
*/
public function save($object);
}
And you would interact with the mapper like:
$fooObjectMapper = new Foo_Mapper;
$fooObjectList = $fooObjectMapper->fetchAll();
var_dump($fooObjectList->first());
or
$fooObjectMapper = new Foo_Mapper;
$fooObject = $fooObject->fetch(array('id = ?' => 1));
$fooObject->setActive(false);
$fooObjectMapper->save($fooObject);
I usually write a mapper abstract for any 'PDO' enabled databases. One of the attributes of that concrete mapper is then the Zend_Db_Adapter to issue commands against. Makes for a flexible solution, easy to use mock data sources in testing.
First it looks like you need to make a little bit more of a conceptual leap. With the data mapper pattern it helps to think in terms of objects instead of database tables. I found these two articles helpful when I needed to make the leap.
http://phpmaster.com/building-a-domain-model/
http://phpmaster.com/integrating-the-data-mappers/
That being said ZF 1 has some very useful tools for building a data mapper/domain model.
The convention in ZF 1 is for each table you are working with to be accessible through the Zend_Db_Table api. The simplest way I've found is to just use the DbTable resource for each table. You could also use the Zend_Db::factory or new Zend_Db_Table('tableName') or any other method that appeals to you.
This example is based on a mp3 song track.
//in effect this is the database adapter for database table 'track', This is $tableGateway used later.
<?php
class Application_Model_DbTable_Track extends Zend_Db_Table_Abstract
{
//name of database table, required to be set if name of class does not match name of table
protected $_name = 'track';
//optional, column name of primary key
protected $_primary = 'id';
}
there are several ways to attach a table to the Db adapter and the Zend_Db_Table api, I just find this method simple to implement and it makes setting up a mapper simple as well.
The mapper class is the bridge between the data source and your object (domain entity). The mapper interacts with the api for Zend_Db_Table in this example.
A really important point to understand: when using classes that extend Zend_Db_Table_Abstract you have all the basic functionality of the Zend_Db component at your disposal. (find(),fetchall(), fetchRow(), select() ...)
<?php
class Music_Model_Mapper_Track extends Model_Mapper_Abstract
{
//the mapper to access the songs artist object
protected $artistMapper;
//the mapper to access to songs album object
protected $albumMapper;
/**
* accepts instance of Zend_Db_Table_Abstract
*
* #param Zend_Db_Table_Abstract $tableGateway
*/
public function __construct(Zend_Db_Table_Abstract $tableGateway = null)
{
//at this point I tend to hardcode $tablegateway but I don't have to
$tableGateway = new Application_Model_DbTable_Track();
parent::__construct($tableGateway);
//parent sets the $tablegateway variable and provides an abstract requirement
//for createEntity(), which is the point of this class
}
/**
* Creates concrete object of Music_Model_Track
*
* #param object $row
* #return Music_Model_Track
*/
public function createEntity($row)
{
$data = array(
'id' => $row->id,
'filename' => $row->filename,
'format' => $row->format,
'genre' => $row->genre,
'hash' => $row->hash,
'path' => $row->path,
'playtime' => $row->playtime,
'title' => $row->title,
'track_number' => $row->track_number,
'album' => $row->album_id,//foriegn key
'artist' => $row->artist_id//foriegn key
);
//instantiate new entity object
return new Music_Model_Track($data);
}
/**
* findById() is proxy for find() method and returns
* an entity object.
*
* #param type $id
* #return object Model_Entity_Abstract
*/
public function findById($id)
{
//instantiate the Zend_Db_Select object
$select = $this->getGateway()->select();
$select->where('id = ?', $id);
//retrieve one database table row
$row = $this->getGateway()->fetchRow($select);
//create one entity object Music_Model_Track
$entity = $this->createEntity($row);
//return one entity object Music_Model_Track
return $entity;
}
//truncated
}
All that has gone before is for the express purpose of building the following object:
<?php
class Music_Model_Track extends Model_Entity_Abstract
{
/**
* $id, __set, __get and toArray() are implemented in the parent
*/
protected $album;
protected $artist;
protected $filename;
protected $format;
protected $genre;
protected $hash;
protected $path;
protected $playtime;
protected $title;
protected $track_number;
//artist and album mappers
protected $albumMapper = null;
protected $artistMapper = null;
//these are the important accessors/mutators because they convert a foreign key
//in the database table to an entity object.
public function getAlbum()
{
//if the album object is already set, use it.
if(!is_null($this->album) && $this->album instanceof Music_Model_Album) {
return $this->album;
} else {
//else we make a new album object
if(!$this->albumMapper) {
$this->albumMapper = new Music_Model_Mapper_Album();
}
//This is the album object we get from the id in our reference array.
return $this->albumMapper->findById($this->getReferenceId('album'));
}
}
//same as above only with the artist object.
public function getArtist()
{
if(!is_null($this->artist) && $this->artist instanceof Music_Model_Artist) {
return $this->artist;
} else {
if(!$this->artistMapper) {
$this->artistMapper = new Music_Model_Mapper_Artist();
}
return $this->artistMapper->findById($this->getReferenceId('artist'));
}
}
//the setters record the foriegn keys recorded in the table row to an array,
//this allows the album and artist objects to be loaded only when needed.
public function setAlbum($album)
{
$this->setReferenceId('album', $album);
return $this;
}
public function setArtist($artist)
{
$this->setReferenceId('artist', $artist);
return $this;
}
//standard setter and getters truncated...
}
so when using the track object you would get album or artist info like:
//this would be used in a controller most likely.
$mapper = new Music_Model_Mapper_Track();
$track = $mapper->findById('1');
//all of the information contained in the album or artist object is
//available to the track object.
//echo album title, year or artist. This album object also contains the artist object
//so the artist object would be available in two ways.
echo $track->album->title; //or
echo $track->artist->name;
echo $track->album->artist->name;
echo $track->getAlbum()->getArtist()->getName();
So what you really need to decide is how you want to structure your application. What I see as obvious may not be an option you wish to implement. A lot of the answers to your questions depend on exactly how these resources are to be used.
I hope this helps you at least a little bit.
You could consider using Doctrine 2. It's an ORM that does not use the ActiveRecord pattern.
In Doctrine, your models (entities) are all just normal PHP objects with zero knowledge of the database. You use mapping (xml, yaml or annotations) to tell Doctrine how they appear in the database, and the Entity Manager and repositories are used as a gateway for persisting entities or doing other database actions.