How to set up CakePHP 2.x to behave like a RESTful webservices (for using it together with JavascriptMVC) - cakephp

I am trying to set up cakephp to work with the very nice javascriptMVC (http://forum.javascriptmvc.com). JavaScriptMVC requires the JSON-Output in the following format:
[{
'id': 1,
'name' : 'Justin Meyer',
'birthday': '1982-10-20'
},
{
'id': 2,
'name' : 'Brian Moschel',
'birthday': '1983-11-10'
}]
Cake would generate a deeper nested array with a prepended Class Name. I found attempts to solve the problem but theyre not for cakephp 2.x. I know that I can simply generate a new array and json_encode() it via php, but it would be nicer to include a function like this https://gist.github.com/1874366 and another one to deflatten it.
Where would be the best place to put such functions? The AppController doesnt seem to work. Should i put it in beforeRender () or beforeFilter() of the controller? Or does someone maybe even know of an existing solution/plugin for this? This would be the best for me in my current Situation, as Im pretty much pressed for time.

Ok, I'm not 100% sure I understand what you are trying to do so here's a word to the wise just in case: Cake and JMVC are both comprehensive MVC frameworks. if you are attempting to combine them as a single cohesive platform to build your application, I strongly suggest you review your approach / platform / etc.
Also -- I'm not an expert by any means in jmvc, so I'm just going to pretend that processing the response from Cake in jmvc is completely out of the question, for some odd reason. For the record, think of Cake's responses like this:
{ "Model" :
[{
'id': 1,
'name' : 'Justin Meyer',
'birthday': '1982-10-20'
},
{
'id': 2,
'name' : 'Brian Moschel',
'birthday': '1983-11-10'
}]
}
Cake has had comprehensive REST service support, since at least Cake 1.2. The lib you are interested in is HttpSocket. As for json encoding and serving response, Request Handling covers, among other things, responding to all manners of requests, content types, decoding and encoding json, etc. Finally, the built-in Set utility will almost certainly cover whatever array manipulation you need in a line or two.
The functionality you are interested in is pretty basic and hasn't changed too much. I'd bet a lot of the (reasonably simple) solutions you have already found would probably still work, maybe with a little bit of tweaking.
For pretty much any basic service endpoint, you would probably create a controller (not AppController - that is application-wide, hence you can't invoke it directly) method, considering Cake routes the controller/action into your url:
Cake consuming services from a different app would look like this:
http://cakeproject/collect/getInfo
class CollectController extends AppController {
public function getInfo($array = null) {
App::uses('HttpSocket', 'Network/Http');
$http = new HttpSocket();
$http->get('http://jmvcproject/controller/action', $array);
// ...etc.
}
Cake providing services from the same controller / action to a different app would simply be:
public function getInfo($array = null) {
$results = $this->Collect->find('all', $array);
// ...fetch the results
}
Or you could just loop over that array with foreach($this->data as $data) { ... to drop the class name. But if your data will include associated models, etc, Set is probably the most versatile and resilient solution.
Anyway, HTH

Related

CakePHP How to create pagination from API JSON data source

I have an API like example
I have used cakephp HTTP client to get data, below my attempted code
public index()
{
$http = new Client();
$response = $http->get('https://restcountries.eu/rest/v2/all');
// $json = $response->getJson(); //also tried usgin json
$countries = $this->paginate($response);
$this->set(compact('countries '));
}
I am trying to apply pagination with this country data then fetch it in view with pagination.
After tried above code , I have gotten below error
Argument 1 passed to Cake\Datasource\Paginator::extractData() must be an instance of Cake\Datasource\RepositoryInterface, instance of Cake\Http\Client\Response given, called in \myapp\vendor\cakephp\cakephp\src\Datasource\Paginator.php on line 176
How can I get my desire result ?
You have probably need to implement a class who extend RepositoryInterface.
class JsonSource implements Cake\Datasource\RepositoryInterface
{ ... }
public index() {
$http = new Client();
$response = $http->get('https://restcountries.eu/rest/v2/all');
$src = new JsonSource();
$src->fromResponse($response);
$countries = $this->paginate($src);
$this->set(compact('countries ')); }
Is a bit tedious, because you need to define Json like a datasource.
The default pagination only supports querying tables (repositories), or operating on pre-built query instances.
To extend on #Zeppi's answer. You basically have three somewhat straightforward options here:
Create custom query/repository implementations as hinted by #Zeppi.
This can indeed be quite a lot of work though, so you might want to look into alternatively implementing it with the help of plugins, for example muffin/webservice, which does most of the hard work of implementing the required interfaces.
Or create a custom paginator that actually accepts and works on array data.
Or use what is widely know as a "datatable", that is a JavaScript based table in the frontend that paginates the data, for example jQuery DataTables.

How can i fetch dynamic data from database based on selected language.?

Hi i am working on a project in laravel 7.0, in back-end i have a table called Posts which contains 2 text language input one in french and the other is arabic added by the back-end application.
what i am trying to do is when the user uses the French Language i want the title_fr to be displayed on the view and same thing in Arabic language the title should be title_ar.
P.S data are stored in French and Arabic
I have tried the similar solution given in an other similar question but none of it worked in my case!
Any idea how i might get this to work ?
Thanks in advance.
You can do something similar to below. We have a model Post, this model has an attribute title. I also assume that you have an attribute that will return user's language from the User model.
class Post extends Model
{
public function getTitleAttribute(): string
{
return Auth::user()->language === 'fr' ? $this->title_fr : $this->title_ar;
}
}
FYI above is just a demo on what can be done. For a full blow solution I would recommend decorator pattern.
Also it might be worth considering using morph for things like that. You can have a service provider that will initiate the morph map for you post model relevant to the language that user has, I.e.
Class ModelProvider {
Protected $models = [
‘fr’ => [
‘post’ => App/Models/Fr/Post::class,
],
‘ar’ => [
‘post’ => App/Models/Ar/Post::class,
]
];
Public function boot() {
$language = Auth::user()->Settings->language;
Relation::morphMap($This->models[$language]);
}
}
Afterwards you just need to call to Relation::getMorphModel(‘post’) to grab Post class that will return correct language.
I.e. App/Models/Fr/Post can have a an attribute title:
Public function getTitleAttribute(): string {
Return $this->title_fr;
}
For example above you would also want to utilise interfaces to make sure that all models follow the same contract, something below would do the trick:
Interface I18nPostInterface {
Public function getTitleAttribute(): string
}
Also, depending on the database you use, to store titles (and other language data) in a JSON format in the database. MySQL 8 has an improve support for JSON data, but there are limitations with that.
So I was Able to fetch data from my database based on the Language selected by the user.
Like i said before I have a table called Posts and has columns id,title_fr and title_ar. I am using laravel Localization.
Inside my PostController in the index function i added this code:
public function index()
{
//
$post = Post::all();
$Frtitle = post::get()->pluck('title_fr');
$Artitle = post::get()->pluck('title_ar');
return view('post.index',compact('post','Frtitle','Artitle'));
}
if anyone has a better way then mine please let me know, i am sure
there is a better way.

Yii2 + Redis as Database

I want to use Yii2 and redis as database.
So far, I got Redis ActiveRecord Class for Yii2 from Here.
link1
link2
but, I got a problem. WHY THIS CLASS ADDS ANYTHING AS HASH IN REDIS????
Above that I cant Find in which pattern it Insert data. I add one user and it will add a user under user:xxx namespace and another record under s:user:xxx and so on but none of theme has any fields that i defined in attributes!! only contain IDs.
I know that a Key-value type database and RDBMS are different and also know how can implement relation like records in Redis, but I don't know why it will only save IDs.
I could not find any example of using redis ActiveRecords so far.
There is one in here and its not good enough.
So here is my main wuestion: how can add data to redis Using activeRecords and different data types In YII2?
And if its impossible with ActiveRecords what is the best solution? in this case
ANOTHER QUESTION: is it possible to use a Model instead and write my own model::save() method? and what is the best data validation solution at this rate?
Actually I want to make a telegram bot, so i should get messages and send them in RabitMQ and get data in a worker, do the process and save results to Redis, and finally send response to user through the RabitMQ.
So I need to do a lot of validations AND OF COURSE AUTHENTICATIONS and save and select and range and save to sets an lists and this and that ....
I want a good way to make Model or active record or the proper solution to validation, save and retrieve data to Redis and Yii2.
Redis DB can be declared as a cache component or as a database connection or both.
When it is declared as a cache component (using the yii/redis/cache) it is accessible within that component to store key/value pairs as shown here.
$cache = Yii::$app->cache;
// try retrieving $data from cache
$data = $cache->get($key);
// store $data in cache so that it can be retrieved next time
$cache->set($key, $data);
// one more example:
$access_token = Yii::$app->security->generateRandomString();
$cache->add(
// key
$access_token,
// data (can also be an array)
[
'id' => Yii::$app->user->identity->id
'name' => Yii::$app->user->identity->name
],
// expires
60*60*3
);
Also other components may start using it for caching proposes like session if configured to do so or like the yii\web\UrlManager which by default will try to cache the generated URL rules in whatever valid caching mechanism defined under the config file's cache component as explained here. So it is normal to find some stored data other than yours in that case.
When Redis is declared as a DB connection like in the links you provided which means using the yii\redis\Connection class you can make your model extending its \yii\redis\ActiveRecord class as any other ActiveRecord model in Yii. The only difference I know so far is that you need to define your attributes manually as there is no DB schema to parse for NoSQL databases. Then just define your rules, scenarios, relations, events, ... as any other ActiveRecord model:
class Customer extends \yii\redis\ActiveRecord
{
public function attributes()
{
return ['id', 'name', 'address', 'registration_date'];
}
public function rules()
{
return [
['name', 'required'],
['name', 'string', 'min' => 3, 'max' => 12, 'on' => 'register'],
...
];
}
public function attributeLabels() {...}
...
}
All available methods including save(), validate(), getErrors(), ... could be found here and should be used like any other ActiveRecord class as shown in the official guide.

CakePHP: Scaffolding after having written edit/view/add

I have an application in which we give a very friendly interface for managing data. This is done through many controllers' add/edit/view functions. But now the requirement has come that we should have "super admins" able to edit anything, and scaffolding will give them a quick and dirty manner of changing data. Since scaffolding uses add/edit/view by default, I've unintentionally overwritten the ability to scaffold.
I can't just go and change all my calls to edit/add for our "user friendly" data managing. So I want to essentially ignore the add/edit/view when, for example, a user has a flag of "yes, please let me scaffold". I imagined it would be something like:
public function edit($id) {
if (admin_user) {
$scaffold;
} else {
[user-friendly version code]
}
}
But no dice. How can I achieve what I want?
suppose you already have admin users and you want to scaffold only super-user:
Also suppose you store the information about beeing a super-user or not in a column named super in the users table
in your core.php
Configure::write('Routing.prefixes', array('admin', 'super));
in your appController
public $scaffold = 'super';
beforFilter() {
if($this->Auth->user('super') && !isset($this->params['super'])
$this->redirect(array('super' => true));
}
Now I can't try this code but the idea should work.
edit: we need to check if we are already in a super_action to avoid infinite redirect

CakePHP - select database config based on route or url?

I'm working on a small CakePHP application that is subject to the following constraint (awkward but out of my control): I need it to work on either of two identical databases, with the choice being based on URL. For example:
http://www.example.com/myapp/foo/action/param
http://www.example.com/myapp/bar/action/param
The first obvious solution is to have two identical CakePHP applications at myapp/foo and myapp/bar with different database configurations. This has a kludgy feel to it, though, so I'm trying to find an elegant way of creating a single application.
The approach I'm considering is this: Define routes such that myapp/foo and myapp/bar will be associated with the same controller. Then give my DATABASE_CONFIG class a constructor:
function __construct() {
$pathParts = explode('/', $_SERVER['REQUEST_URI']);
if (array_search('foo', $pathParts)) {
$this->default = $this->fooConfig;
} else if (array_search('bar', $pathParts)) {
$this->default = $this->barConfig;
}
}
(Where of course I've defined fooConfig and barConfig for the two databases.) I do have control over the URL, so I can be confident that there won't be extraneous occurrences of foo or bar in the URL.
My question is this: Is there a simpler, more elegant way of handling this odd situation? Maybe something in AppModel and/or AppController? Although I'm getting rid of duplicated code, I can't shake the feeling that I'm replacing one code smell with another.
There are a few ways to do this, here is one.
Write a sweet custom route in which you always match:
Router::connect('/:ds/*', array(), array('routeClass' => 'SweetDbRoute'));
Then have SweetDbRoutes set a class variable you can use everywhere, including in your model constructors. Then it should fail so you don't actually adjust the request.
App::import('SweetDbClass', array('file' => '/path/to/your/sweet_db_class.php'));
class SweetDbRoute extends CakeRoute {
// put your failing route code here, but use your SweetDbClass to track datasource ...
// see http://book.cakephp.org/view/1634/Custom-Route-classes
}
Then in your AppModel:
App::import('SweetDbClass', array('file' => '/path/to/your/sweet_db_class.php'));
class AppModel extends Model {
public function __construct($id = false, $table = null, $ds = null) {
$ds = SweetDbClass::$ds;
parent::__construct($id, $table, $ds);
}
}
So for example, after you perform an insert in one database, the two won't be "identical", right? Are these 2 DB somehow synced with each other? I don't know what do you need to do on those DB, but it's probably easier just to do 2 separate apps.
Yes, you can specify the DB configuration in the model: http://book.cakephp.org/view/922/Database-Configuration but you can't change it on-the-fly though (the models are not expected to change association to another table, I suppose). What you do is probably the only way.
I do have control over the URL, so I can be confident that there won't be extraneous occurrences of foo or bar in the URL
Yes, there can be "extraneous occurrences of foo or bar in the URL" :)) But it won't break your app.

Resources