I made some classes having a lot of methods documented properly using PHP (something like a library).
Now, what the other developers will do is just require the PHP library I made in their code and use the predefined functions in it.
Is it possible to hide the PHP code (of the library I made) from the other PHP developers (requiring the file) and just show them the function name, parameters and its documentation without showing the code inside it? I'm not talking about obfuscation, which can be reversible, I'm talking about preventing users to actually see any code.
eg.
/**
*
* CREATE A NEW THREAD
* #param unknown_type $uid User ID of person who is creating the thread
* #param unknown_type $participant An array having collection of UID of people who are participating in this conversation
* #param unknown_type $msgtype Message Type Flags (1-normal, 2-chat, 3-sent as email, 4-profile post, 5-group post, 6-customer support)
* #param unknown_type $subject Subject of the thread
* #param unknown_type $tname Thread Name
* #param unknown_type $tpic Thread Cover Picture (Defaults to "")
* #param unknown_type $tflag Thread Flag (1-allowed,2-under review,3-blocked) (Defaults to 1)
* #return string|Ambigous <string, unknown> Thread ID on success, "" on failure
*/
public function createthread($uid,$participant,$msgtype,$subject,$tname,$tpic="",$tflag="1")
{
$randobj=new uifriend();
$tid=$randobj->randomstring(30,DB_MESSAGE,MSG_OUTLINE,msgoutline_tid);
$socialobj=new socialoperations();
$listid=$socialobj->createlist("threadlist_".$tid, "2",$msgtype,"1",$uid);
if($socialobj->addtolist($participant, $listid, $uid)!="SUCCESS")
{
return "";
}
if($listid=="")
{
$lasterror="An error occured in creating thread! Unable to Create Lists!";return "";
}
$dbobj=new dboperations();
$res=$dbobj->dbinsert("INSERT INTO ".MSG_OUTLINE." (".msgoutline_tid.",".msgoutline_subject.",".msgoutline_fid.",".msgoutline_participantid.",".msgoutline_msgtype.",".msgoutline_threadpic.",".msgoutline_threadflag.") VALUES
('$tid','$subject','$uid',$listid,'$msgtype','$tpic','$tflag')",DB_MESSAGE);
if($res=="SUCCESS")
{
return $tid;
}
else
{
$lasterror="Unable to create Thread!";return "";
}
}
The other developers must only be able to see the documentation I wrote above the function with the function name and parameters, but the code must not be accessible to them in any way.
Why I want this: I have a lot of secure code in my PHP file which I don't want to show to the other developers, but still allow them to call the functions and read the returned values.
You can't hide your code from other developers if you want to allow them call your functions directly. What you can do is to make a Web Service and give it's documentation to other developers.
Because I had a meta post so this was reopened and another meta post for formatting this question, I'll do my best to properly answer this question. Note that this is only a way of doing this, with its limitations stated at the end of the post.
The API
The remote server
You could create a web API in a different domain and access it from your main domain. I think the best way for explaining how it works is with a practical example. Imagine that your library includes the function 'joinstrings()', which takes 2 arguments. Then you have it in your separated web:
http://apiweb.com/functions.php
<?php
// Your API. I hope the real one is more complex than this (;
function joinstrings($s1, $s2)
{
return $s1 . $s2;
}
// More functions
The remote server access point
This is the public (but key-required) accessible page.
http://apiweb.com/joinstrings/index.php
<?php
// Check if the key is valid and if $v1 and $v2 aren't empty. Else, 'exit;'
include '../validate.php';
// Your API
include '../functions.php';
// The called function
echo joinstrings(urldecode($_GET['v1']), urldecode($_GET['v2']));
The wrapper
Now you can require all your programmers to learn how to use this API. Or, if you prefer to do it right, you'd make a wrapper that makes their life easier. You'd have a class with all the methods that you want to be accessible. You could do this wrapper with functions, but I think it's easier and better with an object and methods:
htpp://web.com/library.php
<?php
class DevelopersLibrary
{
private $Url = "http://apiweb.com/";
// Press your hand against the keyboard. A-Z0-9. Copy it in http://apiweb.com/validate.php
private $Key = "g139h0854g76dqfdbgng";
// Accesible method
public joinstrings($v1, $v2)
{
// Encode only the user input. You don't want to encode '?' nor '&'
if ($Return = file_get_contents($this->Url . 'joinstring'
'?key=' . $this->Key .
'&v1=' . urlencode($v1) .
'&v2=' . urlencode($v2)))
{
return $Return;
}
}
}
Developer's code
Finally, what your developers would do:
http://web.com/index.php
<?php
include './library.php';
$Lib = new DevelopersLibrary();
echo $Lib->joinstrings("Are you sure this is better", "than giving your developers access to the code?");
None of the code is tested, so you should expect some some typos.
Limitations
I can think of solutions for most limitations, but not to extend (more) this post I won't write them here. Ask for a solution to a limitation if you need it in the comments and I'll do my best. In normal case use, none of these limitations are THAT important.
Parameters passed. Using this method as described above, you can only pass numbers or strings as function parameters. Check out json_encoding() for passing other types.
Wrong returned values when there are bugs in the API or parameters passed. If there's a bug in the API, the developers cannot fix it and the returned value might be wrong. Now that might seem trivial, but what if they are trying to retrieve the join of 2 strings and retrieve another [wrong] string with the error text in it? Note: consider returning valid XML and then parsing it in your wrapper.
There's only a unique key which is there for preventing random users from using your API, not to be hidden from developers.
Slower speed. I don't think this even needs explanation.
Developer's extra work. This is solved this with the implementation of the wrapper.
Url length. There's a url length limitation for most browsers of 2000 characters, although I didn't find anything in the PHP manual for file_get_contents(). Read this SO question for more info about GET.
Sure there are more but these are the main ones I could think of.
I hope this long long answer is useful for you or someone.
Related
So I just started web development on my new job, meaning I only have very basic knowledge of php, my html, css and js knowledge is a lot better.
I'm trying to build a basic multiple language site via php sessions but I just can't get it to work the way I want.
I have an index.php file and two more folders called EN and DE in which I have the Englisch.html and the Deutsch.html file. The html files only contain some text and it's my goal to have two buttons (or similar) at the top of my site called EN and DE which switch the session to include the files in said folders and display the text beneeth the buttons and keep the language switch function on every page if I had more then one file per language. By default I want the language to be english so load the Englisch.html first.
I'm guessing I need to create an if else statement on every html file that checks the session so something like: if session EN include Englisch.html elseif session DE include Deutsch.html, and on the index.php I somehow need to set the parameters for the session so something like: startSession EN include Englisch.html startSession DE include Deutsch.html ?
I have no idea how far off I am and any help, espacially actual code examples would be greatly appreciated. I hope this described my problem precisely enough.
Your attempted solution is going to bite you in the long run.
It may seem like an easy solution to switch between different files for different languages, yet assume that your website becomes more dynamic, instead of *.html files you want to deal with *.php files, and then would need to have the same logic inside each of your localized files. It doesn't scale well.
I recommend using a translation libary, there are many available, I had good success with symfony's translation, that you can also include in any php project.
Then translation becomes:
$translatedString = $translator->trans("My content");
And the translation can then be maintained in yaml files and depending on the locale, the right language is chosen and each untranslated string will default to English.
And now, whenever your logic changes it is just at one place where you need to adapt it.
I agree with K0pernikus in that your solution won't scale well. You can write a simple translation library yourself in under an hour and you can then test it to see if it will be robust enough for your needs.
The idea is to have language files without any logic in them. You simply call the file you need. All the logic about which language, file, translation key etc. is contained in your library.
I'll keep it very simple and down to a single file; of course, the actual translations will each need to be stored in a file too.
Directory structure:
/locale/en/messages.php
/locale/fr/messages.php
translator.php (this is the main library file and needs to be included on every page)
Within each messages.php file you need to return an array of translation keys and their respective translation. The translation keys are what you will use in your views. These files will become large with many hundreds or thousands of lines for larger applications. If you intend to keep this home-grown solution you'll need to implement caching. That being said, I have files with hundreds of translations and don't notice any significant performance hit.
<?php
// en
return array(
'applicationName' => 'Matt\'s Marvelous Mysteries',
...
<?php
// fr
return array(
'applicationName' => 'Les merveilleux mystères de Matt',
...
Next, you need a library to read these translations and return the actual translation text to you. This file could simply be a collection of helper functions or a full-blown OOP system.
For simplicity, here is a collection of helper methods that get the job done. It does not implement parameterized substitution and you can add that feature relatively easily by making t() accept a 2nd argument, but that is a whole different topic for another time.
The main method in here is t(). It is very simple and accepts a single translation key. Ex. applicationName or greeting.
Firstly, it tries to determine which language to use. It does this in a sequence of priority: URL, session, browser, fallback.
It first tries to get the language/locale from the URL by looking for
a query string parameter named lang. If you think about it, that makes sense because a user intends to switch their language by clicking a
link that says "English" or "French".
If it doesn't find one in the URL it then moves on to check for one
in the session. Again, if it finds it there it uses it.
If it finds it neither in the URL nor the session, then it checks the
browser/headers from the request.
Finally, if it isn't found in any of those 3 locations then it falls
back to the default language as specified in $defaultLanguage.
Once a language has been found, it puts it into the session so the next request doesn't need to go through all that again. It also loads the appropriate messages.php file based on the discovered language.
Finally, once the language has been found and right file has been loaded into memory it searches for the given $key and returns the appropriate translation. If the $key is not found then it simply returns the given $key which will show up in your views so you know something went horribly wrong and you need to start debugging.
<?php
/**
* Performs the actual translation based on the given key. This is the method that is used
* in the actual views to translate a message.
*
* #param $key
* #return mixed
* #throws Exception
*/
function t($key)
{
$language = getLanguage();
$messages = require "{$_SERVER['DOCUMENT_ROOT']}/locale/{$language}/messages.php";
return (array_key_exists($key, $messages))
? $messages[$key]
: $key;
}
/**
* Returns the language as defined by either the URL, session, or browser setting.
* If a language could not be determined, or is not in a list of supported languages, the default
* language passed in to this method will be returned.
*
* #param string $defaultLanguage
* #return string
*/
function getLanguage($defaultLanguage = 'en')
{
$language = null;
if (isset($_GET['lang'])) {
$language = $_GET['lang'];
} elseif (isset($_SESSION['LANG'])) {
$language = $_SESSION['LANG'];
} else {
$language = getLanguageFromBrowser($defaultLanguage);
}
// If the language given to us is not in our list of supported languages, use the default language.
if (!isset($language) || !in_array($language, getSupportedLanguages())) {
$language = $defaultLanguage;
}
// Store the current language to the session for future use.
$_SESSION['LANG'] = $language;
return $language;
}
/**
* Returns the language that the client's browser is set to use. If we're unable to
* determine a language from the browser this will return the default language passed
* in.
*
* #param string $defaultLanguage
* #return int|string
*/
function getLanguageFromBrowser($defaultLanguage = 'en')
{
$languages = [];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) {
// create a list like "en" => 0.8
$languages = array_combine($lang_parse[1], $lang_parse[4]);
// set default to 1 for any without q factor
foreach ($languages as $lang => $val) {
if ($val === '') $languages[$lang] = 1;
}
// sort list based on value
arsort($languages, SORT_NUMERIC);
}
}
$supportedLanguages = getSupportedLanguages();
foreach ($languages as $locale => $weighting) {
// We're dealing with locale: Ex. en-US
if (preg_match("/[a-z]{2}-[A-Z]{2}/", $locale)) {
$browserLanguage = substr($locale, 0, 2);
} else {
// Probably dealing with a language: Ex. en
$browserLanguage = $locale;
}
if (in_array($browserLanguage, $supportedLanguages)) {
return $browserLanguage;
}
}
return $defaultLanguage;
}
/**
* Returns an array of languages this web application supports.
*
* #return array
*/
function getSupportedLanguages()
{
return [
'en',
'fr'
];
}
To use it, save these methods into a file called translator.php and then include that file in every page you want to use translations.
Sample:
<?php
session_start();
require_once('translator.php');
// Output your language switcheroo-gadget
if (getLanguage() === 'en') {
echo 'French';
} else {
echo 'English';
}
// Your code... blah blah
// Ahh.. Finally, a translation!
echo '<h1>' . t('applicationName') . '</h1>';
Edit
The last thing I will say is that there is a whole world out there of localization, internationalization (often abbreviated as i18n) which you can learn about.
In my example, I simplistically called it language but often people referer to it as locale but that has a different meaning and syntax. For example, there is a difference between en_CA and en_US and en_GB; all of which are English but have regional differences.
echo t('salutation');
So I started using this little library for creating a RESTful PHP server, right here.
In the code, I noticed that it appears that the comments are actually significant, in other words, if I change the comments, it actually changes the behavior of the code. Is this normal practice? I've never seen this used before and it seems weird to me to not ignore comments.
class TestController
{
/**
* Returns a JSON string object to the browser when hitting the root of the domain
*
* #url GET /
*/
public function test()
{
return "Hello World";
}
/**
* Logs in a user with the given username and password POSTed. Though true
* REST doesn't believe in sessions, it is often desirable for an AJAX server.
*
* #url POST /login
*/
public function login()
{
$username = $_POST['username'];
$password = $_POST['password']; //#todo remove since it is not needed anywhere
return array("success" => "Logged in " . $username);
}
/**
* Gets the user by id or current user
*
* #url GET /users/$id
* #url GET /users/current
*/
public function getUser($id = null)
{
// if ($id) {
// $user = User::load($id); // possible user loading method
// } else {
// $user = $_SESSION['user'];
// }
return array("id" => $id, "name" => null); // serializes object into JSON
}
Basically, the #url blocks actually define what request types to which URLs call the function below them. What is the scope of this, does it have to be the #lines right above the function? Is this standard PHP practice?
It is PHP Doc. See https://phpdoc.org/docs/latest/guides/docblocks.html and specifically https://phpdoc.org/docs/latest/guides/docblocks.html#tags
A tag always starts on a new line with an at-sign (#) followed by the name of the tag. Between the start of the line and the tag’s name (including at-sign) there may be one or more spaces or tabs.
Erm... Yes and No!
No, in the sense that it's not a normal PHP feature. In PHP, a comment is a comment and PHP makes no attempt to parse its content.
Yes in the sense that because PHP won't parse the comment, developers sometimes use it as a place to store data for their libraries. The Symfony framework is a good example.
In this case, the library you installed is parsing the comments in the class RestServer.php itself. You can read the class yourself, although there's some pretty hardcore PHP and Regex in there.
In a restful API for fruit, the request assumes something like this:
api/fruit
api/fruit?limit=100
api/fruit/1
api/fruit?color=red
I think there must be a standard for functions that do the work. For instance, something may easily translate to fruit.class.php.
fruit.class.php
function get ($id, $params, $limit) {
// query, etc.
}
So for my above examples, the code would look like
api/fruit
$fruit = $oFruit->get();
api/fruit?limit=100
$fruit = $oFruit->get(NULL, NULL, 100);
api/fruit/1
$fruit = $oFruit->get(1);
api/fruit?color=red
$fruit = $oFruit->get(NULL, array('color' => 'red'));
Is there an industry standard like this or are API/database functions always a mess? I’d really like to standardize my functions.
No, there is no standard as others have already answered...however, in answer to your issue about creating too many methods...I usually only have a single search() method that returns 1 or more results based on my search criteria. I usually create a "search object" that contains my where conditions in an OOP fashion that can then be parsed by the data layer...but that's probably more than you want to get into...many people would build their DQL for their data-layer, etc. There are a lot of ways to avoid getById, getByColor, getByTexture, getByColorAndTexture. If you start seeing lots of methods to cover every single possible combination of search, then you're probably doing it wrong.
As to rest method naming...ZF2 is not the answer, but it's the one I"m currently using on my project at work, and its methods are laid out like so (please note, this is HORRIBLE, dangerous code...open to SQL injection...just doing it for example):
// for GET URL: /api/fruit?color=red
public function getList() {
$where = array();
if( isset($_GET['color']) ) {
$where[] = "color='{$_GET['color']}'";
}
if( isset($_GET['texture']) ) {
$where[] = "texture='{$_GET['texture']}'";
}
return search( implode(' AND ',$where) );
}
// for GET URL: /api/fruit/3
public function get( $id ) {
return getById( $id );
}
// for POST URL /api/fruit
public function create( $postArray ) {
$fruit = new Fruit();
$fruit->color = $postArray['color'];
save($fruit);
}
// for PUT URL /api/fruit/3
public function update( $id, $putArray ) {
$fruit = getById($id);
$fruit->color = $putArray['color'];
save($fruit);
}
// for DELETE /api/fruit/3
public function delete( $id ) {
$fruit = getById($id);
delete($fruit);
}
Prelude
There isn't really a standard or convention for how urls should look, that cover (nearly) all cases.
The only standard I can think of is HATEOAS (Hypermedia as the Engine of Application State), which basically states that a client should derive the url's it can use from previous requests. This means that what the urls are isn't really important (but how the client can discover them is).
REST is kind of a hype nowadays, and it's often not understood what it actually is. I suggest your read Roy Fielding's dissertation on Architectural Styles and the Design of Network-based Software Architectures, especially chapter 5.
Richardson Maturity Model is also a good read.
PS: HAL (Hypertext Application Language) is a standard (among others) for implementing HATEOAS.
Interface
Because there is no standard on urls, there is also no standard for interfaces on that subject. It highly depends on your requirements, your taste, and perhaps what framework you're building the application with.
David Sadowski has made a nice list of libraries and frameworks that can help you develop RESTfull applications. I suggest you take a look at a couple of them, to see if and how they solve the problems you encounter. You should be able to get some idea's from them.
Also read the references I made in the prelude, as it will give you good insight on do's and don'ts of building real RESTfull applications.
PS: Sorry for not giving you a straightforward definitive answer! (I don't think one really exists.)
You are talking about get filters. I prefer magento like filters
There are no convention about how your internal code should look and not so many php frameworks support such functionality as get filters out of the box. You can have a look on magento rest api realization.
You can use functions calls like $model->get($where, $order, $limit)
but you should
define resourse properties
map resource properties to DB result fields
define which filters resource supports
check for filters, remove unsupported and build corresponding $where, $order, $limit
The open source library phprs may meet your needs.
With phprs, you can implement fruit.class like this:
/**
* #path("/api/fruit")
*/
class Fruit{
/**
* #route({"GET","/"})
* #param({"color","$._GET.color"})
* #param({"limit","$._GET.limit"})
*/
function getFruits($color,$limit){
return $oFruit->get(NULL, array('color' =>$color),$limit);
}
/**
* #route({"GET","/*"})
* #param({"id","$.path[2]"})
*/
function getFruitById($id){
return $oFruit->get($id);
}
}
The situation:
My application contains several modules, each of which should be as much self-contained as possible.
On each request, the application should parse a file. Based on the contents of the file, some database entities should be created, updated or removed.
Current approach:
I register a front controller plugin in one of my module bootstraps which takes care of the above.
The problem:
Other modules should be able to perform some routines based on the database entities that are modified in the file parse routine described above; how can I register a front controller plugin that executes after this is done?
Zend_Controller_Plugin objects are executed in LIFO order. Should I take another approach?
Note: I do realize that registerPlugin() takes a second $stackIndex argument, but since there is no way of knowing the current position of the stack, this is really is not a very clean way to solve the problem.
There is a way to know what stack indices have been used already. I recently wrote the following method for just such a case:
/**
*
* Returns the lowest free Zend_Controller_Plugin stack index above $minimalIndex
* #param int $minimalIndex
*
* #return int $lowestFreeIndex | $minimalIndex
*/
protected function getLowestFreeStackIndex($minimalIndex = 101)
{
$plugins = Zend_Controller_Front::getInstance()->getPlugins();
$usedIndices = array();
foreach ($plugins as $stackIndex => $plugin)
{
$usedIndices[$stackIndex] = $plugin;
}
krsort($usedIndices);
$highestUsedIndex = key($usedIndices);
if ($highestUsedIndex < $minimalIndex)
{
return $minimalIndex;
}
$lowestFreeIndex = $highestUsedIndex + 1;
return $lowestFreeIndex;
}
Basically, what you're asking for is the part Zend_Controller_Front::getInstance()->getPlugins(); With that you can do whatever you want, the array contains all the used stack indices as keys.
The function starts returning stack indices from 101 because the Zend Framework error controller plugin uses 100 and I need to register mine with higher indices. That's of course a bit of a magic number, but even the Zend Framework tutorials/manuals don't have a better solution for the 101 stack index problem. A class constant would make it a bit cleaner/more readable.
I'm a magento programmer and I've been loosing several long minutes to figure out why a property on an object was not saved in the database.
Let's explain, here are 3 pieces of code that I would expect to do the same thing :
First code
$order = Mage::getModel('sales/order')->load(1873);
$myInfo = 'important piece of information';
$order->getPayment()->setAdditionalInformation('my_info',$myInfo);
$order->getPayment()->save(); //No information in the database is saved
No value saved in database.
Second code
$order = Mage::getModel('sales/order')->load(1873);
$myInfo = 'important piece of information';
$payment = $order->getPayment();
$payment->setAdditionalInformation('my_info',$myInfo);
$payment->save(); //No information in the database is saved
No value saved in database.
Third code
$order = Mage::getModel('sales/order')->load(1873);
$myInfo = 'important piece of information';
$order->getPayment()->setAdditionalInformation('my_info',$myInfo)->save(); //YEAHHH ! It works ! I now have that in my database.
Finally, I got it !
The code from setAdditionalInformation
/**
* Additional information setter
* Updates data inside the 'additional_information' array
* or all 'additional_information' if key is data array
*
* #param string|array $key
* #param mixed $value
* #return Mage_Payment_Model_Info
* #throws Mage_Core_Exception
*/
public function setAdditionalInformation($key, $value = null)
{
if (is_object($value)) {
Mage::throwException(Mage::helper('sales')->__('Payment disallow storing objects.'));
}
$this->_initAdditionalInformation();
if (is_array($key) && is_null($value)) {
$this->_additionalInformation = $key;
} else {
$this->_additionalInformation[$key] = $value;
}
return $this->setData('additional_information', $this->_additionalInformation);
}
note: The final setData() always returns $this
Question, Why ?
I think I've forgot some specificities about the way PHP works, especially for the first code. I would understand that it doesn't work because of some memory stuff with PHP.
But the two other pieces of code, why doesn't it work ?
Thanks,
Hugues.
These pieces of code are identical from Magento view - you didn't forget anything about how PHP works. With default Magento installation all 3 snippets must produce same results.
If the results of those code blocks are different, then you should:
a) turn off all custom extensions you use and try your code blocks without them - maybe some of extensions modify the default behavior of Order or Payment models.
b) check that your code snippets are really same as presented in this question - maybe there were other code lines that you thought of as non-important and didn't include in this question
c) check that you update view in your MySQL client after executing each code snippet - maybe you see just some old information in payment table
c2) check that you don't use replicated MySQL severs - maybe you update information on master DB, but sees payment table from slave DB, where these changes haven't yet been synced to
d) check that no other code executes after yours - maybe some other model or controller modifies additional_information and so deletes all your changes. Try to insert 'exit' just after your code so you'll be sure about it.
Not a Magento user, but it looks as if each method is returning an object which is required by the next method in the chain.
If you call each method individually, the object they create or modify won't contain any changes made by the previous method calls. By chaining the method calls, each one picks up the changes made by the previous call.