Making global helper functions in a php mvc - php

I have been following a tutorial to develop a mvc using php.
In the framework all the configuration settings are stored in a Config.php(Config class) under App namespace. The properties are constant so If I want to access a property, I have to use the namespace first anywhere I want to access it. So now I can access it like below
Config::DB_NAME OR Config::DB_HOST
So, Is there any way I can define a global helper function say config() using which I can access those values?
Say, If I want to access DB_NAME , I would typeconfig('DB_NAME')
Also, If I want to define some other global helper functions, How can make it?
To make the question more clear. Take Laravel's hleper functions. There is a helper function config() which returns a value from various configuration files. Like config('app.name') will return the APP Name.
These helper functions can be accessed from anywhere, Vies, Models, and Controllers.
Here is what I think can be done.
I don't want to group the functions in a class, that way I have to use namespaces.
Maybe, I can use traits to group the functions.
But How to make it available in each class. I can use use Helpers, but then
How would I access the function from a view file which doesn't have a classs?
Any, idea or suggestion would be helpful.
Config.php
<?php
namespace App;
class Config
{
const DB_HOST = 'localhost';
const DB_NAME = 'mvc';
const DB_USER = 'root';
const DB_PASSWORD = '';
const SHOW_ERRORS = false;
}
The project is currently on github, If anyone want a look of the files structure can see here
Edit
Maybe I can have config.php file which just returns an associative array.
Yet I need a helper function to get the values from it. The file would be like this.
<?php
return [
'DB_HOST'=>'localhost',
'DB_NAME'=>'mvc',
'DB_USERNAME'=>'root',
'DB_PASSWORD' =>'',
];

Thanks to you all for your help and support.
I, finally, found a solution. It's working and good for now. Maybe there is some other way but It will work for me for now.
Here are the steps that I followed to solve it.
Defined all my configuration setting in a separate file called Configs.php This file just returns an associative array.
Configs.php
<?php
return [
'DB_HOST' => 'localhost',
'DB_NAME' => 'mvc',
'DB_USER' => 'root',
'DB_PASSWORD' => '',
'SHOW_ERRORS' => false
];
I used composer to autoload my Helpers.php file. I didn't give it any namespace so it is globally loaded.
Helpers.php (OLD)
<?php
if(!function_exists('config')) {
function config($key)
{
$config_array = include 'Configs.php';
if (array_key_exists($key,$config_array))
{
return $config_array[$key];
}
}
}
Helpers.php (Updated - Suggested by Magnus Erikkson)
<?php
if (!function_exists('config')) {
function config($key, $fallback = null)
{
static $config;
if (is_null($config)) {
$config = include 'Configs.php';
}
return array_key_exists($key, $config)
? $config[$key]
: $fallback;
}
}
Both files Config.php and Helpers.php are under App directory
and here is composer.json
{
"require": {
"twig/twig": "^2.0",
"phpmailer/phpmailer": "^6.0"
},
"autoload": {
"files": [
"app/helpers.php"
],
"psr-4": {
"Core\\":"Core/",
"App\\":"App/"
}
}
}
Now, I can access it in my controller like config('DB_NAME') and it is working!

Related

Cannot instantiate Object dynamically in PHP - Laravel [duplicate]

The autoloader works when I use it in index.php, but when I create an object within index.php and this object has to create other objects (which are all in the same namespace), it throws the error Uncaught Error: Class 'xxx' not found in (...).
My composer.json looks like this:
{
"autoload": {
"psr-4": {
"pizzaCase\\": "src",
"Connection\\": "src/Connection/",
"Elements\\": "src/Elements/"
}
},
"require": {
"cboden/ratchet": "^0.4"
}
}
My index.php looks like this:
<?php
require_once __DIR__. '/vendor/autoload.php';
require_once __DIR__."/src/config.php";
use Connection\Database;
use Elements\Form;
use Elements\FormElement;
use Elements\FormElementRadio;
// Database::init();
$form = new Form();
$data["options"] = "soemthing, something else";
$form->addElement("", "pizza", "", "Choose pizza", "radio", $data);
?>
In the addElement method I then create an object which is also within the src/Elements/ namespace, but it throws the error mentioned above.
The body of my addElement method looks like this:
<?php
namespace Elements;
class Form
{
public static $leftSize = 3;
protected $elements = [];
public function addElement($table, $name, $value, $label=false, $type = false, $data = false)
{
$type = ucfirst($type);
$class = "FormElement{$type}";
//FAILS HERE
if(class_exists($class))
{
//CLASS EXISTS, CREATE OBJECT FROM RESPECTIVE CLASS
$form = new $class($table, $name, $value, $label, $type, $data);
$this->elements[$name] = $form;
}
}
}
What am I doing wrong (or missing)? How come the autoloader can autoload it from index.php, but the object I create cannot create other objects without autoloader failing?
The difference is not to do with where the code is being run; the difference is that the failing code is trying to choose which class to load dynamically.
In PHP, namespaces are essentially a compile-time feature: before any of your code is run, the compiler looks at all references to class names which don't start with \, and prefixes them with the current namespace, or according to rules you've specified with use statements. When the code runs, the current namespace, and use statements, aren't visible at all.
When you specify a class name dynamically, the compiler just sees a string, not a class name, so leaves it alone. Then when the code runs, the class name looked up is assumed to be fully specified, not relative to the current namespace or use statements.
So the solution is simple - specify the full namespace when creating the dynamic class name:
$class = "Elements\FormElement{$type}";
You can also use the magic constant __NAMESPACE__ to have the compiler substitute the current namespace name for you (obviously, this still won't account for any use statements):
$class = __NAMESPACE__ . "\FormElement{$type}";
Alternatively, if you have a specific set of classes you are choosing between, you can use the ::class syntax to generate a string at compile time, based on the current namespace and any use statements in effect:
$mapTypeToClassName = [
'Radio' => FormElementRadio::class, // expands to 'Elements\FormElementRadio'
'Select' => FormElementSelect::class,
// etc
];
$class = $mapTypeToClassName[$type];
It could be because you’re having multiple namespaces for the src directory.
Usually you would just create a namespace for src like this
“psr-4": {
"PizzaCase\\": "src"
}
And then just use PizzaCase\Elements and PizzaCase\Connections as namespaces

Extend matomo/piwik device detector class to add new client parser (namespace issue)

I am trying to add a custom user agent for parsing a custom mobile application user agent along with the existing parsers. I tried following the issue over here: https://github.com/matomo-org/device-detector/issues/5931 but could not do it properly.
Directory Structure
UAParserService
|
|_ composer.json
|_ Vendor
|_ index.php
|_ custom_apps.yml
|_ CustomAppParser.php
|_ DeviceDetector.php
index.php
<?php
require_once "vendor/autoload.php";
use DeviceDetector\DeviceDetector;
use DeviceDetector\Parser\Client\CustomAppParser;
$userAgent = "MyApp/1.0.0 (Linux; Android 9; ONEPLUS A6010)"; // Android App
$dd = new DeviceDetector($userAgent);
$parser = new CustomAppParser();
$dd -> addClientParser($parser);
$dd -> parse();
// Check if user agent is a bot
$isBot = $dd -> isBot();
if($isBot) {
echo json_encode(["is_bot" => $isBot]);
}
else {
$clientInfo = $dd->getClient();
$osInfo = $dd->getOs();
$device = $dd->getDeviceName();
$brand = $dd->getBrandName();
$model = $dd->getModel();
echo json_encode([
"is_bot" => $isBot,
"client_info" => $clientInfo,
"os_info" => $osInfo,
"device_type" => $device,
"device_brand" => $brand,
"device_model" => $model,
], JSON_PRETTY_PRINT);
}
DeviceDetector.php
<?php
namespace UAParserService\DeviceDetector;
use function array_pop;
use function array_unshift;
class DeviceDetector extends \DeviceDetector\DeviceDetector
{
public function addClientParser($parser){
parent::addClientParser($parser);
$item = array_pop($this -> clientParsers);
array_unshift($this -> clientParsers, $item);
}
}
CustomAppParser.php
<?php
namespace DeviceDetector\Parser\Client;
class CustomAppParser extends ClientParserAbstract
{
protected $fixtureFile = "custom_apps.yml";
protected $parserName = "mobile app";
protected function getRegexesDirectory()
{
return dirname(__DIR__);
}
}
composer.json
{
"require": {
"piwik/device-detector": "3.11.7",
"ext-json": "*"
}
}
I am extremely unfriendly with namespaces so I might be doing it totally wrong.
I also do get some errors:
( ! ) Fatal error: Uncaught Error: Class 'DeviceDetector\Parser\Client\FurryAppParser' not found in F:\web projects\project1\UAParserService\index.php on line 17
( ! ) Error: Class 'DeviceDetector\Parser\Client\CustomAppParser' not found in F:\web projects\project1\UAParserService\index.php on line 17
First of all, you should consider to put your own classes into dedicated folder, like src.
After that you can add
"autoload": {
"psr-4": {
"UAParserService\\": "src/"
}
}
to your composer.json and update autoloader with composer dump-autoload command.
At this point you will have setup for use of your namespace.
Things to note:
all your classes in src folder must start its namespace with UAParserService\
Filename must match classname (case sensitive)
Folder structure must match namespace's elements (case sensitive)
UAParserService element will be stripped while searching folders for your class
For example, if DeviceDetector class have namespace UAParserService\DeviceDetector; it must be placed into scr/DeviceDetector/DeviceDetector.php file. The same goes for CustomAppParser class.
Also, if you want to use your own DeviceDetector in index.php you should update use statement touse UAParserService\DeviceDetector\DeviceDetector;
Side note: please don't use spaces around -> operator for methods calls ;). At least either use it everywhere, either don't use it at all...
P.S.: please consider to rename your question to reflect real issue it solves ;). I mean we are solving here namespace issue, not extending issue.

PSR4 not working?

Class not found, apparently. I've tried various things but nothing works.
Composer:
"autoload": {
"psr-4": {
"App\\": "application/"
}
}
File structure:
https://i.imgur.com/h9wOEqI.png
<?php
namespace App\Library\Classes;
defined('START') or exit('We couldn\'t process your request right now.');
class Application
{
private static $libraries = array();
public static function get($library) {
if (isset(self::$libraries[$library]) && isset(self::$classes[$library])) {
return self::$libraries[$library];
}
$fixedLibrary = str_replace('.', '/', $library);
$file = ROOT . '/application/library/classes/' . strtolower($fixedLibrary) . '.php';
self::$libraries[$library] = $library;
$declared = get_declared_classes();
$workingClass = end($declared);
self::$libraries[$library] = new $workingClass();
return self::$libraries[$library];
}
}
?>
Error is on this line:
Application::get('test')->test();
Yet, if I change it to this, it works:
include ROOT . '/application/Library/Application.php';
App\Library\Classes\Application::get('test')->test();
The PSR4 is not built-in part or PHP, you need an implementation of autoloader to use this standard such as provided by the Composer.
When you install or update depedencies, composer generates the relevant code of autoloading, but you can directly update it by the command dump-autoload, as #jibsteroos said. Next you should explicitly include the file vendor/autoload.php in the entry point of your application.
Also, error message says about class Application, but you should add the use statement at first:
use App\Library\Classes\Application;
Application::get('test')->test();
Or use the fully qualified class name (class name with namespace prefix):
\App\Library\Classes\Application::get('test')->test();

Change the name of DB Facade

I'm using Lumen with an existing PHP application.
There's a conflict in the global namespace. The existing app also have a DB class in the global namespace which is conflicting with the Lumen's DB class.
// in vendor/laravel/lument-framework/src/Appliction.php
public function withFacades()
{
Facade::setFacadeApplication($this);
if (! static::$aliasesRegistered) {
static::$aliasesRegistered = true;
class_alias('Illuminate\Support\Facades\App', 'App');
class_alias('Illuminate\Support\Facades\Auth', 'Auth');
class_alias('Illuminate\Support\Facades\Bus', 'Bus');
class_alias('Illuminate\Support\Facades\DB', 'DB');
...
}
}
If I change class_alias('Illuminate\Support\Facades\DB', 'LumenDB'); solves the problem for me, but I don't want to edit code in the vendor folder.
Is there anyway I can change it programmatically?
Alright. I think I have a workaround for now.
In the bootstrap/app.php uncomment or remove this lines
// $app->withFacades();
and replace with.
class_alias('Illuminate\Support\Facades\App', 'App');
class_alias('Illuminate\Support\Facades\Auth', 'Auth');
class_alias('Illuminate\Support\Facades\Bus', 'Bus');
class_alias('Illuminate\Support\Facades\DB', 'LumenDB');
...
...
class_alias('Illuminate\Support\Facades\Validator', 'Validator');
So we will register the facades manually instead of calling the withFacades() function.
The correct method in newer versions of Laravel/Lumen is to pass an array of "user aliases" as the second argument to the $app->withFacades() method.
$app->withFacades(
true, // $aliases parameter set to true (default)
[
'Illuminate\Support\Facades\DB' => 'LumenDB',
] // array of $userAliases
);

Where do I put Laravel 4 helper functions that can display flash messages?

I've written a simple display_messages() function that will search Session::get('errors') for flash data and echo it to the screen.
Where do I put this function? In Codeigniter, you had a helpers folder where you could stick all your little global helper methods.
As Usman suggested,
create a file /application/libraries/demo.php
define a class Demo() { inside it
call the function like so: {{ Demo::display() }}
Works because libraries and models are autoloaded in start.php line 76. I believe that filenames must match Classnames (note capital).
<?php
class Demo {
public static function display() {
if( !$message = Session::get('errors'))
$message = 'No Errors';
echo "<pre>print_r($message)</pre>";
}
}
Can't quite figure out why I had a problem using the classname Common, there may be a conflict (you could define a namespace if this were important)...
Create a folder helpers within your app folder and create a file application_helper.php. With such code:
// app/helpers/application_helper.php
function display_messages()
{
exit('Yes');
}
Then open your composer.json file in root. autoload app/helpers/application_helper.php with composer files.
"autoload": {
....
"files": [
"app/helpers/application_helper.php"
]
Done, you can now call display_messages().
Some autoloaders may require you to run composer dump command for the first time.
Thank you memeLab provided a very useful answer which helped me a lot. I just wanted to expand on his answer as the "libraries" folder was not an auto load directory, at least not in the release/current version of L4 I am using. Also the start.php seems to have been expanded to be the start folder with global.php, local.php, and artisan.php.
So to use your own classes for separate libraries or helpers with the L4 lazy auto loader you just have to include whichever folder you want to store these in to the global.php. For example I added a libraries folder to the directory list.
ClassLoader::addDirectories(array(
app_path().'/commands',
app_path().'/controllers',
app_path().'/models',
app_path().'/database/seeds',
// this a custom path
app_path().'/libraries',
));
Then whatever class you define in that folder as classname.php can be called via CLASSNAME::methodName($someVar); in your controllers.
class CLASSNAME {
public static function methodName($someVar=NULL) {
// whatever you want to do...
return $message;
}
}
So in this fashion you can create a helper class and define different methods to use throughout your controllers. Also be careful defining regular functions outside of your Class in this manner will cause you grief because they will not work (because the class is not always loaded). (for example someFunctionName($someVar); instead of CLASSNAME::methodName($someVar);) If you want to create functions in this manner you would need to make sure the is loaded, however I will not elaborate on this because it is better practice to use the lazy loader classes for such things so you only load the classes you really need.
Thanks again to memeLab and Usman, I would not have gotten as far without their answers. :)
For loading Classes:
Create app/libraries/class/Message.php, and add class in file
class Message {
public static function display() {
}
}
Add "app/libraries/class" to composer.json
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php",
"app/libraries/class"
]
},
Finally run composer dump-autoload in command line.
You can access that by Message::display()
For loading plain non-object php Functions:
Create app/libraries/function/display_messages.php, and add function in file
function display_messages() {
}
add one line in start/global.php
require app_path().'/libraries/function/display_messages.php';
You can access that just by display_messages()
Add this in app/start/global.php
require app_path().'/config/autoload.php';
require app_path().'/start/loader.php';
App::instance('loader',new loader($autoload));
create a new file loader.php in app/start and add:
class loader{
private $helpers = array();
public $autoload = array(
'helpers' => array()
);
function __construct($autoload = array()) {
if (!empty($autoload))
$this->autoload = $autoload;
foreach ($this->autoload as $key => $value)
{
$function = strtolower($key);
$this->$function($value);
}
}
function helpers($helpers=array())
{
if (!is_array($helpers))
$helpers = explode(",",$helpers);
foreach ($helpers as $key => $value) {
$this->helper($value);
}
}
function helper($helper = '',$path = '/')
{
$folder = app_path().'/helpers'.$path;
if (file_exists($folder.$helper.'.php') && !in_array($helper, $this->helpers)){
$this->helpers[] = $helper;
require $folder.$helper.'.php';
}
else{
$segments = explode('/',$helper);
if (is_dir($folder.$segments[0])){
array_shift($segments);
$this->helper($segments,$path.$segments[0].'/');
}
}
}
}
create a new file autoload.php in app/config and add:
$autoload['helpers'] = array('functions'); // my autoload helpers!
create a new folder helpers in app/ , add your helper files. ( es. myhelper.php )
function myhelper()
{
echo 'helper';
}
in your controller add:
App::make('loader')->helper('myhelper');
myhelper();
In L3, I would normally create a application/libraries/helpers.php file, and require_once() it in my application/start.php. Similar to how L3 has a laravel/helpers.php file.
I'm assuming there is something similar you can do in L4.
EDIT: Just looking at the source, app/start/local.php seems like it might be the place.
I used this tutorial and i think is the easiest method: http://laravel-recipes.com/recipes/50/creating-a-helpers-file
First create the file app/helpers.php
Then either load it at the bottom of app\start\global.php as follows.
// at the bottom of the file
require app_path().'/helpers.php';
Or change your composer.json file and dump the autoloader.
{
"autoload": {
"files": [
"app/helpers.php"
]
}
}
$ composer dump-auto
then you can write your functions in helpers.php and call them from anywhere
function myfunction($result){
return $result;
}
open root_folder/vendor/laravel/framework/src/Illuminate/Support/helpers.php
and you can add your function
if ( ! function_exists('display_messages'))
{
function display_messages()
{
return ...
}
}

Categories