most
continuous-integration/drone/push Build is failing Details

issue_a
David D'ALMEIDA 1 year ago
parent 3e0d8f9def
commit fae5e35610

@ -1,45 +1,45 @@
@startuml @startuml
class User { class User {
name : string + name : string
} }
User --> "*" User: listAmis User "1" --> "*" User: friends
User --> "*" notification : lesNotif User "1" --> "*" Notification: notifications
User --> "*" ask : demandeDamis User "1" --> "*" Ask: friendRequests
class notification { class Notification {
text : string - text : string
} }
interface INotifier { interface INotifier {
notify : void + notify() : void
} }
INotifier --|> Observer INotifier --|> Observer
abstract UserManager {
currentUser : User
createUser (...data)
getUser(): User
deleteFriends(user1 : int, user2 :int) : void
addFriends(user:id): void
repondreDemandeAmis(idask : Ask,choice: bool)
getFriends(user: id)
}
class ask { abstract class UserManager {
fromUser : int - currentUser : User
toUser : int + deleteFriend(userId : int) : void
+ addFriend(userId : int) : void
+ respondToFriendRequest(requestId : int, choice : bool) : void
+ getFriends(userId : int) : User[]
} }
class Ask {
- fromUser : int
- toUser : int
}
ask --|> Subject Ask --|> Subject
abstract Subject { abstract class Subject {
+ attatch(o : Observer): void + attach(o : Observer) : void
+ detach(o : Observer): void + detach(o : Observer) : void
+ notify(): void + notify() : void
} }
Subject --> "*" Observer
Subject "1" --> "*" Observer
interface Observer { interface Observer {
update(): void + update() : void
} }
UserManager ..> User UserManager ..> User
@ -47,9 +47,9 @@ UserManager o-- IUserRepository
UserManager o-- INotifier UserManager o-- INotifier
interface IUserRepository { interface IUserRepository {
+ findByUsername(username:string): User + findByUsername(username : string) : User
+ addUser(user : User) : bool + addUser(user : User) : bool
} }
IUserRepository ..> User IUserRepository ..> User
@enduml @enduml

@ -4,10 +4,17 @@ boundary View as v
control Controller as c control Controller as c
entity Model as m entity Model as m
m->c:RecevoirDemandeAmi(idUser) m-->c: pendingRequests: Request[]
c->v:AfficherDemandeAmi(idUser)
v->u:PageDemandeAmi(idUser) c-->v: DisplayPendingRequests(pendingRequests)
u-->v:RepondreDemandeAmi(idUser) v-->u: Show Friend Requests
v-->c:EnregistrerReponse(idUser)
c-->m:EnvoyerReponse(idUser) u->v: RespondToRequest(requestId, response)
v-->c: RecordResponse(requestId, response)
c->m: UpdateRequestStatus(requestId, response)
m-->c: updateStatus: success/failure
c-->v: NotifyUpdateResult(updateStatus)
v-->u: Show Response Result
@enduml @enduml

@ -4,17 +4,25 @@ boundary View as v
control Controller as c control Controller as c
entity Model as m entity Model as m
u->v:/Friend u->v: Request Friends Page
v->c:Get /Friends v->c: Get /Friends
c->m: getFriends(userId) c->m: getFriends(userId)
m-->c: :Friends:User[] alt successful retrieval
c-->v:renderView(listFriend) m-->c: friendsList: User[]
v-->u: else retrieval failed
u->v:clickDeleteFriend(idUser) m-->c: error
v->c:Post: deleteFriend(idUser) end
c-->v: renderView(friendsList)
v-->u: Display Friends
u->v: clickDeleteFriend(idUser)
v->c: Post: deleteFriend(idUser)
c->m: deleteFriend(idUser) c->m: deleteFriend(idUser)
m-->m:deleteFriend(idUser) alt successful deletion
m-->c: Friends:User[] m-->c: updatedFriendsList: User[]
c-->v:renderViewlistFriend(Friends:User[]) else deletion failed
v-->u: m-->c: error
end
c-->v: renderView(updatedFriendsList)
v-->u: Display Updated Friends
@enduml @enduml

@ -21,7 +21,8 @@
}, },
"require": { "require": {
"twig/twig": "^3.0", "twig/twig": "^3.0",
"vlucas/phpdotenv": "^5.5" "vlucas/phpdotenv": "^5.5",
"adriangibbons/php-fit-file-analysis": "^3.2.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "*" "phpunit/phpunit": "*"

714
Sources/composer.lock generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

@ -2,13 +2,17 @@
namespace Console; namespace Console;
use DateTime;
use Model\Athlete; use Model\Athlete;
use Model\Coach; use Model\Coach;
use Model\RelationshipRequest;
use Model\Role; use Model\Role;
use Model\Training;
use Model\User;
use Stub\StubData; use Stub\StubData;
use Manager\DataManager; use Manager\DataManager;
$model = new StubData(); // Couche d'accès au model $model = new Model(); // Couche d'accès au model
function clearScreen() function clearScreen()
{ {
system('clear || cls'); system('clear || cls');
@ -49,13 +53,8 @@ function displayProfileMenu()
echo "\n--- Profil ---\n"; echo "\n--- Profil ---\n";
echo "1. Informations de l'utilisateur\n"; echo "1. Informations de l'utilisateur\n";
echo "2. Historique d'activité\n"; echo "2. Historique d'activité\n";
echo "3. Voir les statistiques de condition physique Général"; echo "3. Liste d'amis\n";
echo "4. Liste d'amis\n"; echo "4. Importer des données (FIT/GPX/TCX)/Manuel\n";
echo "5. Paramètres de confidentialité et visibilité\n";
echo "6. Synchroniser un appareil\n";
// Importer des données (FIT/GPX/TCX)/Manuel
// Synchroniser l'appareil de fréquence cardiaque
// Synchroniser l'app mobile
echo "0. Retour au menu principal\n"; echo "0. Retour au menu principal\n";
echo "Choisissez une option: "; echo "Choisissez une option: ";
} }
@ -80,22 +79,30 @@ function displayCoachMenu()
echo "3. Analyses par athlète\n"; echo "3. Analyses par athlète\n";
echo "4. Gérer la liste de mes athlètes\n"; echo "4. Gérer la liste de mes athlètes\n";
// Gérer les athlètes (comprend : Ajouter un athlète, Supprimer un athlète, Consulter les statistiques d'un athlète) // Gérer les athlètes (comprend : Ajouter un athlète, Supprimer un athlète, Consulter les statistiques d'un athlète)
echo "5. Gérer la liste de mes exercices\n";
echo "0. Retour au menu principal\n"; echo "0. Retour au menu principal\n";
echo "Choisissez une option: "; echo "Choisissez une option: ";
} }
function displaySocialManagementMenu() function displaySocialManagementMenu() {
{
clearScreen(); clearScreen();
echo "\n--- Gestion sociale ---\n"; echo "\n--- Gestion sociale ---\n";
echo "1. Rechercher des utilisateurs\n"; echo "1. Rechercher des coach\n";
echo "2. Gérer la liste d'amis\n"; echo "2. Rechercher des athletes\n";
// Ajouter des amis echo "3. Gérer la liste d'amis\n";
// Supprimer des amis ...
echo "3. Options de partage\n"; echo "4. Options de partage\n";
echo "0. Retour au menu principal\n"; echo "0. Retour au menu principal\n";
echo "Choisissez une option: "; echo "Choisissez une option: ";
} }
function displaySubSocialManagementMenu(){
clearScreen();
echo "\n--- Gestion sociale Sub 3---\n";
echo " 3.1. Voir les demandes d'amitié\n";
echo " 3.2. Répondre à une demande d'amitié\n";
echo " 3.3. Ajouter un ami\n";
echo " 3.4. Supprimer un ami\n";
}
function displaySettingsMenu() function displaySettingsMenu()
{ {
@ -105,18 +112,48 @@ function displaySettingsMenu()
// Modifier le profil du athlète et coach // Modifier le profil du athlète et coach
echo "2. Personnaliser le profil public\n"; echo "2. Personnaliser le profil public\n";
echo "3. Configurer les alertes\n"; echo "3. Configurer les alertes\n";
echo "4. Supprimer mon compte";
echo "0. Retour au menu principal\n"; echo "0. Retour au menu principal\n";
echo "Choisissez une option: "; echo "Choisissez une option: ";
} }
function loginUser(DataManager $model)
function displayManagementArrayAthlete()
{
clearScreen();
echo "\n--- Menu Management Groupe Athlete ---\n";
echo "1. Ajouter un athlète\n";
echo "2. Supprimer un athlète\n";
echo "3. Voir tout les athlètes de la liste\n";
echo "0. Retour au menu du coach\n";
echo "Choisissez une option: ";
}
function displayManagementArrayTraining()
{
clearScreen();
echo "\n--- Menu Management des entrainements ---\n";
echo "1. Ajouter un entrainement\n";
echo "2. Supprimer un entrainement\n";
echo "3. Voir tout les entrainements de la liste\n";
echo "0. Retour au menu du coach\n";
echo "Choisissez une option: ";
}
/**
* Fonction permettant à un utilisateur de se connecter.
*
* @param Model $model La couche d'accès au modèle de données.
* @return bool Retourne true si l'utilisateur s'est connecté avec succès, sinon false.
*/
function loginUser(Model $model): bool
{ {
try { try {
echo "\nEntrez votre nom d'utilisateur: "; echo "\nEntrez votre nom email: ";
$username = trim(fgets(STDIN)); $emailUser = trim(fgets(STDIN));
echo "Entrez votre mot de passe: "; echo "Entrez votre mot de passe: ";
$password = trim(fgets(STDIN)); $password = trim(fgets(STDIN));
if ($model->userMgr->login($username, $password)) { if ($model->userMgr->login($emailUser, $password)) {
return true; return true;
} else { } else {
echo "Erreur de connexion. Essayez encore.\n"; echo "Erreur de connexion. Essayez encore.\n";
@ -131,57 +168,148 @@ function loginUser(DataManager $model)
} }
} }
function addFriend(DataManager $model){ function addFriend(Model $model) {
echo "\nEntrez le nom de la personne que vous recherchez : "; clearScreen();
echo "\nEntrez le nom d'utilisateur de la personne que vous recherchez : ";
$nom = trim(fgets(STDIN)); $nom = trim(fgets(STDIN));
$userList = searchUsersByName($name);
$i=1; $userList = $model->userMgr->searchUsersByName($nom);
foreach($u as $userList){ if (empty($userList)) {
echo $u->getNom()." ".$u->getPrenom()." : ".$i++."\n"; echo "Aucun utilisateur trouvé.\n";
return;
}
/** @var User $user */
foreach ($userList as $index => $user) {
echo ($index + 1) . ". " . $user->getNom() . " " . $user->getPrenom() . "\n";
} }
echo "\nEntrez le numéro de la personne que vous voulez ajouter ou 0 pour annuler";
echo "\nEntrez le numéro de la personne que vous voulez ajouter ou 0 pour annuler : ";
$choice = trim(fgets(STDIN)); $choice = trim(fgets(STDIN));
switch ($choice){
case '0':
echo "Ajout d'ami annulé";
break;
default : if ($choice === '0') {
$user = $userList[$choice-1]; echo "Ajout d'ami annulé.\n";
echo "Ajout de ".$user->getPrenom()." ".$user->getNom()."\n"; return;
if ($model->userMgr->addFriend()){
sleep(2);
echo "Notification envoyée\n";
} }
else echo "Problème a l'envoi de la notification, veuillez vérifier votre connexion ou réessayer plus tard\n";
break; $selectedIndex = intval($choice) - 1;
if (!isset($userList[$selectedIndex])) {
echo "Sélection invalide.\n";
return;
} }
$user = $userList[$selectedIndex];
echo "Ajout de " . $user->getPrenom() . " " . $user->getNom() . "\n";
if ($model->userMgr->addFriend($user->getUsername())) {
echo $userList[0]->getRole()->getUsersRequests()[0];
echo $userList[0]->getNotifications()[0]->getType();
sleep(2);
echo "Notification envoyée.\n";
} else {
echo "Problème à l'envoi de la notification, veuillez vérifier votre connexion ou réessayer plus tard.\n";
}
} }
function deleteFriend(DataManager $model, int $userId){
$model->userMgr->deleteFriend($userId); function deleteFriend(Model $model) {
echo "Ami supprimé\n"; echo "\nListe de vos amis :\n";
$friendList = $model->userMgr->getCurrentUser()->getRole()->getUsersList();
if (empty($friendList)) {
echo "Vous n'avez aucun ami à supprimer.\n";
return;
}
/** @var User $friend */
foreach ($friendList as $index => $friend) {
echo ($index + 1) . ". " . $friend->getNom() . " " . $friend->getPrenom() . "\n";
}
echo "\nEntrez le numéro de l'ami que vous voulez supprimer ou 0 pour annuler : ";
$choice = trim(fgets(STDIN));
if ($choice === '0') {
echo "Suppression d'ami annulée.\n";
return;
}
$selectedIndex = intval($choice) - 1;
if (!isset($friendList[$selectedIndex])) {
echo "Sélection invalide.\n";
return;
}
$friend = $friendList[$selectedIndex];
if ($model->userMgr->deleteFriend($friend->getId())) { // Supposition que deleteFriend utilise l'ID de l'ami
echo "Ami " . $friend->getPrenom() . " " . $friend->getNom() . " supprimé.\n";
} else {
echo "Problème lors de la suppression, veuillez réessayer plus tard.\n";
}
} }
function answerAdd(DataManager $model, User $user){ function answerAdd(Model $model) {
echo "Vous avez une demande d'ami de ".$user->getPrenom." ".$user->getNom."\n"; echo "\nListe des demandes d'ami en attente :\n";
while ($answer!='o' || $answer!='n'){
echo "Voulez vous accepter (o) ou refuser (n)\n"; $friendRequests = $model->userMgr->getCurrentUser()->getRole()->getUsersRequests();
$answer = trim(fgets(STDIN));
if($answer=='o'){ if (empty($friendRequests)) {
answer($model->userMgr, $user, "accept"); echo "Aucune demande d'ami en attente.\n";
echo "Demande acceptée"; return;
}
foreach ($friendRequests as $index => $request) {
echo ($index + 1) . ". Demande de : " . $request->getId() . "\n";
}
echo "\nEntrez le numéro de la demande à répondre ou 0 pour annuler : ";
$choice = trim(fgets(STDIN));
if ($choice === '0') {
echo "Aucune action effectuée.\n";
return;
}
$selectedIndex = intval($choice) - 1;
if (!isset($friendRequests[$selectedIndex])) {
echo "Sélection invalide.\n";
return;
}
/** @var RelationshipRequest $request */
$request = $friendRequests[$selectedIndex];
echo "Répondre à la demande de " . $request->getFromUser() . " " . $request->getToUser() . ". Accepter (oui) ou refuser (non) ? ";
$response = strtolower(trim(fgets(STDIN)));
if ($response === 'oui') {
if ($model->userMgr->respondToFriendRequest($request->getId(),true)) { // Supposition de la méthode acceptFriendRequest
echo $model->userMgr->getCurrentUser()->getRole()->getUsersList()[0];
echo "Demande d'ami acceptée.\n";
} else {
echo "Problème lors de l'acceptation de la demande, veuillez réessayer plus tard.\n";
} }
else if ($answer=='n'){ } elseif ($response === 'non') {
answer($model->userMgr, $user, "refuse"); if ($model->userMgr->respondToFriendRequest($request->getId(),false)) { // Supposition de la méthode rejectFriendRequest
echo "Demande refusée"; echo "Demande d'ami refusée.\n";
} else {
echo "Problème lors du refus de la demande, veuillez réessayer plus tard.\n";
} }
else echo "Réponse incorrect, veuillez réessayer\n"; } else {
echo "Réponse non reconnue. Aucune action effectuée.\n";
} }
} }
function registerUser(DataManager $model)
/**
* Fonction permettant à un utilisateur de s'inscrire.
*
* @param Model $model La couche d'accès au modèle de données.
* @return bool Retourne true si l'inscription a réussi, sinon false.
*/
function registerUser(Model $model)
{ {
try { try {
@ -191,6 +319,9 @@ function registerUser(DataManager $model)
echo "Entrez votre prénom: "; echo "Entrez votre prénom: ";
$prenom = trim(fgets(STDIN)); $prenom = trim(fgets(STDIN));
echo "Entrez votre username: ";
$username = trim(fgets(STDIN));
echo "Entrez votre adresse email: "; echo "Entrez votre adresse email: ";
$email = trim(fgets(STDIN)); $email = trim(fgets(STDIN));
@ -221,6 +352,7 @@ function registerUser(DataManager $model)
$registrationData = [ $registrationData = [
'nom' => $nom, 'nom' => $nom,
'prenom' => $prenom, 'prenom' => $prenom,
'username' => $username,
'email' => $email, 'email' => $email,
'sexe' => $sexe, 'sexe' => $sexe,
'taille' => $taille, 'taille' => $taille,
@ -244,15 +376,402 @@ function registerUser(DataManager $model)
} }
} }
function ArrayAthleteMenu(Model $model)
{
do {
displayManagementArrayAthlete();
$coachChoice = trim(fgets(STDIN));
switch ($coachChoice) {
case '1':
echo "Renseignez le surnom de l'utilisateur : ";
$username = trim(fgets(STDIN));
if($model->coachMgr->addUser($username)){
echo "Ajout avec succès !";
} else {
echo "Le user ne peut pas être ajouter en tant que athlete !";
}
sleep(2);
break;
case '2':
echo "Renseignez le surnom de l'utilisateur : ";
$username = trim(fgets(STDIN));
if($model->coachMgr->removeUser($username)){
echo "Suppression avec succès !";
} else {
echo "Pb suppression ou aucun utilisateur de ce nom !";
}
sleep(2);
break;
case '3':
$usersArray = $model->coachMgr->getUsersList();
if (!empty($usersArray)) {
foreach ($usersArray as $value) {
echo $value->__toString() . "\n";
}
} else {
echo "Aucun utilisateur dans la liste\n";
}
sleep(2);
break;
case '0':
return;
default :
echo "Option invalide. Veuillez réessayer.\n";
sleep(2);
break;
}
} while($coachChoice);
}
function ArrayTrainingMenu(Model $model)
{
do {
displayManagementArrayTraining();
$coachChoice = trim(fgets(STDIN));
switch ($coachChoice) {
case '1':
$existingTrainings = $model->coachMgr->getTrainingsList();
$lastTraining = end($existingTrainings);
$lastTrainingId = $lastTraining ? $lastTraining->getId() : 0;
$newTrainingId = $lastTrainingId + 1;
echo "L'ID de l'entraînement sera automatiquement défini sur : $newTrainingId\n";
$date = new DateTime();
echo "Renseignez la latitude de l'entraînement : ";
$latitude = trim(fgets(STDIN));
echo "Renseignez la longitude de l'entraînement : ";
$longitude = trim(fgets(STDIN));
echo "Renseignez la description de l'entraînement : ";
$description = trim(fgets(STDIN));
$training = new Training($newTrainingId, $date, $latitude, $longitude, $description, null);
if($model->coachMgr->addTraining($training)){
echo "Ajout avec succès !";
} else {
echo "Pb ajout !";
}
sleep(2);
break;
case '2':
echo "Renseignez l'id de l'entrainement : ";
$idTraining = trim(fgets(STDIN));
if($model->coachMgr->removeTraining($idTraining)){
echo "Suppression avec succès !";
} else {
echo "Pb suppression ou aucun entrainement de cet id !";
}
sleep(2);
break;
case '3':
$trainingArray = $model->coachMgr->getTrainingsList();
if (!empty($trainingArray)) {
foreach ($trainingArray as $value) {
echo $value->__toString() . "\n";
}
} else {
echo "Aucun entrainement dans la liste\n";
}
sleep(2);
break;
case '0':
return;
default :
echo "Option invalide. Veuillez réessayer.\n";
sleep(2);
break;
}
} while($coachChoice);
}
function ArrayFriendManagementMenu(Model $model) {
do {
displaySubSocialManagementMenu();
$userChoice = trim(fgets(STDIN));
switch ($userChoice) {
case '3.1': // Voir les demandes d'amitié
$friendRequests = $model->userMgr->getCurrentUser()->getRole()->getUsersRequests();
if (!empty($friendRequests)) {
foreach ($friendRequests as $request) {
echo $request->__toString() . "\n";
}
} else {
echo "Aucune demande d'amitié.\n";
}
sleep(2);
break;
case '3.2': // Répondre une demande d'amitié
answerAdd($model);
break;
case '3.3': // Ajouter un ami
addFriend($model);
sleep(2);
break;
case '3.4': // Supprimer un ami
deleteFriend($model);
sleep(2);
break;
case '0': // Retour au menu principal
return;
default:
echo "Option invalide. Veuillez réessayer.\n";
sleep(2);
break;
}
} while ($userChoice);
}
//function displayCoachMenu()
//{
// clearScreen();
// echo "\n--- Menu Coach ---\n";
// echo "1. Liste des athlètes\n";
// echo "2. Statistiques globales\n";
// echo "3. Analyses par athlète\n";
// echo "4. Gérer la liste de mes athlètes\n";
// // Gérer les athlètes (comprend : Ajouter un athlète, Supprimer un athlète, Consulter les statistiques d'un athlète)
// echo "5. Gérer la liste de mes exercices\n";
// echo "0. Retour au menu principal\n";
// echo "Choisissez une option: ";
//}
function CoachMenu(Model $model)
{
do {
displayCoachMenu();
$coachChoice = trim(fgets(STDIN));
switch ($coachChoice) {
case '1': // echo "1. Liste des athlètes\n";
$arrayUsers = $model->coachMgr->getUsersList();
if (isset($arrayUsers) && !empty($arrayUsers)) {
foreach ($arrayUsers as $value) {
echo $value->__toString() . "\n";
}
} else {
echo "Aucun utilisateur dans la liste\n";
}
sleep(2);
break;
// case '2': // echo "2. Statistiques globales\n";
// $arrayUsers = $model->coachMgr->getUsersList();
//
// if (!empty($arrayUsers)) {
// do {
// clearScreen();
// $cpt = 0;
// foreach ($arrayUsers as $value) {
// echo $cpt . " - " . $value->__toString() . "\n";
// $cpt = $cpt + 1;
// }
//
// echo "Renseignez le numéro de l'utilisateur choisi : ";
// $usernameNumber = trim(fgets(STDIN));
//
// // Vérifier si l'index saisi est valide
// if (isset($arrayUsers[$usernameNumber])) {
// $selectedUser = $arrayUsers[$usernameNumber];
// if (($arrayStats = $model->coachMgr->getStatistics($selectedUser))) {
// foreach ($arrayStats as $value) {
// echo $value->__toString() . "\n";
// }
// } else {
// echo "Pas de statistiques valides présentent !\n";
// }
// } else {
// echo "Numéro d'utilisateur non valide.\n";
// $cpt = 0;
// }
// } while($cpt == 0);
// } else {
// echo "Aucun utilisateur dans la liste.\n";
// }
// sleep(2);
// break;
case '3': // echo "3. Analyses par athlète\n";
$arrayUsers = $model->coachMgr->getUsersList();
if (!empty($arrayUsers)) {
do {
clearScreen();
$cpt = 0;
foreach ($arrayUsers as $value) {
echo $cpt . " - " . $value->__toString() . "\n";
$cpt = $cpt + 1;
}
echo "Renseignez le numéro de l'utilisateur choisi : ";
$usernameNumber = trim(fgets(STDIN));
// Vérifier si l'index saisi est valide
if (isset($arrayUsers[$usernameNumber])) {
$selectedUser = $arrayUsers[$usernameNumber];
if (($arrayStats = $model->coachMgr->getAnalyse($selectedUser))) {
foreach ($arrayStats as $value) {
echo $value->__toString() . "\n";
}
} else {
echo "Pas d'Analyses valides présentent !\n";
}
} else {
echo "Numéro d'utilisateur non valide.\n";
$cpt = 0;
}
} while($cpt == 0);
} else {
echo "Aucun utilisateur dans la liste.\n";
}
sleep(2);
break;
case '4': // echo "4. Gérer la liste de mes athlètes\n";
ArrayAthleteMenu($model);
break;
case '5': // echo "5. Gérer la liste de mes exercices\n";
ArrayTrainingMenu($model);
break;
case '0': // Quitter
return;
default:
echo "Option invalide. Veuillez réessayer.\n";
sleep(2);
break;
}
} while($coachChoice);
}
//function displaySocialManagementMenu()
//{
// clearScreen();
// echo "\n--- Gestion sociale ---\n";
// echo "1. Rechercher des coach\n";
// echo "2. Rechercher des athletes\n";
// echo "3. Gérer la liste d'amis\n";
// // Ajouter des amis
// // Supprimer des amis ...
// echo "4. Options de partage\n";
// echo "0. Retour au menu principal\n";
// echo "Choisissez une option: ";
//}
// TODO athlteMgr
function socialManagementMenu(Model $model) {
do {
displaySocialManagementMenu();
$managementChoice = trim(fgets(STDIN));
switch ($managementChoice) {
case '1':
echo "Renseignez le surnom du coach que vous recherchez : ";
$coachUsername = trim(fgets(STDIN));
$users = $model->userMgr->searchUsersByName($coachUsername);
if (!empty($users)) {
$foundCoaches = false;
foreach ($users as $user) {
if ($user->getRole() instanceof Coach) {
echo $user->__toString();
$foundCoaches = true;
}
}
if (!$foundCoaches) {
echo "Aucun coach de ce nom : $coachUsername\n";
}
} else {
echo "Aucun utilisateur trouvé avec le surnom : $coachUsername\n";
}
sleep(2);
break;
case '2':
echo "Renseignez le surnom de l'athlete que vous recherchez : ";
$athleteUsername = trim(fgets(STDIN));
$users = $model->userMgr->searchUsersByName($athleteUsername);
if (!empty($users)) {
$foundAthletes = false;
foreach ($users as $user) {
if ($user->getRole() instanceof Athlete) {
echo $user->__toString();
$foundAthletes = true;
}
}
if (!$foundAthletes) {
echo "Aucun athlete de ce nom : $athleteUsername\n";
}
} else {
echo "Aucun utilisateur trouvé avec le surnom : $athleteUsername\n";
}
sleep(2);
break;
case '3': // 3. Gérer la liste d'amis
ArrayFriendManagementMenu($model);
return;
case '0': // echo "0. Retour au menu principal\n";
return;
default:
echo "Option invalide. Veuillez réessayer.\n";
sleep(2);
break;
}
} while($managementChoice);
}
function profileMenu(Model $model)
{
do {
displayProfileMenu();
$athleteChoice = trim(fgets(STDIN));
switch ($athleteChoice) {
case '1':
echo $model->userMgr->getCurrentUser();
sleep(2);
break;
case '2':
if($model->userMgr->getCurrentUser()->getRole() instanceof Athlete) {
$activities = $model->athleteMgr->getActivities();
if($activities !== null && count($activities) > 0) {
foreach ($activities as $activity) {
echo $activity->__toString();
}
} else {
echo "No activities found";
}
} else {
echo "Vous etes pas un athléte";
}
sleep(2);
break;
case '3': // Liste d'amis
sleep(2);
break;
case '4': // Importer des données (FIT/GPX/TCX)
echo "Veuillez renseigner le chemin du fichier :\n";
$passFile = trim(fgets(STDIN));
echo "Veuillez renseigner le type d'activité :\n";
$typeActivity = trim(fgets(STDIN));
echo "Veuillez renseigner l'effort resenti (de 0 à 5) :\n";
do {
$effort = trim(fgets(STDIN));
} while ($effort < 0 || $effort > 5);
$isAddActivity = $model->activityMgr->uploadFile($typeActivity, $effort, $passFile);
echo $isAddActivity ? "Activité ajoutée avec succès" : "Erreur lors de l'ajout de l'activité";
case '0':
return;
default :
echo "Option invalide. Veuillez réessayer.\n";
sleep(2);
break;
}
} while($athleteChoice);
}
// const auth = getAuth();
// signInWithEmailAndPassword(auth, email, password)
// const auth = getAuth();
// signOut(auth).then(() => {
// // Sign-out successful.
// }).catch((error) => {
// // An error happened.
// });
while (true) { while (true) {
$loggedIn = false; $loggedIn = false;
@ -261,15 +780,22 @@ while (true) {
$choice = trim(fgets(STDIN)); $choice = trim(fgets(STDIN));
switch ($choice) { switch ($choice) {
case '1': // Se connecter case '1': // Se connecter
if (loginUser($model)) {
if($model->userMgr->login("john.doe@example.com", "password123"))
$loggedIn = true; $loggedIn = true;
}
/* if (loginUser($model)) {
$loggedIn = true;
}*/
break; break;
case '2': // S'inscrire case '2': // S'inscrire
if (registerUser($model)) { if($model->userMgr->login("bruce.lee@example.com", "hello321"))
$loggedIn = true; $loggedIn = true;
} // if (registerUser($model)) {
// $loggedIn = true;
// }
break; break;
case '0': // Quitter case '0': // Quitter
@ -292,57 +818,27 @@ while (true) {
break; break;
case '2': // Profil case '2': // Profil
while($profileChoice!=0){ profileMenu( $model);
displayProfileMenu();
$profileChoice = trim(fgets(STDIN));
switch($$profileChoice){
case '1':
echo "Afficher les informations de l'utilisateur";
break;
case '2':
echo "Afficher l'historique d'activité";
break;
case '3':
echo "Afficher les statistiques de condition physique générales";
break;
case '4':
echo "Afficher la liste des amis";
break;
case '5':
echo "Afficher le paramètres de confidentialité et de visibilités";
break;
case '6':
echo "Afficher la page de synchronisation d'un appareil";
break;
case '7':
echo "Afficher la page d'importation des données";
break;
}
}
break; break;
case '3': // Analyse de la fréquence cardiaque case '3': // Analyse de la fréquence cardiaque
displayHeartRateAnalysisMenu(); displayHeartRateAnalysisMenu();
$analysisChoice = trim(fgets(STDIN)); $analysisChoice = trim(fgets(STDIN));
// TODO: Ajouter la logique pour les options d'analyse ici. // TODO WEB
break; break;
case '4': // Gestion sociale case '4': // Gestion sociale
displaySocialManagementMenu(); socialManagementMenu($model);
$socialChoice = trim(fgets(STDIN));
// TODO: Ajouter la logique pour les options de gestion sociale ici. // TODO: Ajouter la logique pour les options de gestion sociale ici.
break; break;
case '5': // Athlètes (pour les Coachs seulement) case '5': // Coach
displayCoachMenu(); if($model->userMgr->getCurrentUser()->getRole() instanceof \Model\Coach) {
$coachChoice = trim(fgets(STDIN)); CoachMenu($model);
// TODO: Ajouter la logique pour les options de coach ici. } else {
echo "Vous n'avez pas accès à cette section ! (il faut etre coach)\n";
sleep(2);
}
break; break;
case '6': // Paramètres case '6': // Paramètres

@ -0,0 +1,32 @@
<?php
namespace Console;
use Manager\ActivityManager;
use Manager\AthleteManager;
use Manager\CoachManager;
use Manager\DataManager;
use Manager\UserManager;
use Network\RelationshipService;
use Stub\NotificationService;
use Stub\StubData;
class Model
{
public UserManager $userMgr;
public CoachManager $coachMgr;
public DataManager $dataManager;
public AthleteManager $athleteMgr;
public ActivityManager $activityMgr;
public function __construct()
{
$this->dataManager = new StubData();
$authService = new \Stub\AuthService($this->dataManager->userRepository, new \Shared\HashPassword());
$notificationService = new NotificationService($this->dataManager->notificationRepository,$this->dataManager->userRepository);
$relationshipService = new RelationshipService($this->dataManager->relationshipRequestRepository,$notificationService);
$this->userMgr = new UserManager($this->dataManager,$authService,$relationshipService);
$this->athleteMgr = new AthleteManager($this->dataManager,$authService);
$this->coachMgr = new CoachManager($this->dataManager,$authService);
$this->activityMgr = new ActivityManager($this->dataManager,$authService);
}
}

@ -1,20 +1,34 @@
<?php <?php
namespace Network; namespace Network;
use Model\User;
/** /**
* Interface IAuthService * Interface IAuthService
* Adding more methods here may violate the Single Responsibility Principle (SRP). * Adding more methods here may violate the Single Responsibility Principle (SRP).
* It's recommended to keep this interface focused on authentication-related functionality. * It's recommended to keep this interface focused on authentication-related functionality.
*/ */
interface IAuthService { interface IAuthService {
/**
* Get the currently authenticated user.
*
* This method returns an instance of the User model representing
* the currently authenticated user. If no user is authenticated,
* the behavior of this method is implementation-dependent: it could return null,
* throw an exception, etc.
*
* @return User|null The currently authenticated user, or null if no user is authenticated.
*/
public function getCurrentUser(): ?User;
/** /**
* Authenticate a user. * Authenticate a user.
* *
* @param string $username The username of the user. * @param string $emailUser The emailUser of the user.
* @param string $password The password of the user. * @param string $password The password of the user.
* *
* @return bool True if authentication is successful, false otherwise. * @return bool True if authentication is successful, false otherwise.
*/ */
public function login(string $username, string $password): bool; public function login(string $emailUser, string $password): bool;
/** /**
* Register a new user. * Register a new user.
@ -32,6 +46,6 @@ interface IAuthService {
* *
* @return void * @return void
*/ */
public function logoutUser(): void; public function logoutUser(): bool;
} }

@ -1,6 +1,13 @@
<?php <?php
namespace Network;
use Model\RelationshipRequest;
use Model\User;
interface IFriendRequestService { interface IFriendRequestService {
public function sendRequest($fromUserId, $toUserId); public function sendRequest(User $fromUser, User $toUser);
public function acceptRequest($requestId); public function acceptRequest(RelationshipRequest $request);
public function declineRequest($requestId); public function declineRequest(RelationshipRequest $request);
} }

@ -1,5 +1,8 @@
<?php <?php
namespace Network;
interface INotificationService { use Model\Notification;
public function sendNotification($toUserId, $notification);
interface INotificationService extends \Model\Observer {
public function sendNotification(int $toUserId,Notification $notification);
} }

@ -0,0 +1,60 @@
<?php
namespace Network;
use Model\Notification;
use Model\RelationshipRequest;
use Model\User;
use Repository\IUserRepository;
use Stub\RelationshipRequestRepository;
class RelationshipService implements IFriendRequestService
{
private RelationshipRequestRepository $relationshipRequestRepository;
private INotificationService $notificationService;
/**
* @param RelationshipRequestRepository $relationshipRequestRepository
*/
public function __construct(RelationshipRequestRepository $relationshipRequestRepository,INotificationService $notificationService)
{
$this->relationshipRequestRepository = $relationshipRequestRepository;
$this->notificationService = $notificationService;
}
public function sendRequest(User $fromUser, User $toUser): bool
{
try {
$friendRequest = new RelationshipRequest(random_int(1,150), $fromUser->getId(), $toUser->getId());
} catch (\Exception $e) {
return false;
}
$friendRequest->attachObserver($this->notificationService);
$friendRequest->updateStatus( 'pending');
$this->relationshipRequestRepository->addItem($friendRequest);
$toUser->getRole()->addUsersRequests($friendRequest);
return true;
}
public function acceptRequest(RelationshipRequest $request): bool
{
// Mark the request as accepted
$request->updateStatus('accepted');
$request->detachObserver($this->notificationService);
$this->relationshipRequestRepository->deleteItem($request);
return true;
}
public function declineRequest(RelationshipRequest $request): bool
{
$request->updateStatus('declined') ;
$request->detachObserver($this->notificationService);
$this->relationshipRequestRepository->deleteItem($request);
return true;
}
}

@ -0,0 +1,278 @@
<?php
/**
* Nom du fichier: Activity.php
*
* @author PEREDERII Antoine
* @date 2023-11-25
* @description Classe Activity représentant une activité physique.
*
* @package model
*/
namespace Model;
/**
* @class Activity
* @brief Classe représentant une activité physique.
*/
class Activity
{
private static int $lastId = 0;
private int $idActivity;
private String $type;
private \DateTime $date;
private \DateTime $heureDebut;
private \DateTime $heureFin;
private int $effortRessenti;
private float $variability;
private float $variance;
private float $standardDeviation;
private int $average;
private int $maximum;
private int $minimum;
private float $avrTemperature;
private bool $hasAutoPause;
/**
* Constructeur de la classe Activity.
*
* @param String $type Le type d'activité.
* @param \DateTime $date La date de l'activité.
* @param \DateTime $heureDebut L'heure de début de l'activité.
* @param \DateTime $heureFin L'heure de fin de l'activité.
* @param int $effortRessenti L'effort ressenti pour l'activité (entre 0 et 5).
* @param float $variability La variabilité de la fréquence cardiaque.
* @param float $variance La variance de la fréquence cardiaque.
* @param float $standardDeviation L'écart type de la fréquence cardiaque.
* @param int $average La moyenne de la fréquence cardiaque.
* @param int $maximum La fréquence cardiaque maximale.
* @param int $minimum La fréquence cardiaque minimale.
* @param float $avrTemperature La température moyenne pendant l'activité.
* @param bool $hasAutoPause Indique si la pause automatique est activée.
*
* @throws \Exception Si l'effort ressenti n'est pas compris entre 0 et 5.
*/
public function __construct(
String $type,
\DateTime $date,
\DateTime $heureDebut,
\DateTime $heureFin,
int $effortRessenti,
float $variability,
float $variance,
float $standardDeviation,
float $average,
int $maximum,
int $minimum,
float $avrTemperature,
bool $hasPause
) {
$this->idActivity = self::generateId();
$this->type = $type;
$this->date = $date;
$this->heureDebut = $heureDebut;
$this->heureFin = $heureFin;
if ($effortRessenti < 0 || $effortRessenti > 5) {
throw new \Exception("L'effort ressenti doit être compris entre 0 et 5");
}
$this->effortRessenti = $effortRessenti;
$this->variability = $variability;
$this->variance = $variance;
$this->standardDeviation = $standardDeviation;
$this->average = $average;
$this->maximum = $maximum;
$this->minimum = $minimum;
$this->avrTemperature = $avrTemperature;
$this->hasAutoPause = $hasPause;
}
/**
* Génère un identifiant unique pour l'activité.
*
* @return int L'identifiant unique généré.
*/
private static function generateId(): int
{
self::$lastId++;
return self::$lastId;
}
/**
* Obtient l'identifiant de l'activité.
*
* @return int L'identifiant de l'activité.
*/
public function getIdActivity(): int
{
return $this->idActivity;
}
/**
* Obtient le type d'activité.
*
* @return String Le type d'activité.
*/
public function getType(): String
{
return $this->type;
}
/**
* Obtient la date de l'activité.
*
* @return \DateTime La date de l'activité.
*/
public function getDate(): \DateTime
{
return $this->date;
}
/**
* Obtient l'heure de début de l'activité.
*
* @return \DateTime L'heure de début de l'activité.
*/
public function getHeureDebut(): \DateTime
{
return $this->heureDebut;
}
/**
* Obtient l'heure de fin de l'activité.
*
* @return \DateTime L'heure de fin de l'activité.
*/
public function getHeureFin(): \DateTime
{
return $this->heureFin;
}
/**
* Obtient l'effort ressenti pour l'activité.
*
* @return int L'effort ressenti pour l'activité.
*/
public function getEffortRessenti(): int
{
return $this->effortRessenti;
}
/**
* Obtient la variabilité de la fréquence cardiaque.
*
* @return float La variabilité de la fréquence cardiaque.
*/
public function getVariability(): float
{
return $this->variability;
}
/**
* Obtient la variance de la fréquence cardiaque.
*
* @return float La variance de la fréquence cardiaque.
*/
public function getVariance(): float
{
return $this->variance;
}
/**
* Obtient l'écart type de la fréquence cardiaque.
*
* @return float L'écart type de la fréquence cardiaque.
*/
public function getStandardDeviation(): float
{
return $this->standardDeviation;
}
/**
* Obtient la moyenne de la fréquence cardiaque.
*
* @return float La moyenne de la fréquence cardiaque.
*/
public function getAverage(): float
{
return $this->average;
}
/**
* Obtient la fréquence cardiaque maximale.
*
* @return int La fréquence cardiaque maximale.
*/
public function getMaximum(): int
{
return $this->maximum;
}
/**
* Obtient la fréquence cardiaque minimale.
*
* @return int La fréquence cardiaque minimale.
*/
public function getMinimum(): int
{
return $this->minimum;
}
/**
* Obtient la température moyenne pendant l'activité.
*
* @return float La température moyenne pendant l'activité.
*/
public function getAvrTemperature(): float
{
return $this->avrTemperature;
}
/**
* Modifie le type d'activité.
*
* @param String $type Le nouveau type d'activité.
*/
public function setType(String $type): void
{
$this->type = $type;
}
/**
* Modifie l'effort ressenti pour l'activité.
*
* @param int $effortRessenti Le nouvel effort ressenti.
* @throws \Exception Si l'effort ressenti n'est pas compris entre 0 et 5.
*/
public function setEffortRessenti(int $effortRessenti): void
{
if ($effortRessenti < 0 || $effortRessenti > 5) {
throw new \Exception("L'effort ressenti doit être compris entre 0 et 5");
}
$this->effortRessenti = $effortRessenti;
}
/**
* Convertit l'objet Activity en chaîne de caractères.
*
* @return String La représentation de l'activité sous forme de chaîne de caractères.
*/
public function __toString(): String
{
return "Activité n°" . $this->idActivity . " : " .
$this->type . " le " . $this->date->format('d/m/Y') .
" de " . $this->heureDebut->format('H:i:s') . " à " . $this->heureFin->format('H:i:s') .
" avec un effort ressenti de " . $this->effortRessenti . "/5" .
" et une température moyenne de " . $this->avrTemperature . "°C" .
" et une variabilité de " . $this->variability . " bpm" .
" et une variance de " . $this->variance . " bpm" .
" et un écart type de " . $this->standardDeviation . " bpm" .
" et une moyenne de " . $this->average . " bpm" .
" et un maximum de " . $this->maximum . " bpm" .
" et un minimum de " . $this->minimum . " bpm" .
" et une pause automatique " . ($this->hasAutoPause ? "activée" : "désactivée") . ".\n";
}
}
?>

@ -1,9 +1,66 @@
<?php <?php
/**
* Nom du fichier: Athlete.php
*
* @author PEREDERII Antoine
* @date 2023-11-25
* @description Classe Athlete représentant le rôle d'un athlète.
*
* @package model
*/
namespace Model; namespace Model;
use Shared\Exception\NotImplementedException;
use Stub\TrainingRepository;
/**
* @class Athlete
* @brief Classe représentant le rôle d'un athlète.
*/
class Athlete extends Role { class Athlete extends Role {
// Attributs spécifiques a l'athlete si nécessaire // Attributs spécifiques à l'athlète si nécessaire
// private array $statsList = [];
private array $activityList = [];
// private array $dataSourcesList = [];
/**
* Obtient la liste des activités de l'athlète.
*
* @return array La liste des activités de l'athlète.
*/
public function getActivities(): array {
return $this->activityList;
}
/**
* Ajoute une activité à la liste des activités de l'athlète.
*
* @param Activity $myActivity L'activité à ajouter.
* @return bool True si l'ajout est réussi, sinon false.
*/
public function addActivity(Activity $myActivity): bool {
if (isset($myActivity)) {
$this->activityList[] = $myActivity;
return true;
}
return false;
}
public function CheckAdd(User $user): bool
{
if($user->getRole() instanceof Athlete){
return true;
} else {
return false;
}
}
} }
?> ?>

@ -1,8 +1,8 @@
<?php <?php
namespace Model; namespace Model;
class Coach extends Role { abstract class Coach extends Role
// Attributs spécifiques au Coach si nécessaire {
}
?> }

@ -0,0 +1,22 @@
<?php
namespace Model;
class CoachAthlete extends Coach {
/**
* @param User $user
* @return bool
*/
public function CheckAdd(User $user): bool
{
var_dump($user->getRole());
if($user->getRole() instanceof \Model\Athlete){
echo "Yo 5 david";
return true;
}
return false;
}
}
?>

@ -0,0 +1,72 @@
<?php
namespace Model;
class Notification
{
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type): void
{
$this->type = $type;
}
/**
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* @param string $message
*/
public function setMessage(string $message): void
{
$this->message = $message;
}
private string $type;
private string $message;
private int $toUserId;
/**
* @return int
*/
public function getToUserId(): int
{
return $this->toUserId;
}
/**
* @param int $toUserId
*/
public function setToUserId(int $toUserId): void
{
$this->toUserId = $toUserId;
}
/**
* @param string $type
* @param string $message
*/
public function __construct(int $toUserId,string $type, string $message)
{
$this->type = $type;
$this->toUserId =$toUserId;
$this->message = $message;
}
public function __toString(): string
{
return var_export($this, true);
}
}

@ -0,0 +1,24 @@
<?php
namespace Model;
abstract class Observable {
private array $observers = array();
public function attachObserver(Observer $observer) {
$this->observers[] = $observer;
}
public function detachObserver(Observer $observer) {
$key = array_search($observer, $this->observers);
if ($key !== false) {
unset($this->observers[$key]);
}
}
public function notifyObservers() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
}

@ -0,0 +1,8 @@
<?php
namespace Model;
interface Observer
{
public function update(Observable $observable);
}

@ -0,0 +1,99 @@
<?php
namespace Model;
class RelationshipRequest extends Observable
{
private int $id;
private int $fromUser;
private int $toUser;
private string $status = ''; // 'pending', 'accepted', 'declined'
private array $observers = [];
// TODO maybe need to change do User or Tiny User or User DTO
public function __construct(int $id, int $fromUser, int $toUser) {
$this->id = $id;
$this->fromUser = $fromUser;
$this->toUser = $toUser;
}
public function updateStatus(string $newStatus): void {
if (in_array($newStatus, ['pending', 'accepted', 'declined'])) {
$this->status = $newStatus;
$this->notifyObservers();
} else {
throw new \InvalidArgumentException("Invalid status: $newStatus");
}
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* @return int
*/
public function getFromUser(): int
{
return $this->fromUser;
}
/**
* @param int $fromUser
*/
public function setFromUser(int $fromUser): void
{
$this->fromUser = $fromUser;
}
/**
* @return int
*/
public function getToUser(): int
{
return $this->toUser;
}
/**
* @param int $toUser
*/
public function setToUser(int $toUser): void
{
$this->toUser = $toUser;
}
/**
* @return string
*/
public function getStatus(): string
{
return $this->status;
}
public function __toString(): string
{
return sprintf(
"RelationshipRequest: ID: %d, FromUser: %d, ToUser: %d, Status: %s",
$this->id,
$this->fromUser,
$this->toUser,
$this->status
);
}
}

@ -2,7 +2,72 @@
namespace Model; namespace Model;
abstract class Role { abstract class Role {
protected array $usersList = [];
protected array $usersRequests = [];
protected array $trainingList = [];
// Attributs spécifiques au Coach si nécessaire // Attributs spécifiques au Coach si nécessaire
public function getUsersList(): ?array
{
return $this->usersList;
}
public function getUsersRequests(): ?array
{
return $this->usersRequests;
}
public function addUsersRequests(RelationshipRequest $request): void
{
var_dump("====================6=========================");
$this->usersRequests [] = $request;
}
public function removeRequest(RelationshipRequest $req): bool
{
$key = array_search($req, $this->usersRequests);
if ($key !== false) {
array_splice($this->usersRequests, $key, 1);
return true;
}
return false;
}
public abstract function CheckAdd(User $user) : bool;
public function addUser(User $user): bool
{
if($this->CheckAdd($user)){
array_push($this->usersList, $user);
return true;
}
return false;
}
/**
* @param User $user
* @return bool
*/
public function removeUser(User $user): bool
{
if($this->CheckAdd($user)){
$key = array_search($user, $this->usersList);
if ($key !== false) {
array_splice($this->usersList, $key, 1);
return true;
}
}
return false;
}
public function addTraining(Training $training)
{
$this->trainingList [] = $training;
}
public function getTrainingsList(): array
{
return $this->trainingList;
}
} }

@ -0,0 +1,49 @@
<?php
namespace Model;
class Training
{
private int $idTraining;
private \DateTime $date;
private float $latitude;
private float $longitude;
private ?String $description;
private ?String $feedback;
public function __construct(
int $idTraining,
\DateTime $date,
float $latitude,
float $longitude,
?String $description = null,
?String $feedback = null
) {
$this->idTraining = $idTraining;
$this->date = $date;
$this->latitude = $latitude;
$this->longitude = $longitude;
$this->description = $description;
$this->feedback = $feedback;
}
public function getId():int {
return $this->idTraining;
}
public function getDate():\DateTime {
return $this->date;
}
public function getLocation(): String {
return $this->longitude . $this->latitude;
}
public function getDescription(): String
{
return $this->description;
}
public function getFeedback(): String {
return $this->feedback;
}
public function __toString(): String {
return $this->idTraining . " - " . $this->description;
}
}

@ -1,6 +1,5 @@
<?php <?php
namespace Model; namespace Model;
use Model\Role;
// Data Class // Data Class
@ -13,6 +12,7 @@ class User{
private int $id; private int $id;
private string $nom; private string $nom;
private string $prenom; private string $prenom;
private string $username;
private string $email; private string $email;
private string $motDePasse; private string $motDePasse;
private string $sexe; private string $sexe;
@ -20,29 +20,73 @@ class User{
private float $poids; private float $poids;
private \DateTime $dateNaissance; private \DateTime $dateNaissance;
private Role $role; private Role $role;
protected array $notifications = [];
/** /**
* @var array List of friends associated with the user * @return array
*/ */
private array $friendsList; public function getNotifications(): array
{
public function __construct( return $this->notifications;
int $id,
string $nom,
string $prenom,
string $email,
string $motDePasse,
string $sexe,
float $taille,
float $poids,
\DateTime $dateNaissance,
Role $role
) {
$this->friends = array();
} }
/**
* @param int $id
* @param string $nom
* @param string $prenom
* @param string $username
* @param string $email
* @param string $motDePasse
* @param string $sexe
* @param float $taille
* @param float $poids
* @param \DateTime $dateNaissance
* @param \Model\Role $role
*/
public function __construct(int $id, string $nom, string $prenom, string $username, string $email, string $motDePasse, string $sexe, float $taille, float $poids, \DateTime $dateNaissance, Role $role)
{
$this->id = $id;
$this->nom = $nom;
$this->prenom = $prenom;
$this->username = $username;
$this->email = $email;
$this->motDePasse = $motDePasse;
$this->sexe = $sexe;
$this->taille = $taille;
$this->poids = $poids;
$this->dateNaissance = $dateNaissance;
$this->role = $role;
}
public function addNotification($notification): void {
$this->notifications[] = $notification;
}
// Method to delete a notification by its index
public function deleteNotification($index): void {
if (array_key_exists($index, $this->notifications)) {
unset($this->notifications[$index]);
}
}
public function getId(): int { public function getId(): int {
return $this->id; return $this->id;
} }
/**
* @return string
*/
public function getUsername(): string
{
return $this->username;
}
/**
* @param string $username
*/
public function setUsername(string $username): void
{
$this->username = $username;
}
public function setId(int $id): void { public function setId(int $id): void {
$this->id = $id; $this->id = $id;
@ -129,7 +173,7 @@ class User{
} }
public function __toString() { public function __toString() {
return "Athlete [ID: {$this->id}, Nom: {$this->nom}, Prénom: {$this->prenom}, Email: {$this->email}]"; return "Athlete [ID: {$this->id}, Username : $this->username, Nom: {$this->nom}, Prénom: {$this->prenom}, Email: {$this->email}]";
} }
} }

@ -0,0 +1,165 @@
<?php
/**
* Nom du fichier: ActivityManager.php
*
* @author PEREDERII Antoine
* @date 2023-11-25
* @description Classe ActivityManager pour gérer les opérations liées aux activités avec l'upload de données de fichiers .fit.
*
* @package manager
*/
namespace Manager;
use adriangibbons\phpFITFileAnalysis;
use Exception;
use Model\Activity;
use Network\IAuthService;
use Stub\AuthService;
/**
* @class ActivityManager
* @brief Classe pour gérer les opérations liées aux activités.
*/
class ActivityManager
{
/**
* @var AuthService|IAuthService
*/
private IAuthService $authService;
/**
* Constructeur de la classe ActivityManager.
*
* @param IAuthService $authService Le service d'authentification utilisé pour vérifier l'utilisateur actuel.
*/
public function __construct(DataManager $dataManager,IAuthService $authService)
{
$this->authService = $authService;
}
/**
* Enregistre les données d'un fichier FIT au format JSON.
*
* @param object $monFichierFit L'objet FIT File à partir duquel extraire les données.
* @return bool Retourne true en cas de succès, sinon false.
* @throws Exception En cas d'erreur lors de l'ouverture ou de l'enregistrement du fichier FIT.
*/
public function saveFitFileToJSON($monFichierFit): bool
{
try {
// Extraction des données du fichier FIT
$fitData = [
'timestamps' => $monFichierFit->data_mesgs['record']['timestamp'],
'latitudes' => $monFichierFit->data_mesgs['record']['position_lat'],
'longitudes' => $monFichierFit->data_mesgs['record']['position_long'],
'altitudes' => $monFichierFit->data_mesgs['record']['altitude'],
'heartRates' => $monFichierFit->data_mesgs['record']['heart_rate'],
'cadences' => $monFichierFit->data_mesgs['record']['cadence'],
'distances' => $monFichierFit->data_mesgs['record']['distance'],
'speeds' => $monFichierFit->data_mesgs['record']['speed'],
'powers' => $monFichierFit->data_mesgs['record']['power'],
'temperatures' => $monFichierFit->data_mesgs['record']['temperature'],
];
// Conversion des données en format JSON
$jsonFitData = json_encode($fitData, JSON_PRETTY_PRINT);
// Enregistrement du fichier JSON
file_put_contents('/Users/Perederii/SAE/git/Web/Sources/src/data/model/fitFileSaver/jsonFiles/ActivitySave.json', $jsonFitData);
return true;
} catch (\Exception $e) {
echo $e;
}
return false;
}
/**
* Télécharge un fichier FIT, extrait les données pertinentes et crée une nouvelle activité associée.
*
* @param string $type Le type d'activité.
* @param int $effortRessenti L'effort ressenti pour l'activité.
* @param string|resource $file_path_or_data Le chemin du fichier FIT ou les données brutes du fichier FIT.
* @param array|null $options Les options de téléchargement du fichier FIT (facultatif).
* @return bool Retourne true en cas de succès, sinon false.
* @throws Exception En cas d'erreur lors du téléchargement, du traitement ou de l'enregistrement des données.
*/
public function uploadFile($type, $effortRessenti, $file_path_or_data, ?array $options = null): bool
{
try {
// Vérification des options par défaut
if (empty($options)) {
$options = [
'fix_data' => ['all'],
'data_every_second' => false,
'units' => 'metric',
'pace' => true,
'garmin_timestamps' => false,
'overwrite_with_dev_data' => false
];
}
// Ouverture du fichier FIT
if (!($monFichierFit = new phpFITFileAnalysis($file_path_or_data, $options))) {
throw new Exception("Problème lors de l'ouverture du fichier FIT");
}
// Extraction du premier et du dernier timestamp
$firstTimestamp = $monFichierFit->data_mesgs['record']['timestamp'][0];
$lastTimestamp = end($monFichierFit->data_mesgs['record']['timestamp']);
// Conversion des timestamps en objets DateTime
$startDate = \DateTime::createFromFormat('Y-m-d', date('Y-m-d', $firstTimestamp));
$startTime = \DateTime::createFromFormat('H:i:s', date('H:i:s', $firstTimestamp));
$endTime = ($lastTimestamp) ? \DateTime::createFromFormat('H:i:s', date('H:i:s', $lastTimestamp)) : null;
// Vérification des conversions en DateTime
if (!$startDate || !$startTime || ($lastTimestamp && !$endTime)) {
throw new \Exception("La conversion en DateTime a échoué.");
}
// Extraction des autres données nécessaires
$heartRateList = $monFichierFit->data_mesgs['record']['heart_rate'];
$variability = max($heartRateList) - min($heartRateList);
$average = number_format(array_sum($heartRateList) / count($heartRateList), 2);
$varianceV = array_sum(array_map(fn($x) => pow($x - $average, 2), $heartRateList)) / count($heartRateList);
$variance = number_format($varianceV, 2);
$standardDeviation = number_format(sqrt($variance), 2);
$maximum = max($heartRateList);
$minimum = min($heartRateList);
$temperatureList = $monFichierFit->data_mesgs['record']['temperature'];
$averageTemperature = (!empty($temperatureList)) ? number_format(array_sum($temperatureList) / count($temperatureList), 1) : -200;
$isPaused = count($monFichierFit->isPaused()) > 0;
// Création de l'objet Activity
$newActivity = new Activity(
$type,
$startDate,
$startTime,
$endTime,
$effortRessenti,
$variability,
$variance,
$standardDeviation,
$average,
$maximum,
$minimum,
$averageTemperature,
$isPaused
);
// Ajout de l'activité et enregistrement du fichier FIT en JSON
if ($this->authService->getCurrentUser()->getRole()->addActivity($newActivity) && $this->saveFitFileToJSON($monFichierFit)) {
return true;
}
} catch (\Exception $e) {
echo $e;
}
return false;
}
}
?>

@ -0,0 +1,49 @@
<?php
/**
* Nom du fichier: AthleteManager.php
*
* @author PEREDERII Antoine
* @date 2023-11-25
* @description Classe AthleteManager pour gérer les opérations liées aux athlètes et à leurs activités.
*
* @package VotrePackage
*/
namespace Manager;
use Network\IAuthService;
use Stub\AuthService;
/**
* @class AthleteManager
* @brief Classe pour gérer les opérations liées aux athlètes.
*/
class AthleteManager {
private IAuthService $authService;
private DataManager $dataManager;
/**
* Constructeur de la classe AthleteManager.
*
* @param AuthService $authService Le service d'authentification utilisé pour vérifier l'utilisateur actuel.
*/
public function __construct(DataManager $dataManager,IAuthService $authService)
{
$this->authService = $authService;
$this->dataManager = $dataManager;
}
/**
* Récupère les activités de l'athlète actuel.
*
* @return array|null Retourne un tableau d'objets Activity en cas de succès, sinon null.
*/
public function getActivities(): ?array {
if ($this->authService->getCurrentUser() && $this->authService->getCurrentUser()->getRole()->getActivities()) {
return $this->authService->getCurrentUser()->getRole()->getActivities();
}
return null;
}
}
?>

@ -0,0 +1,86 @@
<?php
namespace Manager;
use Model\Training;
use Network\IAuthService;
class CoachManager
{
private IAuthService $authService;
private DataManager $dataManager;
public function __construct(DataManager $dataManager,IAuthService $authService)
{
$this->authService = $authService;
$this->dataManager = $dataManager;
}
public function getUsersList(): ?array
{
if ($this->authService->getCurrentUser() && $this->authService->getCurrentUser()->getRole()->getUsersList()) {
return $this->authService->getCurrentUser()->getRole()->getUsersList();
}
return null;
}
public function getTrainingsList(): ?array {
if ($this->authService->getCurrentUser() && $this->authService->getCurrentUser()->getRole()->getTrainingsList()) {
return $this->authService->getCurrentUser()->getRole()->getTrainingsList();
}
return null;
}
public function addUser(string $username): bool
{
if ($this->authService->getCurrentUser() && $this->authService->getCurrentUser()->getRole()) {
$useToAdd = $this->dataManager->userRepository->getItemByName($username, 0, 1);
if($useToAdd) { // count 1 seul et debuis 0 (debut)
if ($this->authService->getCurrentUser()->getRole()->addUser($useToAdd)) {
return true;
}
}
}
return false;
}
public function removeUser(String $username): bool
{
if ($this->authService->getCurrentUser() && $this->authService->getCurrentUser()->getRole()) {
if(($user = $this->dataManager->userRepository->getItemByName($username, 0, 1))) { // count 1 seul et debuis 0 (debut)
if ($this->authService->getCurrentUser()->getRole()->removeUser($user)) {
return true;
}
}
}
return false;
}
public function addTraining(Training $training): bool
{
if ($this->authService->getCurrentUser() && $this->authService->getCurrentUser()->getRole()) {
if ($this->authService->getCurrentUser()->getRole()->addTraining($training)) {
$this->dataManager->trainingRepository->addItem($training);
return true;
}
}
return false;
}
public function removeTraining(String $trainingId): bool
{
return false;
}
/* public function getStatistics(User $user) : ?Statistic {
if ($this->authService->currentUser && $this->authService->currentUser->getRole()) {
if (($arrayStat = $this->authService->currentUser->getRole()->getUserList($user)->getStatistic())) {
return $arrayStat;
}
}
return null;
}
public function getAnalyse(User $user): bool {
return false;
}*/
}

@ -1,9 +1,16 @@
<?php <?php
namespace Manager; namespace Manager;
abstract class DataManager { use Repository\INotificationRepository;
public $userMgr; use Repository\IRelationshipRequestRepository;
use Repository\ITrainingRepository;
use Repository\IUserRepository;
abstract class DataManager {
public IUserRepository $userRepository;
public IRelationshipRequestRepository $relationshipRequestRepository;
public ITrainingRepository $trainingRepository;
public INotificationRepository $notificationRepository;
} }
?> ?>

@ -1,85 +1,163 @@
<?php <?php
namespace Manager; namespace Manager;
use IFriendRequestService;
use Model\Athlete; use Model\Athlete;
use Model\Coach; use Model\Coach;
use Model\RelationshipRequest;
use Model\User; use Model\User;
use Network\IAuthService; use Network\IAuthService;
use Network\IFriendRequestService;
use Shared\Validation; use Shared\Validation;
use Stub\UserRepository; use Stub\UserRepository;
// c'est le modéle
// should be here try catch ??
class UserManager class UserManager
{ {
/**
* @return User
*/
public function getCurrentUser(): User
{
return $this->currentUser;
}
private IAuthService $authService; private IAuthService $authService;
// private IFriendRequestService $friendService; // private IFriendRequestService $friendService;
private User $currentUser; private User $currentUser;
private UserRepository $userRepo; private DataManager $dataManager;
private IFriendRequestService $relationshipService;
public function __construct(IAuthService $authService)
public function __construct(DataManager $dataManager, IAuthService $authService, IFriendRequestService $relationshipService)
{ {
$this->authService = $authService; $this->authService = $authService;
$this->dataManager = $dataManager;
$this->relationshipService = $relationshipService;
} }
public function login($loginUser, $passwordUser): bool public function login($emailUser, $passwordUser): bool
{ {
// Validate data format
if (!Validation::val_email($emailUser)) {
// The email is not valid
return false;
}
if (!Validation::val_string($passwordUser) || !Validation::val_string($loginUser)) if (!Validation::val_password($passwordUser)) {
throw new \Exception(" some wrong with cred !!!!!"); // The email is not valid
if ($this->authService->login($loginUser, $passwordUser)) { return false;
$this->currentUser = $this->userRepo->GetItemByName($loginUser,0,1);
return true;
} }
if ($this->authService->login($emailUser, $passwordUser)) {
$this->currentUser = $this->authService->getCurrentUser();
// Check if the current user is correctly set
if ($this->currentUser === null) {
return false;
}
return true;
} else {
// Handle unsuccessful login (e.g., log error, throw exception, etc.)
return false; return false;
} }
public function addFriends($newFriendId) {
$list=$this->userRepo->getItems();
foreach($u as $list){
if($u->getId()==$newFriendId){
throw new \Exception("User non existing");
} }
public function addFriend(string $newFriend): bool
{
$newFriend = $this->dataManager->userRepository->getItemByName($newFriend, 0, 1);
if ($newFriend === null) {
throw new \Exception("User not found.");
} }
$list=$this->currentUser->getFriendsList();
foreach($u as $list){ if (in_array($newFriend, $this->currentUser->getRole()->getUsersList())) {
if($newFriendId==$u->getId()){
throw new \Exception("Already friend"); throw new \Exception("Already friend");
} }
var_dump("====================1=========================");
if($this->relationshipService->sendRequest($this->currentUser, $newFriend)){
return true;
};
return false;
}
public function respondToFriendRequest(int $requestId, bool $choice): bool
{
foreach ($this->currentUser->getRole()->getUsersRequests() as $key => $friendRequest) {
/** @var RelationshipRequest $friendRequest */
if ($friendRequest->getId() === $requestId) {
/** @var User $userToAdd */
$userToAdd = $this->dataManager->userRepository->getItemById($friendRequest->getToUser());
if ($userToAdd === null) {
throw new \Exception("User not found with ID: " . $friendRequest->getFromUser());
}
if ($choice) {
try {
if($this->currentUser->getRole()->addUser($userToAdd)){
$this->dataManager->userRepository->addFriend($this->currentUser->getId(),$userToAdd->getId());
$this->relationshipService->acceptRequest($friendRequest);
$this->currentUser->getRole()->removeRequest($friendRequest);
};
}catch (\Exception $e){
$this->currentUser->getRole()->removeUser($userToAdd);
throw new \Exception("Failed to add friend" . $userToAdd->getUsername() );
}
} else {
$this->relationshipService->declineRequest($friendRequest);
} }
if($this->friendService->sendRequest($this->currentUser->getId(), $newFriendId)==true){
return true; return true;
} }
}
return false; return false;
} }
public function register($loginUser, $passwordUser,$data): bool public function searchUsersByName(string $name): array
{ {
// foreach ($data as $entry) { return $this->dataManager->userRepository->getItemsByName($name, 0, 10);
// $isValid = Validation::val_string($entry); }
// if (!$isValid) {
// throw new \Exception( "Entry '$entry' is not a valid string."); public function getFriends(): array{
// } return $this->currentUser->getRole()->getUsersList();
// } }
// NEED TO PERSIST
public function deleteFriend(int $id): bool
{
/** @var User $userToRemove */
$userToRemove = $this->dataManager->userRepository->getItemById($id);
$fieldsToValidate = [ if ($userToRemove === null) {
'nom' => 'Nom non valide', throw new \Exception("User not found with ID: " . $id);
'prenom' => 'Prénom non valide', }
'taille' => 'Taille non valide',
'poids' => 'Poids non valide',
];
if (!$this->currentUser->getRole()->removeUser($userToRemove)) {
throw new \Exception("User with ID: " . $id . " is not in the current user's friends list.");
}
foreach ($fieldsToValidate as $fieldName => $errorMessage) { try {
if (!Validation::val_string($data[$fieldName])) { $this->dataManager->userRepository->deleteFriend($this->currentUser->getId(),$userToRemove->getId() );
throw new \Exception($errorMessage); }catch (\Exception $e){
throw new \Exception("Failed to delete friend" . $userToRemove->getUsername() );
} }
return true;
} }
// NEED TO PERSIST
public function register($loginUser, $passwordUser, $data): bool
{
// foreach ($data as $entry) {
// $isValid = Validation::val_string($entry);
// if (!$isValid) {
// throw new \Exception( "Entry '$entry' is not a valid string.");
// }
// }
$roleName = $data['roleName']; $roleName = $data['roleName'];
if ($roleName !== "Athlete" && $roleName !== "Coach") { if ($roleName !== "Athlete" && $roleName !== "Coach") {
@ -91,7 +169,7 @@ class UserManager
} }
if ($this->authService->register($loginUser, $passwordUser, $data)) { if ($this->authService->register($loginUser, $passwordUser, $data)) {
$this->currentUser = $this->userRepo->GetItemByName($loginUser,0,1); $this->currentUser = $this->authService->getCurrentUser();
return true; return true;
} }
return false; return false;
@ -99,31 +177,12 @@ class UserManager
public function deconnecter(): bool public function deconnecter(): bool
{ {
if($this->authService->logoutUser()){ if ($this->authService->logoutUser()) {
return true; return true;
} }
return false; return false;
} }
public function searchUsersByName(string $name) : array{
$list=array();
$usersList=$this->currentUser->getItems();
foreach($u as $usersList){
if(strcasecmp($name,$u->getNom())==0 || strcasecmp($name,$u->getPrenom())==0){
array_push($list, $u);
}
}
return $list;
}
public function deleteFriend(int $id){
$list=$this->currentUser->getFriendsList();
foreach($u as $list){
if($id==$u->getId()){
if($this->currentUser->delFriendsById($id)==1) return true;
else return false;
}
}
return false;
}
} }

@ -4,13 +4,13 @@ namespace Repository;
interface IGenericRepository interface IGenericRepository
{ {
public function getItemById(int $id); public function getItemById(int $id);
public function GetNbItems(): int; public function getNbItems(): int;
public function GetItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array; public function getItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
public function GetItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array; public function getItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
public function GetItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false); public function getItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false);
public function UpdateItem($oldItem, $newItem) : void; public function updateItem($oldItem, $newItem) : void;
public function AddItem($item) : void; public function addItem($item) : void;
public function DeleteItem($item): bool; public function deleteItem($item): bool;
} }
?> ?>

@ -0,0 +1,7 @@
<?php
namespace Repository;
interface INotificationRepository extends IGenericRepository
{
}

@ -0,0 +1,8 @@
<?php
namespace Repository;
interface IRelationshipRequestRepository extends IGenericRepository
{
}

@ -0,0 +1,8 @@
<?php
namespace Repository;
interface ITrainingRepository extends IGenericRepository
{
}

@ -2,6 +2,9 @@
namespace Repository; namespace Repository;
interface IUserRepository extends IGenericRepository { interface IUserRepository extends IGenericRepository {
} public function addFriend(int $user1,int $user2);
public function deleteFriend(int $user1,int $user2);
}
?> ?>

@ -8,7 +8,10 @@ use Stub\UserRepository;
class StubData extends DataManager{ class StubData extends DataManager{
public function __construct(){ public function __construct(){
$this->userMgr = new UserManager(new AuthService(new UserRepository(),new HashPassword())); $this->userRepository = new UserRepository();
$this->relationshipRequestRepository = new RelationshipRequestRepository();
$this->trainingRepository = new TrainingRepository();
$this->notificationRepository = new NotificationRepository();
} }
} }

@ -0,0 +1,49 @@
<?php
namespace Stub;
use Repository\INotificationRepository;
class NotificationRepository implements INotificationRepository
{
public function getItemById(int $id)
{
// TODO: Implement getItemById() method.
}
public function getNbItems(): int
{
// TODO: Implement getNbItems() method.
}
public function getItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array
{
// TODO: Implement getItems() method.
}
public function getItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array
{
// TODO: Implement getItemsByName() method.
}
public function getItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false)
{
// TODO: Implement getItemByName() method.
}
public function updateItem($oldItem, $newItem): void
{
// TODO: Implement updateItem() method.
}
public function addItem($item): void
{
// TODO: Implement addItem() method.
}
public function deleteItem($item): bool
{
// TODO: Implement deleteItem() method.
}
}

@ -0,0 +1,63 @@
<?php
namespace Stub;
use Model\RelationshipRequest;
use Repository\IRelationshipRequestRepository;
class RelationshipRequestRepository implements IRelationshipRequestRepository {
private array $requests = [];
public function __construct() {
// Example Relationship Requests
/* $this->requests[] = new RelationshipRequest(1, 1, 2); // Request from User 1 to User 2
$this->requests[] = new RelationshipRequest(2, 3, 4); // Request from User 3 to User 4*/
// ... more sample requests as needed
}
public function getItemById(int $id): ?RelationshipRequest {
foreach ($this->requests as $request) {
if ($request->id === $id) {
return $request;
}
}
return null;
}
public function getNbItems(): int {
return count($this->requests);
}
public function getItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array {
// Cette méthode est un exemple simple, on ne gère pas l'ordonnancement ici
return array_slice($this->requests, $index, $count);
}
public function getItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array {
throw new \Exception('Search Not applicable on this entity');
}
public function getItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false) {
throw new \Exception('Search Not applicable on this entity');
}
public function updateItem($oldRequest, $newRequest): void {
$index = array_search($oldRequest, $this->requests);
if ($index !== false) {
$this->requests[$index] = $newRequest;
}
}
public function addItem($request): void {
$this->requests[] = $request;
}
public function deleteItem($request): bool {
$index = array_search($request, $this->requests);
if ($index !== false) {
unset($this->requests[$index]);
return true;
}
return false;
}
}

@ -0,0 +1,49 @@
<?php
namespace Stub;
use Repository\ITrainingRepository;
class TrainingRepository implements ITrainingRepository
{
public function getItemById(int $id)
{
// TODO: Implement getItemById() method.
}
public function getNbItems(): int
{
// TODO: Implement getNbItems() method.
}
public function getItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array
{
// TODO: Implement getItems() method.
}
public function getItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array
{
// TODO: Implement getItemsByName() method.
}
public function getItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false)
{
// TODO: Implement getItemByName() method.
}
public function updateItem($oldItem, $newItem): void
{
// TODO: Implement updateItem() method.
}
public function addItem($item): void
{
// TODO: Implement addItem() method.
}
public function deleteItem($item): bool
{
// TODO: Implement deleteItem() method.
}
}

@ -3,7 +3,7 @@
namespace Stub; namespace Stub;
use Model\Athlete; use Model\Athlete;
use Model\Coach; use Model\CoachAthlete;
use Model\User; use Model\User;
use Repository\IUserRepository; use Repository\IUserRepository;
@ -11,11 +11,11 @@ class UserRepository implements IUserRepository {
private array $users = []; private array $users = [];
public function __construct() { public function __construct() {
$this->users[] = new User(1, "Doe", "John", "john.doe@example.com", "password123", 'M', 1.80, 75, new \DateTime("1985-05-15"), new Coach()); $this->users[] = new User(1, "Doe", "John", "Doe","john.doe@example.com", "password123", 'M', 1.80, 75, new \DateTime("1985-05-15"), new CoachAthlete());
$this->users[] = new User(2, "Smith", "Jane", "jane.smith@example.com", "secure456", 'F', 1.65, 60, new \DateTime("1990-03-10"), new Athlete()); $this->users[] = new User(2, "Smith", "Jane","Smith", "jane.smith@example.com", "secure456", 'F', 1.65, 60, new \DateTime("1990-03-10"), new Athlete());
$this->users[] = new User(3, "Martin", "Paul", "paul.martin@example.com", "super789", 'M', 1.75, 68, new \DateTime("1988-08-20"), new Coach()); $this->users[] = new User(3, "Martin", "Paul","Martin", "paul.martin@example.com", "super789", 'M', 1.75, 68, new \DateTime("1988-08-20"), new CoachAthlete());
$this->users[] = new User(4, "Brown", "Anna", "anna.brown@example.com", "test000", 'F', 1.70, 58, new \DateTime("1992-11-25"), new Athlete()); $this->users[] = new User(4, "Brown", "Anna","Brown", "anna.brown@example.com", "test000", 'F', 1.70, 58, new \DateTime("1992-11-25"), new Athlete());
$this->users[] = new User(5, "Lee", "Bruce", "bruce.lee@example.com", "hello321", 'M', 1.72, 70, new \DateTime("1970-02-05"), new Athlete()); $this->users[] = new User(5, "Lee", "Bruce","Lee", "bruce.lee@example.com", "hello321", 'M', 1.72, 70, new \DateTime("1970-02-05"), new Athlete());
} }
public function getItemById(int $id): ?User { public function getItemById(int $id): ?User {
@ -36,28 +36,28 @@ class UserRepository implements IUserRepository {
return null; return null;
} }
public function GetNbItems(): int { public function getNbItems(): int {
return count($this->users); return count($this->users);
} }
public function GetItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array { public function getItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array {
// Cette méthode est un exemple simple, on ne gère pas l'ordonnancement ici // Cette méthode est un exemple simple, on ne gère pas l'ordonnancement ici
return array_slice($this->users, $index, $count); return array_slice($this->users, $index, $count);
} }
public function GetItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array { public function getItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array {
$filteredUsers = array_filter($this->users, function ($user) use ($substring) { $filteredUsers = array_filter($this->users, function ($user) use ($substring) {
return strpos(strtolower($user->getNom()), strtolower($substring)) !== false || strpos(strtolower($user->getPrenom()), strtolower($substring)) !== false; return str_contains(strtolower($user->getNom()), strtolower($substring)) || str_contains(strtolower($user->getPrenom()), strtolower($substring));
}); });
return array_slice($filteredUsers, $index, $count); return array_slice($filteredUsers, $index, $count);
} }
public function GetItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): ?User { public function getItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): ?User {
$filteredUsers = $this->GetItemsByName($substring, $index, $count, $orderingPropertyName, $descending); $filteredUsers = $this->GetItemsByName($substring, $index, $count, $orderingPropertyName, $descending);
return isset($filteredUsers[0]) ? $filteredUsers[0] : null; return $filteredUsers[0] ?? null;
} }
// should have type here // should have type here
public function UpdateItem($oldUser, $newUser): void { public function updateItem($oldUser, $newUser): void {
$index = array_search($oldUser, $this->users); $index = array_search($oldUser, $this->users);
if ($index !== false) { if ($index !== false) {
$this->users[$index] = $newUser; $this->users[$index] = $newUser;
@ -65,12 +65,12 @@ class UserRepository implements IUserRepository {
} }
// should have type here // should have type here
public function AddItem( $user): void { public function addItem( $user): void {
$this->users[] = $user; $this->users[] = $user;
} }
// should have type here // should have type here
public function DeleteItem( $user): bool { public function deleteItem( $user): bool {
$index = array_search($user, $this->users); $index = array_search($user, $this->users);
if ($index !== false) { if ($index !== false) {
unset($this->users[$index]); unset($this->users[$index]);
@ -78,6 +78,17 @@ class UserRepository implements IUserRepository {
} }
return false; return false;
} }
public function addFriend(int $user1, int $user2)
{
return true;
}
public function deleteFriend(int $user1, int $user2)
{
return true;
}
} }
?> ?>

@ -1,45 +1,50 @@
<?php <?php
namespace Stub; namespace Stub;
use Model\Coach;
use Model\Athlete; use Model\Athlete;
use Model\CoachAthlete;
use Model\User; use Model\User;
use Repository\IUserRepository;
use Shared\Exception\NotImplementedException; use Shared\Exception\NotImplementedException;
use Network\IAuthService; use Network\IAuthService;
use Shared\IHashPassword; use Shared\IHashPassword;
use Stub\UserRepository; use Stub\UserRepository;
class AuthService implements IAuthService { class AuthService implements IAuthService {
private $userRepository; private IUserRepository $userRepository;
private $passwordHasher; private IHashPassword $passwordHacher;
public function __construct(UserRepository $userRepository, IHashPassword $passwordHasher) { private ?User $currentUser;
public function __construct(UserRepository $userRepository, IHashPassword $passwordHacher) {
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
$this->passwordHasher = $passwordHasher; $this->passwordHacher = $passwordHacher;
} }
public function login(string $username,string $password): bool { public function login(string $emailUser,string $password): bool {
$user = $this->userRepository->GetItemByName($username,0,1); $user = $this->userRepository->getItemByEmail($emailUser);
if ($user == null || !$user instanceof User) { if (!$user instanceof User) {
throw new \Exception('Unable to find user with that name'); throw new \Exception('Unable to find user with that name');
} }
if ($user->isValidPassword($password)) { if ($user->isValidPassword($password)) {
$this->currentUser = $user;
return true; return true;
} }
return false; return false;
} }
public function register(string $loginUser, string $password, $data): bool public function register(string $username, string $password, $data): bool
{ {
$hashedPassword = $this->passwordHasher->hashPassword($password); $hashedPassword = $this->passwordHacher->hashPassword($password);
$existingUser = $this->userRepository->getItemByEmail($loginUser); $existingUser = $this->userRepository->getItemByEmail($username);
if ($existingUser != null || $existingUser instanceof User ) { if ($existingUser != null || $existingUser instanceof User ) {
throw new \Exception('User already exists'); throw new \Exception('User already exists');
} }
$prenom = $data['prenom']; $prenom = $data['prenom'];
$username = $data['username'];
$nom = $data['nom']; $nom = $data['nom'];
$email = $data['email']; $email = $data['email'];
$sexe = $data['sexe']; $sexe = $data['sexe'];
@ -49,7 +54,7 @@ class AuthService implements IAuthService {
$roleName = $data['roleName']; $roleName = $data['roleName'];
$role = null; $role = null;
if($roleName == "Coach"){ if($roleName == "Coach"){
$role = new Coach(); $role = new CoachAthlete();
} }
else if($roleName == "Athlete"){ else if($roleName == "Athlete"){
$role = new Athlete(); $role = new Athlete();
@ -58,6 +63,7 @@ class AuthService implements IAuthService {
random_int(0, 100), random_int(0, 100),
$nom, $nom,
$prenom, $prenom,
$username,
$email, $email,
$hashedPassword, $hashedPassword,
$sexe, $sexe,
@ -69,14 +75,20 @@ class AuthService implements IAuthService {
); );
$this->userRepository->addItem($user); $this->userRepository->addItem($user);
$this->currentUser = $user;
return true; return true;
} }
public function logoutUser(): void public function logoutUser(): bool
{
$this->currentUser = null;
return true;
}
public function getCurrentUser(): ?User
{ {
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! logout method not implemented !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); return $this->currentUser;
return;
} }
} }

@ -0,0 +1,61 @@
<?php
namespace Stub;
use Model\Notification;
use Model\Observable;
use Model\Observer;
use Model\RelationshipRequest;
use Network\INotificationService;
use Repository\INotificationRepository;
use Repository\IUserRepository;
class NotificationService implements INotificationService
{
private INotificationRepository $repository;
private IUserRepository $userRepository;
public function __construct(INotificationRepository $repository,IUserRepository $userRepository)
{
$this->repository = $repository;
$this->userRepository = $userRepository;
}
public function sendNotification($toUserId, $notification)
{
$this->repository->addItem($notification);
$this->userRepository->getItemById($toUserId)->addNotification($notification);
}
public function update(Observable $observable)
{
echo "======================";
echo "======================";
var_dump("999999999999999");
if ($observable instanceof RelationshipRequest) {
$request = $observable;
switch ($request->getStatus()) {
case 'pending':
$msg = "You have a new friend request from user " . $request->getFromUser();
$notification = new Notification($request->getToUser(),"social", $msg);
$this->sendNotification($request->getToUser(), $notification);
break;
case 'accepted':
$msg = "User " . $request->getToUser() . " has accepted your request.";
$notification = new Notification($request->getFromUser(),"social", $msg);
$this->sendNotification($request->getFromUser(), $notification);
break;
case 'declined':
$msg = "User " . $request->getToUser() . " has declined your request.";
$notification = new Notification($request->getFromUser(),"social", $msg);
$this->sendNotification($request->getFromUser(), $notification);
break;
}
}
}
}

@ -56,7 +56,7 @@ final class Validation {
* @throws Exception Si le mot de passe n'est pas valide. * @throws Exception Si le mot de passe n'est pas valide.
*/ */
public static function val_password(string $password) : bool { public static function val_password(string $password) : bool {
if ($password === null) { if (!isset($password)) {
throw new Exception("Le mot de passe ne peut être vide."); throw new Exception("Le mot de passe ne peut être vide.");
} else { } else {
if (!preg_match('/^.{6,}$/', $password)) { if (!preg_match('/^.{6,}$/', $password)) {
@ -65,6 +65,24 @@ final class Validation {
return Validation::val_string($password); return Validation::val_string($password);
} }
} }
/**
* Valide un mail.
*
* @param string $email Le mail à valider.
* @return bool True si le mail est valide, sinon false.
* @throws Exception Si le mail n'est pas valide.
*/
public static function val_email(string $email) : bool {
if (!isset($email)) {
throw new Exception("L'adresse e-mail ne peut être vide.");
} else {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new Exception("L'adresse e-mail n'est pas valide.");
}
return true;
}
}
/** /**
* Valide un booléen. * Valide un booléen.

@ -0,0 +1,11 @@
engines:
phpmd:
enabled: true
ratings:
paths:
- "**.php"
exclude_paths:
- "demo/"
- "tests/"

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

@ -0,0 +1,49 @@
vendor
issues
composer.phar
composer.lock
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
/nbproject

@ -0,0 +1,15 @@
dist: trusty
language: php
php:
- 5.5
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --dev --no-interaction
script:
- mkdir -p build/logs
- phpunit --coverage-clover build/logs/clover.xml tests
after_script:
- php vendor/bin/coveralls -v

@ -1,11 +1,13 @@
Copyright (c) 2014 Doctrine Project The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy of Copyright (c) 2015 Adrian Gibbons
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to Permission is hereby granted, free of charge, to any person obtaining a copy
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software and associated documentation files (the "Software"), to deal
of the Software, and to permit persons to whom the Software is furnished to do in the Software without restriction, including without limitation the rights
so, subject to the following conditions: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.

@ -0,0 +1,383 @@
[![Build Status](https://travis-ci.org/adriangibbons/php-fit-file-analysis.svg?branch=master)](https://travis-ci.org/adriangibbons/php-fit-file-analysis) [![Packagist](https://img.shields.io/packagist/v/adriangibbons/php-fit-file-analysis.svg)](https://packagist.org/packages/adriangibbons/php-fit-file-analysis) [![Packagist](https://img.shields.io/packagist/dt/adriangibbons/php-fit-file-analysis.svg)](https://packagist.org/packages/adriangibbons/php-fit-file-analysis) [![Coverage Status](https://coveralls.io/repos/adriangibbons/php-fit-file-analysis/badge.svg?branch=master&service=github)](https://coveralls.io/github/adriangibbons/php-fit-file-analysis?branch=master)
# phpFITFileAnalysis
A PHP (>= v5.4) class for analysing FIT files created by Garmin GPS devices.
[Live demonstration](http://adriangibbons.com/php-fit-file-analysis/demo/) (Right-click and Open in new tab)
## Demo Screenshots
![Mountain Biking](demo/img/mountain-biking.jpg)
![Power Analysis](demo/img/power-analysis.jpg)
![Quadrant Analysis](demo/img/quadrant-analysis.jpg)
![Swim](demo/img/swim.jpg)
Please read this page in its entirety and the [FAQ](https://github.com/adriangibbons/php-fit-file-analysis/wiki/Frequently-Asked-Questions-(FAQ)) first if you have any questions or need support.
## What is a FIT file?
FIT or Flexible and Interoperable Data Transfer is a file format used for GPS tracks and routes. It is used by newer Garmin fitness GPS devices, including the Edge and Forerunner series, which are popular with cyclists and runners.
Visit the FAQ page within the Wiki for more information.
## How do I use phpFITFileAnalysis with my PHP-driven website?
A couple of choices here:
**The more modern way:** Add the package *adriangibbons/php-fit-file-analysis* in a composer.json file:
```JSON
{
"require": {
"adriangibbons/php-fit-file-analysis": "^3.2.0"
}
}
```
Run ```composer update``` from the command line.
The composer.json file should autoload the ```phpFITFileAnalysis``` class, so as long as you include the autoload file in your PHP file, you should be able to instantiate the class with:
```php
<?php
require __DIR__ . '/vendor/autoload.php'; // this file is in the project's root folder
$pFFA = new adriangibbons\phpFITFileAnalysis('fit_files/my_fit_file.fit');
?>
```
**The more manual way:** Download the ZIP from GitHub and put PHP class file from the /src directory somewhere appropriate (e.g. classes/). A conscious effort has been made to keep everything in a single file.
Then include the file on the PHP page where you want to use it and instantiate an object of the class:
```php
<?php
include('classes/phpFITFileAnalysis.php');
$pFFA = new adriangibbons\phpFITFileAnalysis('fit_files/my_fit_file.fit');
?>
```
Note that the only mandatory parameter required when creating an instance is the path to the FIT file that you want to load.
There are more **Optional Parameters** that can be supplied. These are described in more detail further down this page.
The object will automatically load the FIT file and iterate through its contents. It will store any data it finds in arrays, which are accessible via the public data variable.
### Accessing the Data
Data read by the class are stored in associative arrays, which are accessible via the public data variable:
```php
$pFFA->data_mesgs
```
The array indexes are the names of the messages and fields that they contain. For example:
```php
// Contains an array of all heart_rate data read from the file, indexed by timestamp
$pFFA->data_mesgs['record']['heart_rate']
// Contains an integer identifying the number of laps
$pFFA->data_mesgs['session']['num_laps']
```
**OK, but how do I know what messages and fields are in my file?**
You could either iterate through the $pFFA->data_mesgs array, or take a look at the debug information you can dump to a webpage:
```php
// Option 1. Iterate through the $pFFA->data_mesgs array
foreach ($pFFA->data_mesgs as $mesg_key => $mesg) { // Iterate the array and output the messages
echo "<strong>Found Message: $mesg_key</strong><br>";
foreach ($mesg as $field_key => $field) { // Iterate each message and output the fields
echo " - Found Field: $mesg_key -> $field_key<br>";
}
echo "<br>";
}
// Option 2. Show the debug information
$pFFA->showDebugInfo(); // Quite a lot of info...
```
**How about some real-world examples?**
```php
// Get Max and Avg Speed
echo "Maximum Speed: ".max($pFFA->data_mesgs['record']['speed'])."<br>";
echo "Average Speed: ".( array_sum($pFFA->data_mesgs['record']['speed']) / count($pFFA->data_mesgs['record']['speed']) )."<br>";
// Put HR data into a JavaScript array for use in a Chart
echo "var chartData = [";
foreach ($pFFA->data_mesgs['record']['heart_rate'] as $timestamp => $hr_value) {
echo "[$timestamp,$hr_value],";
}
echo "];";
```
**Enumerated Data**
The FIT protocol makes use of enumerated data types. Where these values have been identified in the FIT SDK, they have been included in the class as a private variable: $enum_data.
A public function is available, which will return the enumerated value for a given message type. For example:
```php
// Access data stored within the private class variable $enum_data
// $pFFA->enumData($type, $value)
// e.g.
echo $pFFA->enumData('sport', 2)); // returns 'cycling'
echo $pFFA->enumData('manufacturer', $this->data_mesgs['device_info']['manufacturer']); // returns 'Garmin';
echo $pFFA->manufacturer(); // Short-hand for above
```
In addition, public functions provide a short-hand way to access commonly used enumerated data:
- manufacturer()
- product()
- sport()
### Optional Parameters
There are five optional parameters that can be passed as an associative array when the phpFITFileAnalysis object is instantiated. These are:
- fix_data
- data_every_second
- units
- pace
- garmin_timestamps
- overwrite_with_dev_data
For example:
```php
$options = [
'fix_data' => ['cadence', 'distance'],
'data_every_second' => true
'units' => 'statute',
'pace' => true,
'garmin_timestamps' => true,
'overwrite_with_dev_data' => false
];
$pFFA = new adriangibbons\phpFITFileAnalysis('my_fit_file.fit', $options);
```
The optional parameters are described in more detail below.
#### "Fix" the Data
FIT files have been observed where some data points are missing for one sensor (e.g. cadence/foot pod), where information has been collected for other sensors (e.g. heart rate) at the same instant. The cause is unknown and typically only a relatively small number of data points are missing. Fixing the issue is probably unnecessary, as each datum is indexed using a timestamp. However, it may be important for your project to have the exact same number of data points for each type of data.
**Recognised values:** 'all', 'cadence', 'distance', 'heart_rate', 'lat_lon', 'power', 'speed'
**Examples: **
```php
$options = ['fix_data' => ['all']]; // fix cadence, distance, heart_rate, lat_lon, power, and speed data
$options = ['fix_data' => ['cadence', 'distance']]; // fix cadence and distance data only
$options = ['fix_data' => ['lat_lon']]; // fix position data only
```
If the *fix_data* array is not supplied, then no "fixing" of the data is performed.
A FIT file might contain the following:
<table>
<thead>
<th></th>
<th># Data Points</th>
<th>Delta (c.f. Timestamps)</th>
</thead>
<tbody>
<tr>
<td>timestamp</td><td>10251</td><td>0</td>
</tr>
<tr>
<td>position_lat</td><td>10236</td><td>25</td>
</tr>
<tr>
<td>position_long</td><td>10236</td><td>25</td>
</tr>
<tr>
<td>altitude</td><td>10251</td><td>0</td>
</tr>
<tr>
<td>heart_rate</td><td>10251</td><td>0</td>
</tr>
<tr>
<td>cadence</td><td>9716</td><td>535</td>
</tr>
<tr>
<td>distance</td><td>10236</td><td>25</td>
</tr>
<tr>
<td>speed</td><td>10236</td><td>25</td>
</tr>
<tr>
<td>power</td><td>10242</td><td>9</td>
</tr>
<tr>
<td>temperature</td><td>10251</td><td>0</td>
</tr>
</tbody>
</table>
As illustrated above, the types of data most susceptible to missing data points are: position_lat, position_long, altitude, heart_rate, cadence, distance, speed, and power.
With the exception of cadence information, missing data points are "fixed" by inserting interpolated values.
For cadence, zeroes are inserted as it is thought that it is likely no data has been collected due to a lack of movement at that point in time.
**Interpolation of missing data points**
```php
// Do not use code, just for demonstration purposes
var_dump($pFFA->data_mesgs['record']['temperature']); // ['100'=>22, '101'=>22, '102'=>23, '103'=>23, '104'=>23];
var_dump($pFFA->data_mesgs['record']['distance']); // ['100'=>3.62, '101'=>4.01, '104'=>10.88];
```
As you can see from the trivial example above, temperature data have been recorded for each of five timestamps (100, 101, 102, 103, and 104). However, distance information has not been recorded for timestamps 102 and 103.
If *fix_data* includes 'distance', then the class will attempt to insert data into the distance array with the indexes 102 and 103. Values are determined using a linear interpolation between indexes 101(4.01) and 104(10.88).
The result would be:
```php
var_dump($pFFA->data_mesgs['record']['distance']); // ['100'=>3.62, '101'=>4.01, '102'=>6.30, '103'=>8.59, '104'=>10.88];
```
#### Data Every Second
Some of Garmin's Fitness devices offer the choice of Smart Recording or Every Second Recording.
Smart Recording records key points where the fitness device changes direction, speed, heart rate or elevation. This recording type records less track points and will potentially have gaps between timestamps of greater than one second.
You can force timestamps to be regular one second intervals by setting the option:
```php
$options = ['data_every_second' => true];
```
Missing timestamps will have data interpolated as per the ```fix_data``` option above.
If the ```fix_data``` option is not specified in conjunction with ```data_every_second``` then ```'fix_data' => ['all']``` is assumed.
*Note that you may experience degraded performance using the ```fix_data``` option. Improving the performance will be explored - it is likely the ```interpolateMissingData()``` function is sub-optimal.*
#### Set Units
By default, **metric** units (identified in the table below) are assumed.
<table>
<thead>
<th></th>
<th>Metric<br><em>(DEFAULT)</em></th>
<th>Statute</th>
<th>Raw</th>
</thead>
<tbody>
<tr>
<td>Speed</td><td>kilometers per hour</td><td>miles per hour</td><td>meters per second</td>
</tr>
<tr>
<td>Distance</td><td>kilometers</td><td>miles</td><td>meters</td>
</tr>
<tr>
<td>Altitude</td><td>meters</td><td>feet</td><td>meters</td>
</tr>
<tr>
<td>Latitude</td><td>degrees</td><td>degrees</td><td>semicircles</td>
</tr>
<tr>
<td>Longitude</td><td>degrees</td><td>degrees</td><td>semicircles</td>
</tr>
<tr>
<td>Temperature</td><td>celsius (&#8451;)</td><td>fahrenheit (&#8457;)</td><td>celsius (&#8451;)</td>
</tr>
</tbody>
</table>
You can request **statute** or **raw** units instead of metric. Raw units are those were used by the device that created the FIT file and are native to the FIT standard (i.e. no transformation of values read from the file will occur).
To select the units you require, use one of the following:
```php
$options = ['units' => 'statute'];
$options = ['units' => 'raw'];
$options = ['units' => 'metric']; // explicit but not necessary, same as default
```
#### Pace
If required by the user, pace can be provided instead of speed. Depending on the units requested, pace will either be in minutes per kilometre (min/km) for metric units; or minutes per mile (min/mi) for statute.
To select pace, use the following option:
```php
$options = ['pace' => true];
```
Pace values will be decimal minutes. To get the seconds, you may wish to do something like:
```php
foreach ($pFFA->data_mesgs['record']['speed'] as $key => $value) {
$min = floor($value);
$sec = round(60 * ($value - $min));
echo "pace: $min min $sec sec<br>";
}
```
Note that if 'raw' units are requested then this parameter has no effect on the speed data, as it is left untouched from what was read-in from the file.
#### Timestamps
Unix time is the number of seconds since **UTC 00:00:00 Jan 01 1970**, however the FIT standard specifies that timestamps (i.e. fields of type date_time and local_date_time) represent seconds since **UTC 00:00:00 Dec 31 1989**.
The difference (in seconds) between FIT and Unix timestamps is 631,065,600:
```php
$date_FIT = new DateTime('1989-12-31 00:00:00', new DateTimeZone('UTC'));
$date_UNIX = new DateTime('1970-01-01 00:00:00', new DateTimeZone('UTC'));
$diff = $date_FIT->getTimestamp() - $date_UNIX->getTimestamp();
echo 'The difference (in seconds) between FIT and Unix timestamps is '. number_format($diff);
```
By default, fields of type date_time and local_date_time read from FIT files will have this delta added to them so that they can be treated as Unix time. If the FIT timestamp is required, the 'garmin_timestamps' option can be set to true.
#### Overwrite with Developer Data
The FIT standard allows developers to define the meaning of data without requiring changes to the FIT profile being used. They may define data that is already incorporated in the standard - e.g. HR, cadence, power, etc. By default, if developers do this, the data will overwrite anything in the regular ```$pFFA->data_mesgs['record']``` array. If you do not want this occur, set the 'overwrite_with_dev_data' option to false. The data will still be available in ```$pFFA->data_mesgs['developer_data']```.
## Analysis
The following functions return arrays of data that could be used to create tables/charts:
```php
array $pFFA->hrPartionedHRmaximum(int $hr_maximum);
array $pFFA->hrPartionedHRreserve(int $hr_resting, int $hr_maximum);
array $pFFA->powerPartioned(int $functional_threshold_power);
array $pFFA->powerHistogram(int $bucket_width = 25);
```
For advanced control over these functions, or use with other sensor data (e.g. cadence or speed), use the underlying functions:
```php
array $pFFA->partitionData(string $record_field='', $thresholds=null, bool $percentages = true, bool $labels_for_keys = true);
array $pFFA->histogram(int $bucket_width=25, string $record_field='');
```
Functions exist to determine thresholds based on percentages of user-supplied data:
```php
array $pFFA->hrZonesMax(int $hr_maximum, array $percentages_array=[0.60, 0.75, 0.85, 0.95]);
array $pFFA->hrZonesReserve(int $hr_resting, int $hr_maximum, array $percentages_array=[0.60, 0.65, 0.75, 0.82, 0.89, 0.94 ]) {
array $pFFA->powerZones(int $functional_threshold_power, array $percentages_array=[0.55, 0.75, 0.90, 1.05, 1.20, 1.50]);
```
### Heart Rate
A function exists for analysing heart rate data:
```php
// hr_FT is heart rate at Functional Threshold, or Lactate Threshold Heart Rate
array $pFFA->hrMetrics(int $hr_resting, int $hr_maximum, string $hr_FT, $gender);
// e.g. $pFFA->hrMetrics(52, 189, 172, 'male');
```
**Heart Rate metrics:**
* TRIMP (TRaining IMPulse)
* Intensity Factor
### Power
Three functions exist for analysing power data:
```php
array $pFFA->powerMetrics(int $functional_threshold_power);
array $pFFA->criticalPower(int or array $time_periods); // e.g. 300 or [600, 900]
array $pFFA->quadrantAnalysis(float $crank_length, int $ftp, int $selected_cadence = 90, bool $use_timestamps = false); // Crank length in metres
```
**Power metrics:**
* Average Power
* Kilojoules
* Normalised Power (estimate had your power output been constant)
* Variability Index (ratio of Normalised Power / Average Power)
* Intensity Factor (ratio of Normalised Power / Functional Threshold Power)
* Training Stress Score (effort based on relative intensity and duration)
**Critical Power** (or Best Effort) is the highest average power sustained for a specified period of time within the activity. You can supply a single time period (in seconds), or an array or time periods.
**Quadrant Analysis** provides insight into the neuromuscular demands of a bike ride through comparing pedal velocity with force by looking at cadence and power.
Note that ```$pFFA->criticalPower``` and some power metrics (Normalised Power, Variability Index, Intensity Factor, Training Stress Score) will use the [PHP Trader](http://php.net/manual/en/book.trader.php) extension if it is loaded on the server. If the extension is not loaded then it will use the built-in Simple Moving Average algorithm, which is far less performant particularly for larger files!
A demo of power analysis is available [here](http://adriangibbons.com/php-fit-file-analysis/demo/power-analysis.php).
## Other methods
Returns array of booleans using timestamp as key. true == timer paused (e.g. autopause):
```php
array isPaused()
```
Returns a JSON object with requested ride data:
```php
array getJSON(float $crank_length = null, int $ftp = null, array $data_required = ['all'], int $selected_cadence = 90)
/**
* $data_required can be ['all'] or a combination of:
* ['timestamp', 'paused', 'temperature', 'lap', 'position_lat', 'position_long', 'distance', 'altitude', 'speed', 'heart_rate', 'cadence', 'power', 'quadrant-analysis']
*/
```
Returns array of gear change information (if present, e.g. using Shimano D-Fly Wireless Di2 Transmitter):
```php
// By default, time spent in a gear whilst the timer is paused (e.g. autopause) is ignored. Set to false to include.
array gearChanges($bIgnoreTimerPaused = true)
```
## Acknowledgement
This class has been created using information available in a Software Development Kit (SDK) made available by ANT ([thisisant.com](http://www.thisisant.com/resources/fit)).
As a minimum, I'd recommend reading the three PDFs included in the SDK:
1. FIT File Types Description
2. FIT SDK Introductory Guide
3. Flexible & Interoperable Data Transfer (FIT) Protocol
Following these, the 'Profile.xls' spreadsheet and then the Java/C/C++ examples.

@ -0,0 +1,17 @@
{
"name": "adriangibbons/php-fit-file-analysis",
"type": "library",
"description": "A PHP class for analysing FIT files created by Garmin GPS devices",
"keywords": ["garmin", "fit"],
"homepage": "https://github.com/adriangibbons/php-fit-file-analysis",
"require-dev": {
"phpunit/phpunit": "4.8.*",
"squizlabs/php_codesniffer": "2.*",
"satooshi/php-coveralls": "^2.0"
},
"autoload": {
"psr-4": {
"adriangibbons\\": "src/"
}
}
}

@ -0,0 +1,319 @@
#lap-row-chart svg g g.axis { display: none; }
div.dc-chart {
float: left;
}
.dc-chart rect.bar {
stroke: none;
cursor: pointer;
}
.dc-chart rect.bar:hover {
fill-opacity: .5;
}
.dc-chart rect.stack1 {
stroke: none;
fill: red;
}
.dc-chart rect.stack2 {
stroke: none;
fill: green;
}
.dc-chart rect.deselected {
stroke: none;
fill: #ccc;
}
.dc-chart .empty-chart .pie-slice path {
fill: #FFEEEE;
cursor: default;
}
.dc-chart .empty-chart .pie-slice {
cursor: default;
}
.dc-chart .pie-slice {
fill: white;
font-size: 12px;
cursor: pointer;
}
.dc-chart .pie-slice.external{
fill: black;
}
.dc-chart .pie-slice :hover {
fill-opacity: .8;
}
.dc-chart .pie-slice.highlight {
fill-opacity: .8;
}
.dc-chart .selected path {
stroke-width: 3;
stroke: #ccc;
fill-opacity: 1;
}
.dc-chart .deselected path {
stroke: none;
fill-opacity: .5;
fill: #ccc;
}
.dc-chart .axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dc-chart .axis text {
font: 10px sans-serif;
}
.dc-chart .grid-line {
fill: none;
stroke: #ccc;
opacity: .5;
shape-rendering: crispEdges;
}
.dc-chart .grid-line line {
fill: none;
stroke: #ccc;
opacity: .5;
shape-rendering: crispEdges;
}
.dc-chart .brush rect.background {
z-index: -999;
}
.dc-chart .brush rect.extent {
fill: steelblue;
fill-opacity: .125;
}
.dc-chart .brush .resize path {
fill: #eee;
stroke: #666;
}
.dc-chart path.line {
fill: none;
stroke-width: 1.5px;
}
.dc-chart circle.dot {
stroke: none;
}
.dc-chart g.dc-tooltip path {
fill: none;
stroke: grey;
stroke-opacity: .8;
}
.dc-chart path.area {
fill-opacity: .3;
stroke: none;
}
.dc-chart .node {
font-size: 0.7em;
cursor: pointer;
}
.dc-chart .node :hover {
fill-opacity: .8;
}
.dc-chart .selected circle {
stroke-width: 3;
stroke: #ccc;
fill-opacity: 1;
}
.dc-chart .deselected circle {
stroke: none;
fill-opacity: .5;
fill: #ccc;
}
.dc-chart .bubble {
stroke: none;
fill-opacity: 0.6;
}
.dc-data-count {
float: right;
margin-top: 15px;
margin-right: 15px;
}
.dc-data-count .filter-count {
color: #3182bd;
font-weight: bold;
}
.dc-data-count .total-count {
color: #3182bd;
font-weight: bold;
}
.dc-data-table {
}
.dc-chart g.state {
cursor: pointer;
}
.dc-chart g.state :hover {
fill-opacity: .8;
}
.dc-chart g.state path {
stroke: white;
}
.dc-chart g.selected path {
}
.dc-chart g.deselected path {
fill: grey;
}
.dc-chart g.selected text {
}
.dc-chart g.deselected text {
display: none;
}
.dc-chart g.county path {
stroke: white;
fill: none;
}
.dc-chart g.debug rect {
fill: blue;
fill-opacity: .2;
}
.dc-chart g.row rect {
fill-opacity: 0.8;
cursor: pointer;
}
.dc-chart g.row rect:hover {
fill-opacity: 0.6;
}
.dc-chart g.row text {
fill: #333;
font-size: 12px;
cursor: pointer;
}
.dc-legend {
font-size: 11px;
}
.dc-legend-item {
cursor: pointer;
}
.dc-chart g.axis text {
/* Makes it so the user can't accidentally click and select text that is meant as a label only */
-webkit-user-select: none; /* Chrome/Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10 */
-o-user-select: none;
user-select: none;
pointer-events: none;
}
.dc-chart path.highlight {
stroke-width: 3;
fill-opacity: 1;
stroke-opacity: 1;
}
.dc-chart .highlight {
fill-opacity: 1;
stroke-opacity: 1;
}
.dc-chart .fadeout {
fill-opacity: 0.2;
stroke-opacity: 0.2;
}
.dc-chart path.dc-symbol, g.dc-legend-item.fadeout {
fill-opacity: 0.5;
stroke-opacity: 0.5;
}
.dc-hard .number-display {
float: none;
}
.dc-chart .box text {
font: 10px sans-serif;
-webkit-user-select: none; /* Chrome/Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10 */
-o-user-select: none;
user-select: none;
pointer-events: none;
}
.dc-chart .box line,
.dc-chart .box circle {
fill: #fff;
stroke: #000;
stroke-width: 1.5px;
}
.dc-chart .box rect {
stroke: #000;
stroke-width: 1.5px;
}
.dc-chart .box .center {
stroke-dasharray: 3,3;
}
.dc-chart .box .outlier {
fill: none;
stroke: #ccc;
}
.dc-chart .box.deselected .box {
fill: #ccc;
}
.dc-chart .box.deselected {
opacity: .5;
}
.dc-chart .symbol{
stroke: none;
}
.dc-chart .heatmap .box-group.deselected rect {
stroke: none;
fill-opacity: .5;
fill: #ccc;
}
.dc-chart .heatmap g.axis text {
pointer-events: all;
cursor: pointer;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>phpFITFileAnalysis demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container">
<h2><strong>phpFITFileAnalysis </strong><small>A PHP class for analysing FIT files created by Garmin GPS devices.</small></h2>
<p>This is a demonstration of the phpFITFileAnalysis class available on <a class="btn btn-default btn-lg" href="https://github.com/adriangibbons/phpFITFileAnalysis" target="_blank" role="button"><i class="fa fa-github"></i> GitHub</a></p>
</div>
</div>
<div class="container">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-file-code-o"></i> Mountain Biking</h3>
</div>
<div class="panel-body text-center">
<a href="mountain-biking.php"><img src="img/mountain-biking.jpg" alt="Mountain Biking"></a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-file-code-o"></i> Power Analysis <small>(cycling)</small></h3>
</div>
<div class="panel-body text-center">
<a href="power-analysis.php"><img src="img/power-analysis.jpg" alt="Power Analysis"></a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-file-code-o"></i> Quadrant Analysis</h3>
</div>
<div class="panel-body text-center">
<a href="quadrant-analysis.php"><img src="img/quadrant-analysis.jpg" alt="Quadrant Analysis"></a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-file-code-o"></i> Swim</h3>
</div>
<div class="panel-body text-center">
<a href="swim.php"><img src="img/swim.jpg" alt="Swim"></a>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,83 @@
<?php
/*
* A Ramer-Douglas-Peucker implementation to simplify lines in PHP
* Unlike the one by Pol Dell'Aiera this one is built to operate on an array of arrays and in a non-OO manner,
* making it suitable for smaller apps which must consume input from ArcGIS or Leaflet, without the luxury of GeoPHP/GEOS
*
* Usage:
* $verts = array( array(0,1), array(1,2), array(2,1), array(3,5), array(4,6), array(5,5) );
* $tolerance = 1.0;
* $newverts = simplify_RDP($verts,$tolerance);
*
* Bonus: It does not trim off extra ordinates from each vertex, so it's agnostic as to whether your data are 2D or 3D
* and will return the kept vertices unchanged.
*
* This operates on a single set of vertices, aka a single linestring.
* If used on a multilinestring you will want to run it on each component linestring separately.
*
* No license, use as you will, credits appreciated but not required, etc.
* Greg Allensworth, GreenInfo Network <gregor@greeninfo.org>
*
* My invaluable references:
* https://github.com/Polzme/geoPHP/commit/56c9072f69ed1cec2fdd36da76fa595792b4aa24
* http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
* http://math.ucsd.edu/~wgarner/math4c/derivations/distance/distptline.htm
*/
function simplify_RDP($vertices, $tolerance) {
// if this is a multilinestring, then we call ourselves one each segment individually, collect the list, and return that list of simplified lists
if (is_array($vertices[0][0])) {
$multi = array();
foreach ($vertices as $subvertices) $multi[] = simplify_RDP($subvertices,$tolerance);
return $multi;
}
$tolerance2 = $tolerance * $tolerance;
// okay, so this is a single linestring and we simplify it individually
return _segment_RDP($vertices,$tolerance2);
}
function _segment_RDP($segment, $tolerance_squared) {
if (sizeof($segment) <= 2) return $segment; // segment is too small to simplify, hand it back as-is
// find the maximum distance (squared) between this line $segment and each vertex
// distance is solved as described at UCSD page linked above
// cheat: vertical lines (directly north-south) have no slope so we fudge it with a very tiny nudge to one vertex; can't imagine any units where this will matter
$startx = (float) $segment[0][0];
$starty = (float) $segment[0][1];
$endx = (float) $segment[ sizeof($segment)-1 ][0];
$endy = (float) $segment[ sizeof($segment)-1 ][1];
if ($endx == $startx) $startx += 0.00001;
$m = ($endy - $starty) / ($endx - $startx); // slope, as in y = mx + b
$b = $starty - ($m * $startx); // y-intercept, as in y = mx + b
$max_distance_squared = 0;
$max_distance_index = null;
for ($i=1, $l=sizeof($segment); $i<=$l-2; $i++) {
$x1 = $segment[$i][0];
$y1 = $segment[$i][1];
$closestx = ( ($m*$y1) + ($x1) - ($m*$b) ) / ( ($m*$m)+1);
$closesty = ($m * $closestx) + $b;
$distsqr = ($closestx-$x1)*($closestx-$x1) + ($closesty-$y1)*($closesty-$y1);
if ($distsqr > $max_distance_squared) {
$max_distance_squared = $distsqr;
$max_distance_index = $i;
}
}
// cleanup and disposition
// if the max distance is below tolerance, we can bail, giving a straight line between the start vertex and end vertex (all points are so close to the straight line)
if ($max_distance_squared <= $tolerance_squared) {
return array($segment[0], $segment[ sizeof($segment)-1 ]);
}
// but if we got here then a vertex falls outside the tolerance
// split the line segment into two smaller segments at that "maximum error vertex" and simplify those
$slice1 = array_slice($segment, 0, $max_distance_index);
$slice2 = array_slice($segment, $max_distance_index);
$segs1 = _segment_RDP($slice1, $tolerance_squared);
$segs2 = _segment_RDP($slice2, $tolerance_squared);
return array_merge($segs1,$segs2);
}

@ -0,0 +1,104 @@
<?php
/*!
* PHP Polyline Encoder
*
*/
class PolylineEncoder {
private $points;
private $encoded;
public function __construct() {
$this->points = array();
}
/**
* Add a point
*
* @param float $lat : lattitude
* @param float $lng : longitude
*/
function addPoint($lat, $lng) {
if (empty($this->points)) {
$this->points[] = array('x' => $lat, 'y' => $lng);
$this->encoded = $this->encodeValue($lat) . $this->encodeValue($lng);
} else {
$n = count($this->points);
$prev_p = $this->points[$n-1];
$this->points[] = array('x' => $lat, 'y' => $lng);
$this->encoded .= $this->encodeValue($lat-$prev_p['x']) . $this->encodeValue($lng-$prev_p['y']);
}
}
/**
* Return the encoded string generated from the points
*
* @return string
*/
function encodedString() {
return $this->encoded;
}
/**
* Encode a value following Google Maps API v3 algorithm
*
* @param type $value
* @return type
*/
function encodeValue($value) {
$encoded = "";
$value = round($value * 100000);
$r = ($value < 0) ? ~($value << 1) : ($value << 1);
while ($r >= 0x20) {
$val = (0x20|($r & 0x1f)) + 63;
$encoded .= chr($val);
$r >>= 5;
}
$lastVal = $r + 63;
$encoded .= chr($lastVal);
return $encoded;
}
/**
* Decode an encoded polyline string to an array of points
*
* @param type $value
* @return type
*/
static public function decodeValue($value) {
$index = 0;
$points = array();
$lat = 0;
$lng = 0;
while ($index < strlen($value)) {
$b;
$shift = 0;
$result = 0;
do {
$b = ord(substr($value, $index++, 1)) - 63;
$result |= ($b & 0x1f) << $shift;
$shift += 5;
} while ($b > 31);
$dlat = (($result & 1) ? ~($result >> 1) : ($result >> 1));
$lat += $dlat;
$shift = 0;
$result = 0;
do {
$b = ord(substr($value, $index++, 1)) - 63;
$result |= ($b & 0x1f) << $shift;
$shift += 5;
} while ($b > 31);
$dlng = (($result & 1) ? ~($result >> 1) : ($result >> 1));
$lng += $dlng;
$points[] = array('x' => $lat/100000, 'y' => $lng/100000);
}
return $points;
}
}

@ -0,0 +1,298 @@
<?php
/**
* Demonstration of the phpFITFileAnalysis class using Twitter Bootstrap framework
* https://github.com/adriangibbons/phpFITFileAnalysis
*
* Not intended to be demonstration of how to best use Google APIs, but works for me!
*
* If you find this useful, feel free to drop me a line at Adrian.GitHub@gmail.com
*/
require __DIR__ . '/../src/phpFITFileAnalysis.php';
require __DIR__ . '/libraries/PolylineEncoder.php'; // https://github.com/dyaaj/polyline-encoder
require __DIR__ . '/libraries/Line_DouglasPeucker.php'; // https://github.com/gregallensworth/PHP-Geometry
try {
$file = '/fit_files/mountain-biking.fit';
$options = [
// Just using the defaults so no need to provide
// 'fix_data' => [],
// 'units' => 'metric',
// 'pace' => false
];
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
} catch (Exception $e) {
echo 'caught exception: '.$e->getMessage();
die();
}
// Google Static Maps API
$position_lat = $pFFA->data_mesgs['record']['position_lat'];
$position_long = $pFFA->data_mesgs['record']['position_long'];
$lat_long_combined = [];
foreach ($position_lat as $key => $value) { // Assumes every lat has a corresponding long
$lat_long_combined[] = [$position_lat[$key],$position_long[$key]];
}
$delta = 0.0001;
do {
$RDP_LatLng_coord = simplify_RDP($lat_long_combined, $delta); // Simplify the array of coordinates using the Ramer-Douglas-Peucker algorithm.
$delta += 0.0001; // Rough accuracy somewhere between 4m and 12m depending where in the World coordinates are, source http://en.wikipedia.org/wiki/Decimal_degrees
$polylineEncoder = new PolylineEncoder(); // Create an encoded string to pass as the path variable for the Google Static Maps API
foreach ($RDP_LatLng_coord as $RDP) {
$polylineEncoder->addPoint($RDP[0], $RDP[1]);
}
$map_encoded_polyline = $polylineEncoder->encodedString();
$map_string = '&path=color:red%7Cenc:'.$map_encoded_polyline;
} while (strlen($map_string) > 1800); // Google Map web service URL limit is 2048 characters. 1800 is arbitrary attempt to stay under 2048
$LatLng_start = implode(',', $lat_long_combined[0]);
$LatLng_finish = implode(',', $lat_long_combined[count($lat_long_combined)-1]);
$map_string .= '&markers=color:red%7Clabel:F%7C'.$LatLng_finish.'&markers=color:green%7Clabel:S%7C'.$LatLng_start;
// Google Time Zone API
$date = new DateTime('now', new DateTimeZone('UTC'));
$date_s = $pFFA->data_mesgs['session']['start_time'];
$url_tz = 'https://maps.googleapis.com/maps/api/timezone/json?location='.$LatLng_start.'&timestamp='.$date_s.'&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE';
$result = file_get_contents($url_tz);
$json_tz = json_decode($result);
if ($json_tz->status == 'OK') {
$date_s = $date_s + $json_tz->rawOffset + $json_tz->dstOffset;
} else {
$json_tz->timeZoneName = 'Error';
}
$date->setTimestamp($date_s);
// Google Geocoding API
$location = 'Error';
$url_coord = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='.$LatLng_start.'&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE';
$result = file_get_contents($url_coord);
$json_coord = json_decode($result);
if ($json_coord->status == 'OK') {
foreach ($json_coord->results[0]->address_components as $addressPart) {
if ((in_array('locality', $addressPart->types)) && (in_array('political', $addressPart->types))) {
$city = $addressPart->long_name;
} elseif ((in_array('administrative_area_level_1', $addressPart->types)) && (in_array('political', $addressPart->types))) {
$state = $addressPart->short_name;
} elseif ((in_array('country', $addressPart->types)) && (in_array('political', $addressPart->types))) {
$country = $addressPart->long_name;
}
}
$location = $city.', '.$state.', '.$country;
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>phpFITFileAnalysis demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container">
<h2><strong>phpFITFileAnalysis </strong><small>A PHP class for analysing FIT files created by Garmin GPS devices.</small></h2>
<p>This is a demonstration of the phpFITFileAnalysis class available on <a class="btn btn-default btn-lg" href="https://github.com/adriangibbons/phpFITFileAnalysis" target="_blank" role="button"><i class="fa fa-github"></i> GitHub</a></p>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>File: </dt>
<dd><?php echo $file; ?></dd>
<dt>Device: </dt>
<dd><?php echo $pFFA->manufacturer() . ' ' . $pFFA->product(); ?></dd>
<dt>Sport: </dt>
<dd><?php echo $pFFA->sport(); ?></dd>
</dl>
</div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Recorded: </dt>
<dd>
<?php
echo $date->format('D, d-M-y @ g:ia');
?>
</dd>
<dt>Duration: </dt>
<dd><?php echo gmdate('H:i:s', $pFFA->data_mesgs['session']['total_elapsed_time']); ?></dd>
<dt>Distance: </dt>
<dd><?php echo max($pFFA->data_mesgs['record']['distance']); ?> km</dd>
</dl>
</div>
</div>
<div class="col-md-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Messages</h3>
</div>
<div class="panel-body">
<?php
// Output all the Messages read in the FIT file.
foreach ($pFFA->data_mesgs as $mesg_key => $mesg) {
if ($mesg_key == 'record') {
echo '<strong><mark><u>';
}
echo $mesg_key.'<br>';
if ($mesg_key == 'record') {
echo '</u></mark></strong>';
}
}
?>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Record Fields</h3>
</div>
<div class="panel-body">
<?php
// Output all the Fields found in Record messages within the FIT file.
foreach ($pFFA->data_mesgs['record'] as $mesg_key => $mesg) {
if ($mesg_key == 'speed' || $mesg_key == 'heart_rate') {
echo '<strong><mark><u>';
}
echo $mesg_key.'<br>';
if ($mesg_key == 'speed' || $mesg_key == 'heart_rate') {
echo '</strong></mark></u>';
}
}
?>
</div>
</div>
</div>
<div class="col-md-10">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><a href="http://www.flotcharts.org/" target="_blank"><i class="fa fa-pie-chart"></i> Flot Charts</a> <small><i class="fa fa-long-arrow-left"></i> click</small></h3>
</div>
<div class="panel-body">
<div class="col-md-12">
<div id="speed" style="width:100%; height:75px; margin-bottom:8px"></div>
<div id="heart_rate" style="width:100%; height:75px; margin-bottom:8px"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-map-marker"></i> Google Map</h3>
</div>
<div class="panel-body">
<div id="gmap" style="padding-bottom:20px; text-align:center;">
<strong>Google Geocoding API: </strong><?php echo $location; ?><br>
<strong>Google Time Zone API: </strong><?php echo $json_tz->timeZoneName; ?><br><br>
<img src="https://maps.googleapis.com/maps/api/staticmap?size=640x480&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE<?php echo $map_string; ?>" alt="Google map" border="0">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12"><hr></div>
<div class="col-md-10 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bug"></i> Debug Information</h3>
</div>
<div class="panel-body">
<?php $pFFA->showDebugInfo(); ?>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.flot.min.js"></script>
<script type="text/javascript">
$(document).ready( function() {
var speed_options = {
lines: { show: true, fill: true, fillColor: "rgba(11, 98, 164, 0.4)", lineWidth: 1 },
points: { show: false },
xaxis: {
show: false
},
yaxis: {
max: 35,
tickFormatter: function(label, series) {
return label + ' kmh';
}
},
grid: {
borderWidth: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
};
var speed = {
'color': 'rgba(11, 98, 164, 0.8)',
'data': [
<?php
$tmp = [];
foreach ($pFFA->data_mesgs['record']['speed'] as $key => $value) {
$tmp[] = '['.$key.', '.$value.']';
}
echo implode(', ', $tmp);
?>
]
};
var heart_rate_options = {
lines: { show: true, fill: true, fillColor: 'rgba(255, 0, 0, .4)', lineWidth: 1 },
points: { show: false },
xaxis: {
show: false
},
yaxis: {
min: 80,
tickFormatter: function(label, series) {
return label + ' bpm';
}
},
grid: {
borderWidth: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
};
var heart_rate = {
'color': 'rgba(255, 0, 0, 0.8)',
'data': [
<?php
unset($tmp);
$tmp = [];
foreach ($pFFA->data_mesgs['record']['heart_rate'] as $key => $value) {
$tmp[] = '['.$key.', '.$value.']';
}
echo implode(', ', $tmp);
?>
]
};
$.plot('#speed', [speed], speed_options);
$.plot('#heart_rate', [heart_rate], heart_rate_options);
});
</script>
</body>
</html>

@ -0,0 +1,490 @@
<?php
/**
* Demonstration of the phpFITFileAnalysis class using Twitter Bootstrap framework
* https://github.com/adriangibbons/phpFITFileAnalysis
*
* If you find this useful, feel free to drop me a line at Adrian.GitHub@gmail.com
*/
require __DIR__ . '/../src/phpFITFileAnalysis.php';
try {
$file = '/fit_files/power-analysis.fit';
$options = [
// 'fix_data' => ['all'],
// 'units' => ['metric']
];
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
// Google Time Zone API
$date = new DateTime('now', new DateTimeZone('UTC'));
$date_s = $pFFA->data_mesgs['session']['start_time'];
$url_tz = "https://maps.googleapis.com/maps/api/timezone/json?location=".reset($pFFA->data_mesgs['record']['position_lat']).','.reset($pFFA->data_mesgs['record']['position_long'])."&timestamp=".$date_s."&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE";
$result = file_get_contents("$url_tz");
$json_tz = json_decode($result);
if ($json_tz->status == "OK") {
$date_s = $date_s + $json_tz->rawOffset + $json_tz->dstOffset;
}
$date->setTimestamp($date_s);
$ftp = 329;
$hr_metrics = $pFFA->hrMetrics(52, 185, 172, 'male');
$power_metrics = $pFFA->powerMetrics($ftp);
$criticalPower = $pFFA->criticalPower([2,3,5,10,30,60,120,300,600,1200,3600,7200,10800,18000]);
$power_histogram = $pFFA->powerHistogram();
$power_table = $pFFA->powerPartioned($ftp);
$power_pie_chart = $pFFA->partitionData('power', $pFFA->powerZones($ftp), true, false);
$quad_plot = $pFFA->quadrantAnalysis(0.175, $ftp);
} catch (Exception $e) {
echo 'caught exception: '.$e->getMessage();
die();
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>phpFITFileAnalysis demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container">
<h2><strong>phpFITFileAnalysis </strong><small>A PHP class for analysing FIT files created by Garmin GPS devices.</small></h2>
<p>This is a demonstration of the phpFITFileAnalysis class available on <a class="btn btn-default btn-lg" href="https://github.com/adriangibbons/phpFITFileAnalysis" target="_blank" role="button"><i class="fa fa-github"></i> GitHub</a></p>
</div>
</div>
<div class="container">
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>File: </dt>
<dd><?php echo $file; ?></dd>
<dt>Device: </dt>
<dd><?php echo $pFFA->manufacturer() . ' ' . $pFFA->product(); ?></dd>
<dt>Sport: </dt>
<dd><?php echo $pFFA->sport(); ?></dd>
</dl>
</div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Recorded: </dt>
<dd><?php echo $date->format('D, d-M-y @ g:ia'); ?>
</dd>
<dt>Duration: </dt>
<dd><?php echo gmdate('H:i:s', $pFFA->data_mesgs['session']['total_elapsed_time']); ?></dd>
<dt>Distance: </dt>
<dd><?php echo max($pFFA->data_mesgs['record']['distance']); ?> km</dd>
</dl>
</div>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-tachometer"></i> Metrics</h3></a>
</div>
<div class="panel-body">
<div class="col-md-5 col-md-offset-1">
<h4>Power</h4>
<?php
foreach ($power_metrics as $key => $value) {
echo "$key: $value<br>";
}
?>
</div>
<div class="col-md-5">
<h4>Heart Rate</h4>
<?php
foreach ($hr_metrics as $key => $value) {
echo "$key: $value<br>";
}
?>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-line-chart"></i> Critical Power</h3></a>
</div>
<div class="panel-body">
<div id="criticalPower" style="width:100%; height:300px"></div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bar-chart"></i> Power Distribution (histogram)</h3>
</div>
<div class="panel-body">
<div id="power_distribution" style="width:100%; height:300px"></div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-pie-chart"></i> Power Zones</h3>
</div>
<div class="panel-body">
<div class="col-md-4 col-md-offset-1">
<table id="power_zones_table" class="table table-bordered table-striped">
<thead>
<th>Zone</th>
<th>Zone range</th>
<th>% in zone</th>
</thead>
<tbody>
<?php
$i = 1;
foreach ($power_table as $key => $value) {
echo '<tr id="'.number_format($value, 1, '-', '').'">';
echo '<td>'.$i++.'</td><td>'.$key.' w</td><td>'.$value.' %</td>';
echo '</tr>';
}
?>
</tr>
</tbody>
</table>
</div>
<div class="col-md-4 col-md-offset-1">
<div id="power_pie_chart" style="width:100%; height:250px"></div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-line-chart"></i> Quadrant Analysis <small>Circumferential Pedal Velocity (x-axis) vs Average Effective Pedal Force (y-axis)</small></h3>
</div>
<div class="panel-body">
<div id="quadrant_analysis" style="width:100%; height:600px"></div>
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.flot.min.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.flot.pie.min.js"></script>
<script type="text/javascript">
$(document).ready( function() {
var criticalPower_options = {
lines: { show: true, fill: true, fillColor: "rgba(11, 98, 164, 0.5)", lineWidth: 1 },
points: { show: true },
xaxis: {
ticks: [2,3,5,10,30,60,120,300,600,1200,3600,7200,10800,18000],
transform: function (v) { return Math.log(v); },
inverseTransform: function (v) { return Math.exp(v); },
tickFormatter: function(label, series) {
var hours = parseInt( label / 3600 ) % 24;
var minutes = parseInt( label / 60 ) % 60;
var seconds = label % 60;
var result = (hours > 0 ? hours + "h" : (minutes > 0 ? minutes + "m" : seconds + 's'));
return result;
}
},
yaxis: {
tickFormatter: function(label, series) {
if(label > 0) return label.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' w';
else return '';
}
},
grid: {
hoverable: true,
borderWidth: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
};
var criticalPower = {
'color': 'rgba(11, 98, 164, 1)',
'data': [
<?php
foreach ($criticalPower as $key => $value) {
echo '['.$key.', '.$value.'], ';
}
?>
]
};
var markings = [{ color: "rgba(203, 75, 75, 1)", lineWidth: 2, xaxis: { from: <?php echo $power_metrics['Normalised Power']; ?>, to: <?php echo $power_metrics['Normalised Power']; ?> } }];
var power_distribution_options = {
points: { show: false },
xaxis: {
show: true,
min: 0,
tickSize: 100,
tickFormatter: function(label, series) { return label.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' w'; }
},
yaxis: {
min: 0,
label: 'time in zone',
tickSize: 300,
tickFormatter: function(label, series) {
if(label == 0) return "";
return (label / 60) + ' min';
}
},
grid: {
borderWidth: {
top: 0,
right: 0,
bottom: 0,
left: 0
},
markings: markings
},
legend: { show: false }
};
var power_distribution = {
'color': 'rgba(77, 167, 77, 0.8)',
bars: { show: true, zero: false, barWidth: 25, fillColor: "rgba(77, 167, 77, 0.5)", lineWidth: 1 },
'data': [
<?php
foreach ($power_histogram as $key => $value) {
echo '['.$key.', '.$value.'], ';
}
?>
]
};
var power_pie_chart_options = {
series: {
pie: {
radius: 1,
show: true,
label: {
show: true,
radius: 3/4,
formatter: labelFormatter
}
}
},
grid: { hoverable: true },
legend: { show: false }
};
function labelFormatter(label, series) {
return "<div style='font-size:8pt; text-align:center; padding:2px; color:#333; border-radius: 5px; background-color: #fafafa; border: 1px solid #ddd;'><strong>" + label + "</strong><br/>" + series.data[0][1] + "%</div>";
}
var power_pie_chart = [
{
label: "Active Recovery",
data: <?php echo $power_pie_chart[0]; ?>,
"color": "rgba(217, 83, 79, 0.2)"
},
{
label: "Endurance",
data: <?php echo $power_pie_chart[1]; ?>,
"color": "rgba(217, 83, 79, 0.35)"
},
{
label: "Tempo",
data: <?php echo $power_pie_chart[2]; ?>,
"color": "rgba(217, 83, 79, 0.5)"
},
{
label: "Threshold",
data: <?php echo $power_pie_chart[3]; ?>,
"color": "rgba(217, 83, 79, 0.65)"
},
{
label: "VO2max",
data: <?php echo $power_pie_chart[4]; ?>,
"color": "rgba(217, 83, 79, 0.7)"
},
{
label: "Anaerobic",
data: <?php echo $power_pie_chart[5]; ?>,
"color": "rgba(217, 83, 79, 0.85)"
},
{
label: "Neuromuscular",
data: <?php echo $power_pie_chart[6]; ?>,
"color": "rgba(217, 83, 79, 1)"
}
];
$("<div id='tooltip_bg'></div>").css({
position: "absolute",
display: "none",
"text-align": "center",
"-moz-border-radius": "5px",
"-webkit-border-radius": "5px",
"border-radius": "5px",
"border": "2px solid #fff",
padding: "3px 7px",
"font-size": "12px",
"color": "#fff",
"background-color": "#fff"
}).appendTo("body");
$("<div id='tooltip'></div>").css({
position: "absolute",
display: "none",
"text-align": "center",
"-moz-border-radius": "5px",
"-webkit-border-radius": "5px",
"border-radius": "5px",
"border": "2px solid",
padding: "3px 7px",
"font-size": "12px",
"color": "#555"
}).appendTo("body");
$("#criticalPower").bind("plothover", function (event, pos, item) {
if (item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
var currentColor = item.series.color;
var lastComma = currentColor.lastIndexOf(',');
var newColor = currentColor.slice(0, lastComma + 1) + "0.1)";
$("#tooltip").html('<strong>' + item.series.xaxis.ticks[item.dataIndex].label + '</strong><br>' + y + ' w')
.css({top: item.pageY-45, left: item.pageX+5, "border-color": item.series.color, "background-color": newColor })
.fadeIn(200);
$("#tooltip_bg").html('<strong>' + item.series.xaxis.ticks[item.dataIndex].label + '</strong><br>' + y + ' w')
.css({top: item.pageY-45, left: item.pageX+5 })
.fadeIn(200);
} else {
$("#tooltip").hide();
$("#tooltip_bg").hide();
}
});
$.plot('#criticalPower', [criticalPower], criticalPower_options);
var plot_pd = $.plot('#power_distribution', [power_distribution], power_distribution_options);
var o = plot_pd.pointOffset({ x: <?php echo $power_metrics['Normalised Power']; ?>, y: plot_pd.height() });
$("#power_distribution").append("<span style='background-color: #fafafa; top: 12px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left:" + (o.left + 6) + "px'><strong>normalised power</strong><br><?php echo $power_metrics['Normalised Power']; ?> w</span>");
$.plot('#power_pie_chart', power_pie_chart, power_pie_chart_options);
$("#power_pie_chart").bind("plothover", function (event, pos, obj) {
if (!obj) {
$("#power_zones_table tr").removeClass("danger");
return;
}
$("#power_zones_table tr").removeClass("danger");
$("#" + obj.series.data[0][1].toFixed(1).toString().replace(/\./g, '-') ).addClass("danger");
});
var quad = [<?php
$plottmp = [];
foreach ($quad_plot['plot'] as $v) {
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
}
echo implode(', ', $plottmp); ?>];
var lo = [<?php
unset ($plottmp);
$plottmp = [];
foreach ($quad_plot['ftp-25w'] as $v) {
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
}
echo implode(', ', $plottmp); ?>];
var at = [<?php
unset ($plottmp);
$plottmp = [];
foreach ($quad_plot['ftp'] as $v) {
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
}
echo implode(', ', $plottmp); ?>];
var hi = [<?php
unset ($plottmp);
$plottmp = [];
foreach ($quad_plot['ftp+25w'] as $v) {
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
}
echo implode(', ', $plottmp); ?>];
var markings = [
{
color: "black",
lineWidth: 1,
xaxis: {
from: <?php echo $quad_plot['cpv_threshold']; ?>,
to: <?php echo $quad_plot['cpv_threshold']; ?>
}
},
{
color: "black",
lineWidth: 1,
yaxis: {
from: <?php echo $quad_plot['aepf_threshold']; ?>,
to: <?php echo $quad_plot['aepf_threshold']; ?>
}
}
];
var quadrant_analysis_options = {
xaxis: {
label: 'circumferential pedal velocity',
tickFormatter: function(label, series) { return label + ' m/s'; }
},
yaxis: {
max: 400,
label: 'average effective pedal force',
tickSize: 50,
tickFormatter: function(label, series) {
if(label == 0) return "";
return label + ' N';
}
},
grid: {
borderWidth: {
top: 0,
right: 0,
bottom: 0,
left: 0
},
markings: markings
},
legend: { show: false }
};
var plot_qa = $.plot($("#quadrant_analysis"), [
{
data : quad,
points : { show: true, radius: 0.25, fill : true, fillColor: "#058DC7" }
},
{
data : at,
color: "blue",
lines: { show: true, lineWidth: 0.5 }
},
{
data : lo,
color: "red",
lines: { show: true, lineWidth: 0.5 }
},
{
data : hi,
color: "green",
lines: { show: true, lineWidth: 0.5 }
}
], quadrant_analysis_options);
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 - 40) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: " + (plot_qa.width() - 140) + "px'><strong>High Force / High Velocity</strong><br><?php echo $quad_plot['quad_percent']['hf_hv']; ?> %</span>");
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 - 40) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: 50px'><strong>High Force / Low Velocity</strong><br><?php echo $quad_plot['quad_percent']['hf_lv']; ?> %</span>");
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 + 15) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: 50px'><strong>Low Force / Low Velocity</strong><br><?php echo $quad_plot['quad_percent']['lf_lv']; ?> %</span>");
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 + 15) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: " + (plot_qa.width() - 140) + "px'><strong>Low Force / High Velocity</strong><br><?php echo $quad_plot['quad_percent']['lf_hv']; ?> %</span>");
});
</script>
</body>
</html>

@ -0,0 +1,356 @@
<?php
/**
* Demonstration of the phpFITFileAnalysis class using Twitter Bootstrap framework
* https://github.com/adriangibbons/phpFITFileAnalysis
*
* If you find this useful, feel free to drop me a line at Adrian.GitHub@gmail.com
*/
require __DIR__ . '/../src/phpFITFileAnalysis.php';
try {
$file = '/fit_files/power-analysis.fit';
$options = [
// 'fix_data' => ['all'],
// 'units' => ['metric']
];
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
// Google Time Zone API
$date = new DateTime('now', new DateTimeZone('UTC'));
$date_s = $pFFA->data_mesgs['session']['start_time'];
$url_tz = "https://maps.googleapis.com/maps/api/timezone/json?location=".reset($pFFA->data_mesgs['record']['position_lat']).','.reset($pFFA->data_mesgs['record']['position_long'])."&timestamp=".$date_s."&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE";
$result = file_get_contents("$url_tz");
$json_tz = json_decode($result);
if ($json_tz->status == "OK") {
$date_s = $date_s + $json_tz->rawOffset + $json_tz->dstOffset;
}
$date->setTimestamp($date_s);
$crank_length = 0.175;
$ftp = 329;
$selected_cadence = 90;
$json = $pFFA->getJSON($crank_length, $ftp, ['all'], $selected_cadence);
} catch (Exception $e) {
echo 'caught exception: '.$e->getMessage();
die();
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>phpFITFileAnalysis demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/dc.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container">
<h2><strong>phpFITFileAnalysis </strong><small>A PHP class for analysing FIT files created by Garmin GPS devices.</small></h2>
<p>This is a demonstration of the phpFITFileAnalysis class available on <a class="btn btn-default btn-lg" href="https://github.com/adriangibbons/phpFITFileAnalysis" target="_blank" role="button"><i class="fa fa-github"></i> GitHub</a></p>
</div>
</div>
<div class="container">
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>File: </dt>
<dd><?php echo $file; ?></dd>
<dt>Device: </dt>
<dd><?php echo $pFFA->manufacturer() . ' ' . $pFFA->product(); ?></dd>
<dt>Sport: </dt>
<dd><?php echo $pFFA->sport(); ?></dd>
</dl>
</div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Recorded: </dt>
<dd><?php echo $date->format('D, d-M-y @ g:ia'); ?>
</dd>
<dt>Duration: </dt>
<dd><?php echo gmdate('H:i:s', $pFFA->data_mesgs['session']['total_elapsed_time']); ?></dd>
<dt>Distance: </dt>
<dd><?php echo max($pFFA->data_mesgs['record']['distance']); ?> km</dd>
</dl>
</div>
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-bar-chart"></i> Quadrant Analysis
<button id="reset-button" type="button" class="btn btn-primary btn-xs pull-right">Reset all filters</button>
</h3>
</div>
<div class="panel-body">
<div id="quadrant-analysis-scatter-chart"></div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bar-chart"></i> Google Map</h3>
</div>
<div class="panel-body">
<div class="embed-responsive embed-responsive-4by3">
<div id="google-map" class="embed-responsive-item"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bar-chart"></i> Laps</h3>
</div>
<div class="panel-body">
<div id="lap-row-chart"></div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bar-chart"></i> Cadence histogram</h3>
</div>
<div class="panel-body">
<div id="cad-bar-chart"></div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bar-chart"></i> Heart Rate histogram</h3>
</div>
<div class="panel-body">
<div id="hr-bar-chart"></div>
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/d3.js"></script>
<script type="text/javascript" src="js/crossfilter.js"></script>
<script type="text/javascript" src="js/dc.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=geometry&key=AIzaSyDlPWKTvmHsZ-X6PGsBPAvo0nm1-WdwuYE"></script>
<script type="text/javascript">
$(function(){
var ride_data = <?php echo $json; ?>;
console.log(ride_data);
var segmentOverlay;
var mapOptions = {
zoom: 8
};
var map = new google.maps.Map(document.getElementById('google-map'), mapOptions);
var routeCoordinates = [];
$.each(ride_data.data, function(k, v) {
if(v.position_lat !== null && v.position_long !== null) {
routeCoordinates.push(new google.maps.LatLng(v.position_lat, v.position_long));
}
});
var routePath = new google.maps.Polyline({
path: routeCoordinates,
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 3
});
routePath.setMap(map);
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < routeCoordinates.length; i++) {
bounds.extend(routeCoordinates[i]);
}
map.fitBounds(bounds);
google.maps.event.addDomListener(window, "resize", function() {
var center = map.getCenter();
google.maps.event.trigger(map, "resize");
map.setCenter(center);
});
function updateOverlay(data) {
if(typeof segmentOverlay !== 'undefined') {
segmentOverlay.setMap(null);
}
var segmentCoords = [];
$.each(data, function(k, v) {
if(v.position_lat !== null && v.position_long !== null) {
segmentCoords.push(new google.maps.LatLng(v.position_lat, v.position_long));
}
});
segmentOverlay = new google.maps.Polyline({
path: segmentCoords,
geodesic: true,
strokeColor: '#0000FF',
strokeOpacity: 0.8,
strokeWeight: 3
});
segmentOverlay.setMap(map);
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < segmentCoords.length; i++) {
bounds.extend(segmentCoords[i]);
}
map.fitBounds(bounds);
}
var qaScatterChart = dc.seriesChart("#quadrant-analysis-scatter-chart"),
lapRowChart = dc.rowChart("#lap-row-chart"),
cadBarChart = dc.barChart("#cad-bar-chart"),
hrBarChart = dc.barChart("#hr-bar-chart");
var ndx,
tsDim, latlngDim, qaDim, lapDim, cadDim, hrDim,
qaGrp, lapGrp, cadGrp, hrGrp;
ndx = crossfilter(ride_data.data);
tsDim = ndx.dimension(function(d) {return d.timestamp;});
latlngDim = ndx.dimension(function(d) {return [d.position_lat, d.position_long];});
qaDim = ndx.dimension(function(d) {return [d.cpv, d.aepf, d.lap];});
qaGrp = qaDim.group().reduceSum(function(d) { return d.lap; });
lapDim = ndx.dimension(function(d) {return d.lap;});
lapGrp = lapDim.group();
cadDim = ndx.dimension(function(d) {return d.cadence;});
cadGrp = cadDim.group();
hrDim = ndx.dimension(function(d) {return d.heart_rate;});
hrGrp = hrDim.group();
// Quadrant Analysis chart
var subChart = function(c) {
return dc.scatterPlot(c)
.symbolSize(5)
.highlightedSize(8)
};
qaScatterChart
.width(550)
.height(388)
.chart(subChart)
.x(d3.scale.linear().domain([0,2.5]))
.brushOn(false)
.yAxisLabel("Average Effective Pedal Force (N)")
.xAxisLabel("Circumferential Pedal Velocity (m/s)")
.elasticY(true)
.dimension(qaDim)
.group(qaGrp)
.seriesAccessor(function(d) {return "Lap: " + d.key[2];})
.keyAccessor(function(d) {return d.key[0];})
.valueAccessor(function(d) {return d.key[1];})
.legend(dc.legend().x(450).y(50).itemHeight(13).gap(5).horizontal(1).legendWidth(70).itemWidth(70));
qaScatterChart.margins().left += 20;
qaScatterChart.margins().bottom += 10;
var hght = (lapGrp.size() * 40) > 76 ? lapGrp.size() * 40 : 76;
// Lap chart
lapRowChart
.width(375).height(hght)
.dimension(lapDim)
.group(lapGrp)
.elasticX(true)
.gap(2)
.label(function(d) {
var hours = parseInt(d.value / 3600) % 24;
var minutes = parseInt(d.value / 60 ) % 60;
var seconds = d.value % 60;
return 'Lap ' + d.key + ' (' + ((hours > 0 ? hours + 'h ' : '') + (minutes < 10 ? "0" + minutes : minutes) + "m " + (seconds < 10 ? "0" + seconds : seconds) + 's)');
});
// Cadence chart
cadBarChart
.width(375).height(150)
.dimension(cadDim)
.group(cadGrp)
.x(d3.scale.linear().domain([40,cadDim.top(1)[0].cadence]))
.elasticY(true);
cadBarChart.margins().left = 45;
cadBarChart.yAxis()
.tickFormat(function(d) {
var hours = parseInt(d / 3600) % 24;
var minutes = parseInt(d / 60 ) % 60;
var seconds = d % 60;
return (hours > 0 ? hours + ':' : '') + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds);
})
.ticks(4);
// HR chart
hrBarChart
.width(375).height(150)
.dimension(hrDim)
.group(hrGrp)
.x(d3.scale.linear().domain([hrDim.bottom(1)[0].heart_rate,hrDim.top(1)[0].heart_rate]))
.elasticY(true);
hrBarChart.margins().left = 45;
hrBarChart.yAxis()
.tickFormat(function(d) {
var hours = parseInt(d / 3600) % 24;
var minutes = parseInt(d / 60 ) % 60;
var seconds = d % 60;
return (hours > 0 ? hours + ':' : '') + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds);
})
.ticks(4);
dc.renderAll();
lapRowChart.on('filtered', function(chart, filter){
updateOverlay(tsDim.bottom(Infinity));
});
qaScatterChart.on('renderlet', function(chart) {
var horizontal_line = [{x: chart.x().range()[0], y: chart.y()(ride_data.aepf_threshold)},
{x: chart.x().range()[1], y: chart.y()(ride_data.aepf_threshold)}];
var vertical_line = [{x: chart.x()(ride_data.cpv_threshold), y: chart.y().range()[0]},
{x: chart.x()(ride_data.cpv_threshold), y: chart.y().range()[1]}];
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
// .interpolate('linear');
var path = chart.select('g.chart-body').selectAll('path.aepf_threshold').data([horizontal_line]);
path.enter().append('path').attr('class', 'aepf_threshold').attr('stroke', 'black');
path.attr('d', line);
var path2 = chart.select('g.chart-body').selectAll('path.cpv_threshold').data([vertical_line]);
path2.enter().append('path').attr('class', 'cpv_threshold').attr('stroke', 'black');
path2.attr('d', line);
});
$('#reset-button').on('click', function(e) {
e.preventDefault(); // preventing default click action
dc.filterAll();
dc.redrawAll();
if(typeof segmentOverlay !== 'undefined') {
segmentOverlay.setMap(null);
}
});
});
</script>
</body>
</html>

@ -0,0 +1,185 @@
<?php
/**
* Demonstration of the phpFITFileAnalysis class using Twitter Bootstrap framework
* https://github.com/adriangibbons/phpFITFileAnalysis
*
* If you find this useful, feel free to drop me a line at Adrian.GitHub@gmail.com
*/
require __DIR__ . '/../src/phpFITFileAnalysis.php';
try {
$file = '/fit_files/swim.fit';
$options = [
// 'fix_data' => [],
'units' => 'raw',
// 'pace' => false
];
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
} catch (Exception $e) {
echo 'caught exception: '.$e->getMessage();
die();
}
$units = 'm';
$pool_length = $pFFA->data_mesgs['session']['pool_length'];
$total_distance = number_format($pFFA->data_mesgs['record']['distance']);
if ($pFFA->enumData('display_measure', $pFFA->data_mesgs['session']['pool_length_unit']) == 'statute') {
$pool_length = round($pFFA->data_mesgs['session']['pool_length'] * 1.0936133);
$total_distance = number_format($pFFA->data_mesgs['record']['distance'] * 1.0936133);
$units = 'yd';
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>phpFITFileAnalysis demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container">
<h2><strong>phpFITFileAnalysis </strong><small>A PHP class for analysing FIT files created by Garmin GPS devices.</small></h2>
<p>This is a demonstration of the phpFITFileAnalysis class available on <a class="btn btn-default btn-lg" href="https://github.com/adriangibbons/phpFITFileAnalysis" target="_blank" role="button"><i class="fa fa-github"></i> GitHub</a></p>
</div>
</div>
<div class="container">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-file-code-o"></i> FIT File info</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>File: </dt>
<dd><?php echo $file; ?></dd>
<dt>Device: </dt>
<dd><?php echo $pFFA->manufacturer() . ' ' . $pFFA->product(); ?></dd>
<dt>Sport: </dt>
<dd><?php echo $pFFA->sport(); ?></dd>
<dt>Pool length: </dt>
<dd><?php echo $pool_length.' '.$units; ?></dd>
<dt>Duration: </dt>
<dd><?php echo gmdate('H:i:s', $pFFA->data_mesgs['session']['total_elapsed_time']); ?></dd>
<dt>Total distance: </dt>
<dd><?php echo $total_distance.' '.$units; ?></dd>
</dl>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-bar-chart"></i> Lap Time vs. Number of Strokes</h3>
</div>
<div class="panel-body">
<div id="lap_times" style="width:100%; height:200px; margin-bottom:8px"></div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-tags"></i> Length Message fields</h3>
</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
<thead>
<th>Length</th>
<th>Time (min:sec)</th>
<th># Strokes</th>
<th>Stroke</th>
</thead>
<tbody>
<?php
$lengths = count($pFFA->data_mesgs['length']['total_timer_time']);
$active_length = 0;
for ($i=0; $i<$lengths; $i++) {
$min = floor($pFFA->data_mesgs['length']['total_timer_time'][$i] / 60);
$sec = number_format($pFFA->data_mesgs['length']['total_timer_time'][$i] - ($min*60), 1);
$dur = $min.':'.$sec;
if ($pFFA->enumData('length_type', $pFFA->data_mesgs['length']['length_type'][$i]) == 'active') {
echo '<tr>';
echo '<td>'.($i+1).'</td>';
echo '<td>'.$dur.'</td>';
echo '<td>'.$pFFA->data_mesgs['length']['total_strokes'][$i].'</td>';
echo '<td>'.$pFFA->enumData('swim_stroke', $pFFA->data_mesgs['length']['swim_stroke'][$active_length]).'</td>';
echo '<td></td>';
echo '</tr>';
$active_length++;
} else {
echo '<tr class="danger">';
echo '<td>'.($i+1).'</td>';
echo '<td>'.$dur.'</td>';
echo '<td>-</td>';
echo '<td>Rest</td>';
echo '</tr>';
}
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.flot.min.js"></script>
<script type="text/javascript">
$(document).ready( function() {
var chart_options = {
xaxis: {
show: false
},
yaxes: [ { transform: function (v) { return -v; }, inverseTransform: function (v) { return -v; }, tickFormatter: function(label, series) { return label + ' s'; } },
{ alignTicksWithAxis: 1, position: "right", } ],
grid: {
borderWidth: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
};
var lap_times = {
'color': 'rgba(255, 0, 0, 1)',
'label': 'Lap Time',
'data': [
<?php
$tmp = [];
for ($i=0; $i<$lengths; $i++) {
if ($pFFA->enumData('length_type', $pFFA->data_mesgs['length']['length_type'][$i]) == 'active') {
$tmp[] = '['.$i.', '.$pFFA->data_mesgs['length']['total_timer_time'][$i].']';
}
}
echo implode(', ', $tmp);
?>
],
lines: { show: true, fill: false, lineWidth: 2 },
points: { show: false }
};
var num_strokes = {
'color': 'rgba(11, 98, 164, 0.5)',
'label': 'Number of Strokes',
'data': [
<?php
$tmp = [];
for ($i=0; $i<$lengths; $i++) {
if ($pFFA->enumData('length_type', $pFFA->data_mesgs['length']['length_type'][$i]) == 'active') {
$tmp[] = '['.$i.', '.$pFFA->data_mesgs['length']['total_strokes'][$i].']';
}
}
echo implode(', ', $tmp);
?>
],
bars: { show: true, fill: true, fillColor: "rgba(11, 98, 164, 0.3)", lineWidth: 1 },
points: { show: false },
yaxis: 2
};
$.plot('#lap_times', [lap_times, num_strokes], chart_options);
});
</script>
</body>
</html>

@ -0,0 +1,92 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class BasicTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $demo_files = [];
private $valid_files = ['mountain-biking.fit', 'power-analysis.fit', 'road-cycling.fit', 'swim.fit'];
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
}
public function testDemoFilesExist()
{
$this->demo_files = array_values(array_diff(scandir($this->base_dir), array('..', '.')));
sort($this->demo_files);
sort($this->valid_files);
$this->assertEquals($this->valid_files, $this->demo_files);
var_dump($this->demo_files);
}
/**
* @expectedException Exception
*/
public function testEmptyFilepath()
{
$pFFA = new adriangibbons\phpFITFileAnalysis('');
}
/**
* @expectedException Exception
*/
public function testFileDoesntExist()
{
$pFFA = new adriangibbons\phpFITFileAnalysis('file_doesnt_exist.fit');
}
/**
* @expectedException Exception
*/
public function testInvalidFitFile()
{
$file_path = $this->base_dir . '../composer.json';
$pFFA = new adriangibbons\phpFITFileAnalysis($file_path);
}
public function testDemoFileBasics()
{
foreach($this->demo_files as $filename) {
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $filename);
$this->assertGreaterThan(0, $pFFA->data_mesgs['activity']['timestamp'], 'No Activity timestamp!');
if (isset($pFFA->data_mesgs['record'])) {
$this->assertGreaterThan(0, count($pFFA->data_mesgs['record']['timestamp']), 'No Record timestamps!');
// Check if distance from record messages is +/- 2% of distance from session message
if (is_array($pFFA->data_mesgs['record']['distance'])) {
$distance_difference = abs(end($pFFA->data_mesgs['record']['distance']) - $pFFA->data_mesgs['session']['total_distance'] / 1000);
$this->assertLessThan(0.02 * end($pFFA->data_mesgs['record']['distance']), $distance_difference, 'Session distance should be similar to last Record distance');
}
// Look for big jumps in latitude and longitude
if (isset($pFFA->data_mesgs['record']['position_lat']) && is_array($pFFA->data_mesgs['record']['position_lat'])) {
foreach ($pFFA->data_mesgs['record']['position_lat'] as $key => $value) {
if (isset($pFFA->data_mesgs['record']['position_lat'][$key - 1])) {
if (abs($pFFA->data_mesgs['record']['position_lat'][$key - 1] - $pFFA->data_mesgs['record']['position_lat'][$key]) > 1) {
$this->assertTrue(false, 'Too big a jump in latitude');
}
}
}
}
if (isset($pFFA->data_mesgs['record']['position_long']) && is_array($pFFA->data_mesgs['record']['position_long'])) {
foreach ($pFFA->data_mesgs['record']['position_long'] as $key => $value) {
if (isset($pFFA->data_mesgs['record']['position_long'][$key - 1])) {
if (abs($pFFA->data_mesgs['record']['position_long'][$key - 1] - $pFFA->data_mesgs['record']['position_long'][$key]) > 1) {
$this->assertTrue(false, 'Too big a jump in longitude');
}
}
}
}
}
}
}
}

@ -0,0 +1,33 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class EnumDataTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'swim.fit';
private $pFFA;
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
$this->pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw']);
}
public function testEnumData_manufacturer()
{
$this->assertEquals('Garmin', $this->pFFA->manufacturer());
}
public function testEnumData_product()
{
$this->assertEquals('Forerunner 910XT', $this->pFFA->product());
}
public function testEnumData_sport()
{
$this->assertEquals('Swimming', $this->pFFA->sport());
}
}

@ -0,0 +1,132 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class FixDataTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'road-cycling.fit';
private $filename2 = 'power-analysis.fit';
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
}
/**
* Original road-cycling.fit before fixData() contains:
*
* record message | count()
* -----------------+--------
* timestamp | 4317
* position_lat | 4309 <- test
* position_long | 4309 <- test
* distance | 4309 <- test
* altitude | 4317
* speed | 4309 <- test
* heart_rate | 4316 <- test
* temperature | 4317
*/
public function testFixData_before()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename);
$this->assertEquals(4309, count($pFFA->data_mesgs['record']['position_lat']));
$this->assertEquals(4309, count($pFFA->data_mesgs['record']['position_long']));
$this->assertEquals(4309, count($pFFA->data_mesgs['record']['distance']));
$this->assertEquals(4309, count($pFFA->data_mesgs['record']['speed']));
$this->assertEquals(4316, count($pFFA->data_mesgs['record']['heart_rate']));
$pFFA2 = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename2);
$this->assertEquals(3043, count($pFFA2->data_mesgs['record']['cadence']));
$this->assertEquals(3043, count($pFFA2->data_mesgs['record']['power']));
}
/**
* $pFFA->data_mesgs['record']['heart_rate']
* [805987191 => 118],
* [805987192 => missing],
* [805987193 => 117]
*/
public function testFixData_hr_missing_key()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename);
$hr_missing_key = array_diff($pFFA->data_mesgs['record']['timestamp'], array_keys($pFFA->data_mesgs['record']['heart_rate']));
$this->assertEquals([3036 => 1437052792], $hr_missing_key);
}
public function testFixData_after()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['fix_data' => ['all']]);
$this->assertEquals(4317, count($pFFA->data_mesgs['record']['position_lat']));
$this->assertEquals(4317, count($pFFA->data_mesgs['record']['position_long']));
$this->assertEquals(4317, count($pFFA->data_mesgs['record']['distance']));
$this->assertEquals(4317, count($pFFA->data_mesgs['record']['speed']));
$this->assertEquals(4317, count($pFFA->data_mesgs['record']['heart_rate']));
$pFFA2 = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename2, ['fix_data' => ['cadence', 'power']]);
$this->assertEquals(3043, count($pFFA2->data_mesgs['record']['cadence']));
$this->assertEquals(3043, count($pFFA2->data_mesgs['record']['power']));
}
/**
* $pFFA->data_mesgs['record']['heart_rate']
* [805987191 => 118],
* [805987192 => 117.5],
* [805987193 => 117]
*/
public function testFixData_hr_missing_key_fixed()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['fix_data' => ['heart_rate']]);
$this->assertEquals(117.5, $pFFA->data_mesgs['record']['heart_rate'][1437052792]);
}
public function testFixData_validate_options_pass()
{
// Positive testing
$valid_options = ['all', 'cadence', 'distance', 'heart_rate', 'lat_lon', 'speed', 'power'];
foreach($valid_options as $valid_option) {
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['fix_data' => [$valid_option]]);
}
}
public function testFixData_data_every_second()
{
$options = [
'fix_data' => ['speed'],
'data_every_second' => true,
'units' => 'raw',
];
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, $options);
$this->assertEquals(6847, count($pFFA->data_mesgs['record']['speed']));
}
/**
* @expectedException Exception
*/
public function testFixData_validate_options_fail()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['fix_data' => ['INVALID']]);
}
/**
* @expectedException Exception
*/
public function testFixData_invalid_pace_option()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['pace' => 'INVALID']);
}
/**
* @expectedException Exception
*/
public function testFixData_invalid_pace_option2()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['pace' => 123456]);
}
}

@ -0,0 +1,34 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class GetJSONTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'power-analysis.fit';
private $pFFA;
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
$this->pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw']);
}
public function testGetJSON()
{
// getJSON() create a JSON object that contains available record message information.
$crank_length = null;
$ftp = null;
$data_required = ['timestamp', 'speed'];
$selected_cadence = 90;
$php_object = json_decode($this->pFFA->getJSON($crank_length, $ftp, $data_required, $selected_cadence));
// Assert data
$this->assertEquals('raw', $php_object->units);
$this->assertEquals(3043, count($php_object->data));
$this->assertEquals(1437474517, $php_object->data[0]->timestamp);
$this->assertEquals(1.378, $php_object->data[0]->speed);
}
}

@ -0,0 +1,52 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class HRTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'power-analysis.fit';
private $pFFA;
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
$this->pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw']);
}
public function testHR_hrMetrics()
{
$hr_metrics = $this->pFFA->hrMetrics(50, 190, 170, 'male');
$this->assertEquals(74, $hr_metrics['TRIMPexp']);
$this->assertEquals(0.8, $hr_metrics['hrIF']);
}
public function testHR_hrPartionedHRmaximum()
{
// Calls phpFITFileAnalysis::hrZonesMax()
$hr_partioned_HRmaximum = $this->pFFA->hrPartionedHRmaximum(190);
$this->assertEquals(19.4, $hr_partioned_HRmaximum['0-113']);
$this->assertEquals(33.1, $hr_partioned_HRmaximum['114-142']);
$this->assertEquals(31.4, $hr_partioned_HRmaximum['143-161']);
$this->assertEquals(16.1, $hr_partioned_HRmaximum['162-180']);
$this->assertEquals(0, $hr_partioned_HRmaximum['181+']);
}
public function testHR_hrPartionedHRreserve()
{
// Calls phpFITFileAnalysis::hrZonesReserve()
$hr_partioned_HRreserve = $this->pFFA->hrPartionedHRreserve(50, 190);
$this->assertEquals(45.1, $hr_partioned_HRreserve['0-133']);
$this->assertEquals(5.8, $hr_partioned_HRreserve['134-140']);
$this->assertEquals(20.1, $hr_partioned_HRreserve['141-154']);
$this->assertEquals(15.9, $hr_partioned_HRreserve['155-164']);
$this->assertEquals(12.5, $hr_partioned_HRreserve['165-174']);
$this->assertEquals(0.6, $hr_partioned_HRreserve['175-181']);
$this->assertEquals(0, $hr_partioned_HRreserve['182+']);
}
}

@ -0,0 +1,33 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class IsPausedTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'power-analysis.fit';
private $pFFA;
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
$this->pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw']);
}
public function testIsPaused()
{
// isPaused() returns array of booleans using timestamp as key.
$is_paused = $this->pFFA->isPaused();
// Assert number of timestamps
$this->assertEquals(3190, count($is_paused));
// Assert an arbitrary element/timestamps is true
$this->assertEquals(true, $is_paused[1437477706]);
// Assert an arbitrary element/timestamps is false
$this->assertEquals(false, $is_paused[1437474517]);
}
}

@ -0,0 +1,157 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class PowerTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'power-analysis.fit';
private $pFFA;
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
$this->pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw']);
}
public function testPower_criticalPower_values()
{
$time_periods = [2,5,10,60,300,600,1200,1800,3600];
$cps = $this->pFFA->criticalPower($time_periods);
array_walk($cps, function(&$v) { $v = round($v, 2); });
$this->assertEquals(551.50, $cps[2]);
$this->assertEquals(542.20, $cps[5]);
$this->assertEquals(527.70, $cps[10]);
$this->assertEquals(452.87, $cps[60]);
$this->assertEquals(361.99, $cps[300]);
$this->assertEquals(328.86, $cps[600]);
$this->assertEquals(260.52, $cps[1200]);
$this->assertEquals(221.81, $cps[1800]);
}
public function testPower_criticalPower_time_period_max()
{
// 14400 seconds is 4 hours and longer than file duration so should only get one result back (for 2 seconds)
$time_periods = [2,14400];
$cps = $this->pFFA->criticalPower($time_periods);
$this->assertEquals(1, count($cps));
}
public function testPower_powerMetrics()
{
$power_metrics = $this->pFFA->powerMetrics(350);
$this->assertEquals(221, $power_metrics['Average Power']);
$this->assertEquals(671, $power_metrics['Kilojoules']);
$this->assertEquals(285, $power_metrics['Normalised Power']);
$this->assertEquals(1.29, $power_metrics['Variability Index']);
$this->assertEquals(0.81, $power_metrics['Intensity Factor']);
$this->assertEquals(56, $power_metrics['Training Stress Score']);
}
public function testPower_power_partitioned()
{
// Calls phpFITFileAnalysis::powerZones();
$power_partioned = $this->pFFA->powerPartioned(350);
$this->assertEquals(45.2, $power_partioned['0-193']);
$this->assertEquals(10.8, $power_partioned['194-263']);
$this->assertEquals(18.1, $power_partioned['264-315']);
$this->assertEquals(17.9, $power_partioned['316-368']);
$this->assertEquals(4.2, $power_partioned['369-420']);
$this->assertEquals(3.3, $power_partioned['421-525']);
$this->assertEquals(0.4, $power_partioned['526+']);
}
public function testPower_powerHistogram()
{
// Calls phpFITFileAnalysis::histogram();
$power_histogram = $this->pFFA->powerHistogram(100);
$this->assertEquals(374, $power_histogram[0]);
$this->assertEquals(634, $power_histogram[100]);
$this->assertEquals(561, $power_histogram[200]);
$this->assertEquals(1103, $power_histogram[300]);
$this->assertEquals(301, $power_histogram[400]);
$this->assertEquals(66, $power_histogram[500]);
$this->assertEquals(4, $power_histogram[600]);
}
/**
* @expectedException Exception
*/
public function testPower_criticalPower_no_power()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . 'road-cycling.fit');
$time_periods = [2,14400];
$cps = $pFFA->criticalPower($time_periods);
}
/**
* @expectedException Exception
*/
public function testPower_powerMetrics_no_power()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . 'road-cycling.fit');
$power_metrics = $pFFA->powerMetrics(350);
}
/**
* @expectedException Exception
*/
public function testPower_powerHistogram_no_power()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . 'road-cycling.fit');
$power_metrics = $pFFA->powerHistogram(100);
}
/**
* @expectedException Exception
*/
public function testPower_powerHistogram_invalid_bucket_width()
{
$power_histogram = $this->pFFA->powerHistogram('INVALID');
}
/**
* @expectedException Exception
*/
public function testPower_power_partitioned_no_power()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . 'road-cycling.fit');
$power_partioned = $pFFA->powerPartioned(350);
}
/**
* @expectedException Exception
*/
public function testPower_power_partitioned_not_array()
{
$power_histogram = $this->pFFA->partitionData('power', 123456);
}
/**
* @expectedException Exception
*/
public function testPower_power_partitioned_not_numeric()
{
$power_histogram = $this->pFFA->partitionData('power', [200, 400, 'INVALID']);
}
/**
* @expectedException Exception
*/
public function testPower_power_partitioned_not_ascending()
{
$power_histogram = $this->pFFA->partitionData('power', [400, 200]);
}
}

@ -0,0 +1,51 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class QuadrantAnalysisTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'power-analysis.fit';
private $pFFA;
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
$this->pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw']);
}
public function testQuadrantAnalysis()
{
$crank_length = 0.175;
$ftp = 329;
$selected_cadence = 90;
$use_timestamps = false;
// quadrantAnalysis() returns an array that can be used to plot CPV vs AEPF.
$quadrant_plot = $this->pFFA->quadrantAnalysis($crank_length, $ftp, $selected_cadence, $use_timestamps);
$this->assertEquals(90, $quadrant_plot['selected_cadence']);
$this->assertEquals(199.474, $quadrant_plot['aepf_threshold']);
$this->assertEquals(1.649, $quadrant_plot['cpv_threshold']);
$this->assertEquals(10.48, $quadrant_plot['quad_percent']['hf_hv']);
$this->assertEquals(10.61, $quadrant_plot['quad_percent']['hf_lv']);
$this->assertEquals(14.00, $quadrant_plot['quad_percent']['lf_hv']);
$this->assertEquals(64.91, $quadrant_plot['quad_percent']['lf_lv']);
$this->assertEquals(1.118, $quadrant_plot['plot'][0][0]);
$this->assertEquals(47.411, $quadrant_plot['plot'][0][1]);
$this->assertEquals(0.367, $quadrant_plot['ftp-25w'][0][0]);
$this->assertEquals(829.425, $quadrant_plot['ftp-25w'][0][1]);
$this->assertEquals(0.367, $quadrant_plot['ftp'][0][0]);
$this->assertEquals(897.634, $quadrant_plot['ftp'][0][1]);
$this->assertEquals(0.367, $quadrant_plot['ftp+25w'][0][0]);
$this->assertEquals(965.843, $quadrant_plot['ftp+25w'][0][1]);
}
}

@ -0,0 +1,60 @@
<?php
error_reporting(E_ALL);
if(!class_exists('adriangibbons\phpFITFileAnalysis')) {
require __DIR__ . '/../src/phpFITFileAnalysis.php';
}
class SetUnitsTest extends PHPUnit_Framework_TestCase
{
private $base_dir;
private $filename = 'road-cycling.fit';
public function setUp()
{
$this->base_dir = __DIR__ . '/../demo/fit_files/';
}
public function testSetUnits_validate_options_pass()
{
$valid_options = ['raw', 'statute', 'metric'];
foreach($valid_options as $valid_option) {
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => $valid_option]);
if($valid_option === 'raw') {
$this->assertEquals(1.286, reset($pFFA->data_mesgs['record']['speed']));
}
if($valid_option === 'statute') {
$this->assertEquals(2.877, reset($pFFA->data_mesgs['record']['speed']));
}
if($valid_option === 'metric') {
$this->assertEquals(4.63, reset($pFFA->data_mesgs['record']['speed']));
}
}
}
/**
* @expectedException Exception
*/
public function testSetUnits_validate_options_fail()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'INVALID']);
}
public function testSetUnits_validate_pace_option_pass()
{
$valid_options = [true, false];
foreach($valid_options as $valid_option) {
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['units' => 'raw', 'pace' => $valid_option]);
$this->assertEquals(1.286, reset($pFFA->data_mesgs['record']['speed']));
}
}
/**
* @expectedException Exception
*/
public function testSetUnits_validate_pace_option_fail()
{
$pFFA = new adriangibbons\phpFITFileAnalysis($this->base_dir . $this->filename, ['pace' => 'INVALID']);
}
}

File diff suppressed because it is too large Load Diff

@ -8,7 +8,7 @@ $baseDir = dirname($vendorDir);
return array( return array(
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php', 'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
); );

@ -6,6 +6,7 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'adriangibbons\\' => array($vendorDir . '/adriangibbons/php-fit-file-analysis/src'),
'Twig\\' => array($vendorDir . '/twig/twig/src'), 'Twig\\' => array($vendorDir . '/twig/twig/src'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
@ -22,7 +23,6 @@ return array(
'Hearttrack\\' => array($baseDir . '/src'), 'Hearttrack\\' => array($baseDir . '/src'),
'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'), 'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'), 'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
'Data\\' => array($baseDir . '/src/data'), 'Data\\' => array($baseDir . '/src/data'),
'Console\\' => array($baseDir . '/src/console'), 'Console\\' => array($baseDir . '/src/console'),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -3,26 +3,26 @@
'name' => 'hearttrack/package', 'name' => 'hearttrack/package',
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'ace879472ced43c6e67830f9e1f63dea3b313fec', 'reference' => '3e0d8f9deff68ede14f03250f0ccfc80300ef2d1',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'dev' => true, 'dev' => true,
), ),
'versions' => array( 'versions' => array(
'doctrine/instantiator' => array( 'adriangibbons/php-fit-file-analysis' => array(
'pretty_version' => '1.5.0', 'pretty_version' => 'v3.2.4',
'version' => '1.5.0.0', 'version' => '3.2.4.0',
'reference' => '0a0fa9780f5d4e507415a065172d26a98d02047b', 'reference' => '8efd36b1b963f01c42dc5329626519c040dec664',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/instantiator', 'install_path' => __DIR__ . '/../adriangibbons/php-fit-file-analysis',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => false,
), ),
'graham-campbell/result-type' => array( 'graham-campbell/result-type' => array(
'pretty_version' => 'v1.1.1', 'pretty_version' => 'v1.1.2',
'version' => '1.1.1.0', 'version' => '1.1.2.0',
'reference' => '672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831', 'reference' => 'fbd48bce38f73f8a4ec8583362e732e4095e5862',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../graham-campbell/result-type', 'install_path' => __DIR__ . '/../graham-campbell/result-type',
'aliases' => array(), 'aliases' => array(),
@ -31,7 +31,7 @@
'hearttrack/package' => array( 'hearttrack/package' => array(
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'ace879472ced43c6e67830f9e1f63dea3b313fec', 'reference' => '3e0d8f9deff68ede14f03250f0ccfc80300ef2d1',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -74,207 +74,198 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpoption/phpoption' => array( 'phpoption/phpoption' => array(
'pretty_version' => '1.9.1', 'pretty_version' => '1.9.2',
'version' => '1.9.1.0', 'version' => '1.9.2.0',
'reference' => 'dd3a383e599f49777d8b628dadbb90cae435b87e', 'reference' => '80735db690fe4fc5c76dfa7f9b770634285fa820',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpoption/phpoption', 'install_path' => __DIR__ . '/../phpoption/phpoption',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'phpunit/php-code-coverage' => array( 'phpunit/php-code-coverage' => array(
'pretty_version' => '9.2.29', 'pretty_version' => '10.1.9',
'version' => '9.2.29.0', 'version' => '10.1.9.0',
'reference' => '6a3a87ac2bbe33b25042753df8195ba4aa534c76', 'reference' => 'a56a9ab2f680246adcf3db43f38ddf1765774735',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-code-coverage', 'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpunit/php-file-iterator' => array( 'phpunit/php-file-iterator' => array(
'pretty_version' => '3.0.6', 'pretty_version' => '4.1.0',
'version' => '3.0.6.0', 'version' => '4.1.0.0',
'reference' => 'cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf', 'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-file-iterator', 'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpunit/php-invoker' => array( 'phpunit/php-invoker' => array(
'pretty_version' => '3.1.1', 'pretty_version' => '4.0.0',
'version' => '3.1.1.0', 'version' => '4.0.0.0',
'reference' => '5a10147d0aaf65b58940a0b72f71c9ac0423cc67', 'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-invoker', 'install_path' => __DIR__ . '/../phpunit/php-invoker',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpunit/php-text-template' => array( 'phpunit/php-text-template' => array(
'pretty_version' => '2.0.4', 'pretty_version' => '3.0.1',
'version' => '2.0.4.0', 'version' => '3.0.1.0',
'reference' => '5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28', 'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-text-template', 'install_path' => __DIR__ . '/../phpunit/php-text-template',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpunit/php-timer' => array( 'phpunit/php-timer' => array(
'pretty_version' => '5.0.3', 'pretty_version' => '6.0.0',
'version' => '5.0.3.0', 'version' => '6.0.0.0',
'reference' => '5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2', 'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-timer', 'install_path' => __DIR__ . '/../phpunit/php-timer',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'phpunit/phpunit' => array( 'phpunit/phpunit' => array(
'pretty_version' => '9.6.13', 'pretty_version' => '10.4.2',
'version' => '9.6.13.0', 'version' => '10.4.2.0',
'reference' => 'f3d767f7f9e191eab4189abe41ab37797e30b1be', 'reference' => 'cacd8b9dd224efa8eb28beb69004126c7ca1a1a1',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/phpunit', 'install_path' => __DIR__ . '/../phpunit/phpunit',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/cli-parser' => array( 'sebastian/cli-parser' => array(
'pretty_version' => '1.0.1', 'pretty_version' => '2.0.0',
'version' => '1.0.1.0', 'version' => '2.0.0.0',
'reference' => '442e7c7e687e42adc03470c7b668bc4b2402c0b2', 'reference' => 'efdc130dbbbb8ef0b545a994fd811725c5282cae',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/cli-parser', 'install_path' => __DIR__ . '/../sebastian/cli-parser',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/code-unit' => array( 'sebastian/code-unit' => array(
'pretty_version' => '1.0.8', 'pretty_version' => '2.0.0',
'version' => '1.0.8.0', 'version' => '2.0.0.0',
'reference' => '1fc9f64c0927627ef78ba436c9b17d967e68e120', 'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/code-unit', 'install_path' => __DIR__ . '/../sebastian/code-unit',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/code-unit-reverse-lookup' => array( 'sebastian/code-unit-reverse-lookup' => array(
'pretty_version' => '2.0.3', 'pretty_version' => '3.0.0',
'version' => '2.0.3.0', 'version' => '3.0.0.0',
'reference' => 'ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5', 'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup', 'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/comparator' => array( 'sebastian/comparator' => array(
'pretty_version' => '4.0.8', 'pretty_version' => '5.0.1',
'version' => '4.0.8.0', 'version' => '5.0.1.0',
'reference' => 'fa0f136dd2334583309d32b62544682ee972b51a', 'reference' => '2db5010a484d53ebf536087a70b4a5423c102372',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/comparator', 'install_path' => __DIR__ . '/../sebastian/comparator',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/complexity' => array( 'sebastian/complexity' => array(
'pretty_version' => '2.0.2', 'pretty_version' => '3.1.0',
'version' => '2.0.2.0', 'version' => '3.1.0.0',
'reference' => '739b35e53379900cc9ac327b2147867b8b6efd88', 'reference' => '68cfb347a44871f01e33ab0ef8215966432f6957',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/complexity', 'install_path' => __DIR__ . '/../sebastian/complexity',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/diff' => array( 'sebastian/diff' => array(
'pretty_version' => '4.0.5', 'pretty_version' => '5.0.3',
'version' => '4.0.5.0', 'version' => '5.0.3.0',
'reference' => '74be17022044ebaaecfdf0c5cd504fc9cd5a7131', 'reference' => '912dc2fbe3e3c1e7873313cc801b100b6c68c87b',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/diff', 'install_path' => __DIR__ . '/../sebastian/diff',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/environment' => array( 'sebastian/environment' => array(
'pretty_version' => '5.1.5', 'pretty_version' => '6.0.1',
'version' => '5.1.5.0', 'version' => '6.0.1.0',
'reference' => '830c43a844f1f8d5b7a1f6d6076b784454d8b7ed', 'reference' => '43c751b41d74f96cbbd4e07b7aec9675651e2951',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/environment', 'install_path' => __DIR__ . '/../sebastian/environment',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/exporter' => array( 'sebastian/exporter' => array(
'pretty_version' => '4.0.5', 'pretty_version' => '5.1.1',
'version' => '4.0.5.0', 'version' => '5.1.1.0',
'reference' => 'ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d', 'reference' => '64f51654862e0f5e318db7e9dcc2292c63cdbddc',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/exporter', 'install_path' => __DIR__ . '/../sebastian/exporter',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/global-state' => array( 'sebastian/global-state' => array(
'pretty_version' => '5.0.6', 'pretty_version' => '6.0.1',
'version' => '5.0.6.0', 'version' => '6.0.1.0',
'reference' => 'bde739e7565280bda77be70044ac1047bc007e34', 'reference' => '7ea9ead78f6d380d2a667864c132c2f7b83055e4',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/global-state', 'install_path' => __DIR__ . '/../sebastian/global-state',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/lines-of-code' => array( 'sebastian/lines-of-code' => array(
'pretty_version' => '1.0.3', 'pretty_version' => '2.0.1',
'version' => '1.0.3.0', 'version' => '2.0.1.0',
'reference' => 'c1c2e997aa3146983ed888ad08b15470a2e22ecc', 'reference' => '649e40d279e243d985aa8fb6e74dd5bb28dc185d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/lines-of-code', 'install_path' => __DIR__ . '/../sebastian/lines-of-code',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/object-enumerator' => array( 'sebastian/object-enumerator' => array(
'pretty_version' => '4.0.4', 'pretty_version' => '5.0.0',
'version' => '4.0.4.0', 'version' => '5.0.0.0',
'reference' => '5c9eeac41b290a3712d88851518825ad78f45c71', 'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-enumerator', 'install_path' => __DIR__ . '/../sebastian/object-enumerator',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/object-reflector' => array( 'sebastian/object-reflector' => array(
'pretty_version' => '2.0.4', 'pretty_version' => '3.0.0',
'version' => '2.0.4.0', 'version' => '3.0.0.0',
'reference' => 'b4f479ebdbf63ac605d183ece17d8d7fe49c15c7', 'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-reflector', 'install_path' => __DIR__ . '/../sebastian/object-reflector',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/recursion-context' => array( 'sebastian/recursion-context' => array(
'pretty_version' => '4.0.5', 'pretty_version' => '5.0.0',
'version' => '4.0.5.0', 'version' => '5.0.0.0',
'reference' => 'e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1', 'reference' => '05909fb5bc7df4c52992396d0116aed689f93712',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/recursion-context', 'install_path' => __DIR__ . '/../sebastian/recursion-context',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/resource-operations' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/resource-operations',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/type' => array( 'sebastian/type' => array(
'pretty_version' => '3.2.1', 'pretty_version' => '4.0.0',
'version' => '3.2.1.0', 'version' => '4.0.0.0',
'reference' => '75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7', 'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/type', 'install_path' => __DIR__ . '/../sebastian/type',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'sebastian/version' => array( 'sebastian/version' => array(
'pretty_version' => '3.0.2', 'pretty_version' => '4.0.1',
'version' => '3.0.2.0', 'version' => '4.0.1.0',
'reference' => 'c6c1022351a901512170118436c764e473f6de8c', 'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/version', 'install_path' => __DIR__ . '/../sebastian/version',
'aliases' => array(), 'aliases' => array(),
@ -308,27 +299,27 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'theseer/tokenizer' => array( 'theseer/tokenizer' => array(
'pretty_version' => '1.2.1', 'pretty_version' => '1.2.2',
'version' => '1.2.1.0', 'version' => '1.2.2.0',
'reference' => '34a41e998c2183e22995f158c581e7b5e755ab9e', 'reference' => 'b2ad5003ca10d4ee50a12da31de12a5774ba6b96',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../theseer/tokenizer', 'install_path' => __DIR__ . '/../theseer/tokenizer',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'twig/twig' => array( 'twig/twig' => array(
'pretty_version' => 'v3.7.1', 'pretty_version' => 'v3.8.0',
'version' => '3.7.1.0', 'version' => '3.8.0.0',
'reference' => 'a0ce373a0ca3bf6c64b9e3e2124aca502ba39554', 'reference' => '9d15f0ac07f44dc4217883ec6ae02fd555c6f71d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig', 'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'vlucas/phpdotenv' => array( 'vlucas/phpdotenv' => array(
'pretty_version' => 'v5.5.0', 'pretty_version' => 'v5.6.0',
'version' => '5.5.0.0', 'version' => '5.6.0.0',
'reference' => '1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7', 'reference' => '2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../vlucas/phpdotenv', 'install_path' => __DIR__ . '/../vlucas/phpdotenv',
'aliases' => array(), 'aliases' => array(),

@ -1,47 +0,0 @@
{
"active": true,
"name": "Instantiator",
"slug": "instantiator",
"docsSlug": "doctrine-instantiator",
"codePath": "/src",
"versions": [
{
"name": "1.5",
"branchName": "1.5.x",
"slug": "latest",
"upcoming": true
},
{
"name": "1.4",
"branchName": "1.4.x",
"slug": "1.4",
"aliases": [
"current",
"stable"
],
"maintained": true,
"current": true
},
{
"name": "1.3",
"branchName": "1.3.x",
"slug": "1.3",
"maintained": false
},
{
"name": "1.2",
"branchName": "1.2.x",
"slug": "1.2"
},
{
"name": "1.1",
"branchName": "1.1.x",
"slug": "1.1"
},
{
"name": "1.0",
"branchName": "1.0.x",
"slug": "1.0"
}
]
}

@ -1,35 +0,0 @@
# Contributing
* Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard)
* The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php)
* Any contribution must provide tests for additional introduced conditions
* Any un-confirmed issue needs a failing test case before being accepted
* Pull requests must be sent from a new hotfix/feature branch, not from `master`.
## Installation
To install the project and run the tests, you need to clone it first:
```sh
$ git clone git://github.com/doctrine/instantiator.git
```
You will then need to run a composer installation:
```sh
$ cd Instantiator
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar update
```
## Testing
The PHPUnit version to be used is the one installed as a dev- dependency via composer:
```sh
$ ./vendor/bin/phpunit
```
Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement
won't be merged.

@ -1,38 +0,0 @@
# Instantiator
This library provides a way of avoiding usage of constructors when instantiating PHP classes.
[![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator)
[![Code Coverage](https://codecov.io/gh/doctrine/instantiator/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/instantiator/branch/master)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator)
[![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator)
[![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator)
## Installation
The suggested installation method is via [composer](https://getcomposer.org/):
```sh
composer require doctrine/instantiator
```
## Usage
The instantiator is able to create new instances of any class without using the constructor or any API of the class
itself:
```php
$instantiator = new \Doctrine\Instantiator\Instantiator();
$instance = $instantiator->instantiate(\My\ClassName\Here::class);
```
## Contributing
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out!
## Credits
This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which
has been donated to the doctrine organization, and which is now deprecated in favour of this package.

@ -1,48 +0,0 @@
{
"name": "doctrine/instantiator",
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"type": "library",
"license": "MIT",
"homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
"instantiate",
"constructor"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "https://ocramius.github.io/"
}
],
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-phar": "*",
"ext-pdo": "*",
"doctrine/coding-standard": "^9 || ^11",
"phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.30 || ^5.4"
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"autoload-dev": {
"psr-0": {
"DoctrineTest\\InstantiatorPerformance\\": "tests",
"DoctrineTest\\InstantiatorTest\\": "tests",
"DoctrineTest\\InstantiatorTestAsset\\": "tests"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

@ -1,68 +0,0 @@
Introduction
============
This library provides a way of avoiding usage of constructors when instantiating PHP classes.
Installation
============
The suggested installation method is via `composer`_:
.. code-block:: console
$ composer require doctrine/instantiator
Usage
=====
The instantiator is able to create new instances of any class without
using the constructor or any API of the class itself:
.. code-block:: php
<?php
use Doctrine\Instantiator\Instantiator;
use App\Entities\User;
$instantiator = new Instantiator();
$user = $instantiator->instantiate(User::class);
Contributing
============
- Follow the `Doctrine Coding Standard`_
- The project will follow strict `object calisthenics`_
- Any contribution must provide tests for additional introduced
conditions
- Any un-confirmed issue needs a failing test case before being
accepted
- Pull requests must be sent from a new hotfix/feature branch, not from
``master``.
Testing
=======
The PHPUnit version to be used is the one installed as a dev- dependency
via composer:
.. code-block:: console
$ ./vendor/bin/phpunit
Accepted coverage for new contributions is 80%. Any contribution not
satisfying this requirement wont be merged.
Credits
=======
This library was migrated from `ocramius/instantiator`_, which has been
donated to the doctrine organization, and which is now deprecated in
favour of this package.
.. _composer: https://getcomposer.org/
.. _CONTRIBUTING.md: CONTRIBUTING.md
.. _ocramius/instantiator: https://github.com/Ocramius/Instantiator
.. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard
.. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php

@ -1,4 +0,0 @@
.. toctree::
:depth: 3
index

@ -1,16 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
phpVersion="8.2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

@ -1,12 +0,0 @@
<?php
namespace Doctrine\Instantiator\Exception;
use Throwable;
/**
* Base exception marker interface for the instantiator component
*/
interface ExceptionInterface extends Throwable
{
}

@ -1,50 +0,0 @@
<?php
namespace Doctrine\Instantiator\Exception;
use InvalidArgumentException as BaseInvalidArgumentException;
use ReflectionClass;
use function interface_exists;
use function sprintf;
use function trait_exists;
/**
* Exception for invalid arguments provided to the instantiator
*/
class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface
{
public static function fromNonExistingClass(string $className): self
{
if (interface_exists($className)) {
return new self(sprintf('The provided type "%s" is an interface, and cannot be instantiated', $className));
}
if (trait_exists($className)) {
return new self(sprintf('The provided type "%s" is a trait, and cannot be instantiated', $className));
}
return new self(sprintf('The provided class "%s" does not exist', $className));
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
public static function fromAbstractClass(ReflectionClass $reflectionClass): self
{
return new self(sprintf(
'The provided class "%s" is abstract, and cannot be instantiated',
$reflectionClass->getName()
));
}
public static function fromEnum(string $className): self
{
return new self(sprintf(
'The provided class "%s" is an enum, and cannot be instantiated',
$className
));
}
}

@ -1,59 +0,0 @@
<?php
namespace Doctrine\Instantiator\Exception;
use Exception;
use ReflectionClass;
use UnexpectedValueException as BaseUnexpectedValueException;
use function sprintf;
/**
* Exception for given parameters causing invalid/unexpected state on instantiation
*/
class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface
{
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
public static function fromSerializationTriggeredException(
ReflectionClass $reflectionClass,
Exception $exception
): self {
return new self(
sprintf(
'An exception was raised while trying to instantiate an instance of "%s" via un-serialization',
$reflectionClass->getName()
),
0,
$exception
);
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
public static function fromUncleanUnSerialization(
ReflectionClass $reflectionClass,
string $errorString,
int $errorCode,
string $errorFile,
int $errorLine
): self {
return new self(
sprintf(
'Could not produce an instance of "%s" via un-serialization, since an error was triggered '
. 'in file "%s" at line "%d"',
$reflectionClass->getName(),
$errorFile,
$errorLine
),
0,
new Exception($errorString, $errorCode)
);
}
}

@ -1,262 +0,0 @@
<?php
namespace Doctrine\Instantiator;
use ArrayIterator;
use Doctrine\Instantiator\Exception\ExceptionInterface;
use Doctrine\Instantiator\Exception\InvalidArgumentException;
use Doctrine\Instantiator\Exception\UnexpectedValueException;
use Exception;
use ReflectionClass;
use ReflectionException;
use Serializable;
use function class_exists;
use function enum_exists;
use function is_subclass_of;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function strlen;
use function unserialize;
use const PHP_VERSION_ID;
final class Instantiator implements InstantiatorInterface
{
/**
* Markers used internally by PHP to define whether {@see \unserialize} should invoke
* the method {@see \Serializable::unserialize()} when dealing with classes implementing
* the {@see \Serializable} interface.
*
* @deprecated This constant will be private in 2.0
*/
public const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C';
/** @deprecated This constant will be private in 2.0 */
public const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
/**
* Used to instantiate specific classes, indexed by class name.
*
* @var callable[]
*/
private static $cachedInstantiators = [];
/**
* Array of objects that can directly be cloned, indexed by class name.
*
* @var object[]
*/
private static $cachedCloneables = [];
/**
* @param string $className
* @phpstan-param class-string<T> $className
*
* @return object
* @phpstan-return T
*
* @throws ExceptionInterface
*
* @template T of object
*/
public function instantiate($className)
{
if (isset(self::$cachedCloneables[$className])) {
/** @phpstan-var T */
$cachedCloneable = self::$cachedCloneables[$className];
return clone $cachedCloneable;
}
if (isset(self::$cachedInstantiators[$className])) {
$factory = self::$cachedInstantiators[$className];
return $factory();
}
return $this->buildAndCacheFromFactory($className);
}
/**
* Builds the requested object and caches it in static properties for performance
*
* @phpstan-param class-string<T> $className
*
* @return object
* @phpstan-return T
*
* @template T of object
*/
private function buildAndCacheFromFactory(string $className)
{
$factory = self::$cachedInstantiators[$className] = $this->buildFactory($className);
$instance = $factory();
if ($this->isSafeToClone(new ReflectionClass($instance))) {
self::$cachedCloneables[$className] = clone $instance;
}
return $instance;
}
/**
* Builds a callable capable of instantiating the given $className without
* invoking its constructor.
*
* @phpstan-param class-string<T> $className
*
* @phpstan-return callable(): T
*
* @throws InvalidArgumentException
* @throws UnexpectedValueException
* @throws ReflectionException
*
* @template T of object
*/
private function buildFactory(string $className): callable
{
$reflectionClass = $this->getReflectionClass($className);
if ($this->isInstantiableViaReflection($reflectionClass)) {
return [$reflectionClass, 'newInstanceWithoutConstructor'];
}
$serializedString = sprintf(
'%s:%d:"%s":0:{}',
is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,
strlen($className),
$className
);
$this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
return static function () use ($serializedString) {
return unserialize($serializedString);
};
}
/**
* @phpstan-param class-string<T> $className
*
* @phpstan-return ReflectionClass<T>
*
* @throws InvalidArgumentException
* @throws ReflectionException
*
* @template T of object
*/
private function getReflectionClass(string $className): ReflectionClass
{
if (! class_exists($className)) {
throw InvalidArgumentException::fromNonExistingClass($className);
}
if (PHP_VERSION_ID >= 80100 && enum_exists($className, false)) {
throw InvalidArgumentException::fromEnum($className);
}
$reflection = new ReflectionClass($className);
if ($reflection->isAbstract()) {
throw InvalidArgumentException::fromAbstractClass($reflection);
}
return $reflection;
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @throws UnexpectedValueException
*
* @template T of object
*/
private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void
{
set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool {
$error = UnexpectedValueException::fromUncleanUnSerialization(
$reflectionClass,
$message,
$code,
$file,
$line
);
return true;
});
try {
$this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
} finally {
restore_error_handler();
}
if ($error) {
throw $error;
}
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @throws UnexpectedValueException
*
* @template T of object
*/
private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void
{
try {
unserialize($serializedString);
} catch (Exception $exception) {
throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
}
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool
{
return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
}
/**
* Verifies whether the given class is to be considered internal
*
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
private function hasInternalAncestors(ReflectionClass $reflectionClass): bool
{
do {
if ($reflectionClass->isInternal()) {
return true;
}
$reflectionClass = $reflectionClass->getParentClass();
} while ($reflectionClass);
return false;
}
/**
* Checks if a class is cloneable
*
* Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects.
*
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
private function isSafeToClone(ReflectionClass $reflectionClass): bool
{
return $reflectionClass->isCloneable()
&& ! $reflectionClass->hasMethod('__clone')
&& ! $reflectionClass->isSubclassOf(ArrayIterator::class);
}
}

@ -1,24 +0,0 @@
<?php
namespace Doctrine\Instantiator;
use Doctrine\Instantiator\Exception\ExceptionInterface;
/**
* Instantiator provides utility methods to build objects without invoking their constructors
*/
interface InstantiatorInterface
{
/**
* @param string $className
* @phpstan-param class-string<T> $className
*
* @return object
* @phpstan-return T
*
* @throws ExceptionInterface
*
* @template T of object
*/
public function instantiate($className);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save